本文描述的是在intel X86_64體系架構中,C語言函數在執行過程中的記憶體棧結構情況。其它體系會有所不同,僅供參考,實驗在Ubuntu14.04 X64中使用,考慮到寄存器的熟悉程度,編譯的可執行程式是32位的。
源碼如下:
#include<stdio.h>
void func(int a, int b)
{
int sec[5];
sec[2] = a;
return;
}
int main()
{
func(0xFF, 0x02);
return 0;
}
編譯源碼 gcc -win32 func.c -o func
執行gdb調用可執行程式 gdb func
使用gdb反彙編func和main函數
func:
(gdb) disas func
Dump of assembler code for function func:
0x080483ed <+0>: push %ebp
0x080483ee <+1>: mov %esp,%ebp
0x080483f0 <+3>: sub $0x20,%esp
0x080483f3 <+6>: mov 0x8(%ebp),%eax
0x080483f6 <+9>: mov %eax,-0xc(%ebp)
0x080483f9 <+12>: nop
0x080483fa <+13>: leave
0x080483fb <+14>: ret
End of assembler dump.
main:
(gdb) disas main
Dump of assembler code for function main:
0x080483fc <+0>: push %ebp
0x080483fd <+1>: mov %esp,%ebp
0x080483ff <+3>: sub $0x8,%esp
0x08048402 <+6>: movl $0x2,0x4(%esp)
0x0804840a <+14>: movl $0xff,(%esp)
0x08048411 <+21>: call 0x80483ed <func>
0x08048416 <+26>: mov $0x0,%eax
0x0804841b <+31>: leave
0x0804841c <+32>: ret
End of assembler dump.
在函數func添加斷點 (gdb)b func,使用gdb執行程式(gdb) r,檢視此刻寄存器的值。
Breakpoint 1, 0x080483f3 in func ()
(gdb) i r
eax 0x1 1
ecx 0xd14b7383 -783584381
edx 0xffffd104 -12028
ebx 0xf7fbf000 -134483968
esp 0xffffd0a8 0xffffd0a8
ebp 0xffffd0c8 0xffffd0c8
esi 0x0 0
edi 0x0 0
eip 0x80483f3 0x80483f3 <func+6>
eflags 0x282 [ SF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
因為函數棧位址是逐漸減小的,此時檢視ebp位址8個32位整數值。
(gdb) x /10x 0xffffd0c8
0xffffd0c8: 0xffffd0d8 0x08048416 0x000000ff 0x00000002
0xffffd0d8: 0x00000000 0xf7e31a63 0x00000001 0xffffd174
從ebp所存位址儲存的資料可以看到:
1、參數1 0x000000ff在offset 2上,參數2 0x00000002在offset 3上,由此可見函數的參數是從右向左入棧,是以如果函數參數中有表達式時,也會先執行右邊的參數;
2、offset 1 存放的值是0x08048416,可以從main的反彙編代碼中看見,該值為func函數傳回要執行指令的位址,即offset 1存放的是函數傳回位址。
3、offset 0 存放的值是0xffffd0d8,該值在文中沒有顯示可查處,請讀者可以自行測試,可以在執行main函數時檢視寄存器ebp的值,即ebp存放值指向位址對應的值是主調函數的ebp。
4、從操作
0x080483f3 <+6>: mov 0x8(%ebp),%eax
0x080483f6 <+9>: mov %eax,-0xc(%ebp)
可以看出,局部資料sec是從位址ebp開始申請,是以局部變量是儲存在函數棧中。
OK,綜上所述,ebp向下位置存放函數傳回位址,再向下是函數使用的參數值,而ebp存放值是主調函數的ebp,可以得出下圖的結果,請将func抽象為被調函數,main抽象為主調函數。