在實作這個技術前有兩個關鍵點要先解決:
1. 如何取得此 function 返回位址.
2. 如何依據返回位址查知函式名稱.
關於第一點, 必須先了解堆疊(Stack) 和函式呼叫的處理關係. 堆疊是一個後進先出(Last-In-First-Out)的資料結構. 當呼叫某個函式時, 相關的暫存器(Register)就會被存入堆疊. 而當函式返回時便會從堆疊裡取回返回位址以便回到原來呼叫的下一個指令繼續執行. 至於暫存器(Register), 其中 EIP 是 Instruction Pointer, 用來指出 CPU 將要執行指令的位址. ESP 暫存器則是用來指向目前堆壘的位址.
我們先寫個小程式來觀察可行性.
----------- test.c -----------
void test()
{
}
int main()
{
test();
}
------------------------------
[tim@localhost whocallme]$ gcc -o test test.c
[tim@localhost whocallme]$ gdb ./test
GNU gdb 5.3-25mdk (Mandrake Linux)
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain
conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i586-mandrake-linux-gnu"...
(gdb) b test
Breakpoint 1 at 0x804832f
(gdb) r
Starting program: /home/tim/research/whocallme/test
Breakpoint 1, 0x0804832f in test ()
(gdb) info reg
eax 0x0 0
ecx 0x1 1
edx 0x4014fe50 1075117648
ebx 0x4014e9a0 1075112352
esp 0xbffff698 0xbffff698
ebp 0xbffff698 0xbffff698
esi 0x40013880 1073821824
edi 0xbffff6f4 -1073744140
eip 0x804832f 0x804832f
(gdb) disas test
Dump of assembler code for function test:
0x804832c
0x804832d
0x804832f
0x8048330
End of assembler dump.
ebp 暫存器值是 0xbffff698, 也就是原來的堆疊位址. 我們知道在呼叫函式時(call) CPU 會將返回位址存入堆疊, 因此可以從 ebp 暫存器的位址裡面找到我們需要的返回位址:
(gdb) p/x *0xbffff698
$1 = 0xbffff6a8
別忘了, 一進入此函式時 push $ebp 已被執行, 因此堆疊位址已被減 4, 所以要取得正確的值還得把 4 加回去才行:
(gdb) p/x *(0xbffff698+4)
$2 = 0x8048346
這個值應該就是 test() 正確的返回位址, 來檢查看看:
(gdb) disas main
Dump of assembler code for function main:
0x8048331
0x8048332
0x8048334
0x8048337
0x804833a
0x804833f
0x8048341
0x8048346
0x8048347
0x8048348
0x8048349
0x804834a
0x804834b
0x804834c
0x804834d
0x804834e
0x804834f
End of assembler dump.
果然在 call
接下來我們就用 C 和一些 assembly 配合來實作.
------------- test-1.c ------------------
void test()
{
unsigned long *stack;
asm ("movl %%ebp, %0\n"
printf("ret address = 0x%x\n", *(stack+1));
}
int main()
{
test();
}
-----------------------------------------
[tim@localhost whocallme]$ ./test-1
ret address = 0x8048394
[tim@localhost whocallme]$ gdb ./test-1
(gdb) disas main
Dump of assembler code for function main:
0x804837f
0x8048380
0x8048382
0x8048385
0x8048388
0x804838d
0x804838f
0x8048394
0x8048395
0x8048396
第一個關鍵點目前已解決, 再來要想想怎麼要能夠依記憶體位址查知所處的函式名稱呢?
更多詳細的內容請看 Who Call Me? 一文.