gdb 如何呼叫函式?

2021-09-12 17:14:44 字數 2881 閱讀 1585

在這週,我發現我可以從 gdb 上呼叫 c 函式。這看起來很酷,因為在過去我認為 gdb 最多只是乙個唯讀除錯工具。

我對 gdb 能夠呼叫函式感到很吃驚。正如往常所做的那樣,我在 twitter 上詢問這是如何工作的。我得到了大量的有用答案。我最喜歡的答案是 evan klitzke 的示例 c **,它展示了 gdb 如何呼叫函式。**能夠執行,這很令人激動!

我(通過一些跟蹤和實驗)認為那個示例 c **和 gdb 實際上如何呼叫函式不同。因此,在這篇文章中,我將會闡述 gdb 是如何呼叫函式的,以及我是如何知道的。

關於 gdb 如何呼叫函式,還有許多我不知道的事情,並且,在這兒我寫的內容有可能是錯誤的。

從 gdb 中呼叫 c 函式意味著什麼?

在開始講解這是如何工作之前,我先快速的談論一下我是如何發現這件令人驚訝的事情的。

假如,你已經在執行乙個 c 程式(目標程式)。你可以執行程式中的乙個函式,只需要像下面這樣做:

暫停程式(因為它已經在執行中)

找到你想呼叫的函式的位址(使用符號表)

使程式(目標程式)跳轉到那個位址

當函式返回時,恢復之前的指令指標和暫存器

通過符號表來找到想要呼叫的函式的位址非常容易。下面是一段非常簡單但能夠工作的**,我在 linux 上使用這段**作為例子來講解如何找到位址。這段**使用 elf crate。如果我想找到 pid 為 2345 的程序中的 foo 函式的位址,那麼我可以執行 elf_symbol_value("/proc/2345/exe", "foo")。

fn elf_symbol_value(file_name: &str, symbol_name: &str) -> result>

}}none.ok_or("no symbol found")?這並不能夠真的發揮作用,你還需要找到檔案的記憶體對映,並將符號偏移量加到檔案對映的起始位置。找到記憶體對映並不困難,它位於 /proc/pid/maps 中。

總之,找到想要呼叫的函式位址對我來說很直接,但是其餘部分(改變指令指標,恢復暫存器等)看起來就不這麼明顯了。

你不能僅僅進行跳轉

我已經說過,你不能夠僅僅找到你想要執行的那個函式位址,然後跳轉到那兒。我在 gdb 中嘗試過那樣做(jump foo),然後程式出現了段錯誤。毫無意義。

如何從 gdb 中呼叫 c 函式

首先,這是可能的。我寫了乙個非常簡潔的 c 程式,它所做的事只有 sleep 1000 秒,把這個檔案命名為 test.c :

int foo()

int main() {

sleep(1000);
最後,我們使用 gdb 來跟蹤 test 這一程式:

$ sudo gdb -p $(pgrep -f test)

(gdb) p foo()

$1 = 3

(gdb) quit

我執行 p foo() 然後它執行了這個函式!這非常有趣。

這有什麼用?

下面是一些可能的用途:

它使得你可以把 gdb 當成乙個 c 應答式程式(repl),這很有趣,我想對開發也會有用

在 gdb 中進行除錯的時候展示/瀏覽複雜資料結構的功能函式(感謝 @invalidop)

在程序執行時設定乙個任意的名字空間(我的同事 nelhage 對此非常驚訝)

可能還有許多我所不知道的用途

它是如何工作的

當我在 twitter 上詢問從 gdb 中呼叫函式是如何工作的時,我得到了大量有用的回答。許多答案是「你從符號表中得到了函式的位址」,但這並不是完整的答案。

有個人告訴了我兩篇關於 gdb 如何工作的系列文章:原生除錯:第一部分,原生除錯:第二部分。第一部分講述了 gdb 是如何呼叫函式的(指出了 gdb 實際上完成這件事並不簡單,但是我將會盡力)。

步驟列舉如下:

停止程序

建立乙個新的棧框(遠離真實棧)

儲存所有暫存器

設定你想要呼叫的函式的暫存器引數

設定棧指標指向新的棧框stack frame

在記憶體中某個位置放置一條陷阱指令

為陷阱指令設定返回位址

設定指令暫存器的值為你想要呼叫的函式位址

再次執行程序!

(lctt 譯註:如果將這個呼叫的函式看成乙個單獨的執行緒,gdb 實際上所做的事情就是乙個簡單的執行緒上下文切換)

我不知道 gdb 是如何完成這些所有事情的,但是今天晚上,我學到了這些所有事情中的其中幾件。

建立乙個棧框

如果你想要執行乙個 c 函式,那麼你需要乙個棧來儲存變數。你肯定不想繼續使用當前的棧。準確來說,在 gdb 呼叫函式之前(通過設定函式指標並跳轉),它需要設定棧指標到某個地方。

我認為它在當前棧的棧頂上構造了乙個新的棧框來進行呼叫!

以及你確定是這樣嗎?它應該是分配乙個偽棧,然後臨時將 sp (棧指標暫存器)的值改為那個棧的位址。你可以試一試,你可以在那兒設定乙個斷點,然後看一看棧指標暫存器的值,它是否和當前程式暫存器的值相近?

我通過 gdb 做了乙個試驗:

(gdb) p $rsp

$7 = (void *) 0x7ffea3d0bca8

(gdb) break foo

breakpoint 1 at 0x40052a

(gdb) p foo()

breakpoint 1, 0x000000000040052a in foo ()

(gdb) p $rsp

$8 = (void *) 0x7ffea3d0bc00

這看起來符合「gdb 在當前棧的棧頂構造了乙個新的棧框」這一理論。因為棧指標($rsp)從 0x7ffea3d0bca8 變成了 0x7ffea3d0bc00 —— 棧指標從高位址往低位址長。所以 0x7ffea3d0bca8 在 0x7ffea3d0bc00 的後面。真是有趣!

所以,看起來 gdb 只是在當前棧所在位置建立了乙個新的棧框。這令我很驚訝!

gdb 如何呼叫函式?

在這週,我發現我可以從 gdb 上呼叫 c 函式。這看起來很酷,因為在過去我認為 gdb 最多只是乙個唯讀除錯工具。我對 gdb 能夠呼叫函式感到很吃驚。正如往常所做的那樣,我在 twitter 上詢問這是如何工作的。我得到了大量的有用答案。我最喜歡的答案是 evan klitzke 的示例 c 它...

gdb除錯時呼叫函式 設定觀察點以及GDB的巨集

table of contents 除錯時呼叫函式 觀察點gdb巨集 除錯是一項複雜的任務。開發人員大部分時間都花在除錯上,因此熟悉許多除錯工具很重要 在linux中,本機偵錯程式是gdb,它是基於命令列的,看起來醜陋而原始。許多開發人員,尤其是那些從windows移走並使用visual studi...

gdb 檢視函式呼叫堆疊(frame概念)

1,使用bt backtrace 命令檢視當前堆疊 gdb bt 0 muduo poller poll this 0x62e010,timeoutms 10000,activechannels 0x7fffffffe3c0 at poller.cc 31 1 0x0000000000403f60 ...