當程序crash的時候,我們可以通過coredump文件,來定位問題。比如使用bt命令可以完整的展開函數的調用棧。但是有些時候,部分棧的數據可能被損壞,導致gdb無法直接顯示函數的調用棧。那麼這時就需要我們手工展開函數棧。
關於x86的函數調用棧的示意圖基本如下圖所示:
關於參數的壓棧順序,上圖為cdecl方式,這個可以通過編譯選項修改。GCC默認使用cdecl。
下面看一下例子:
#include <stdlib.h>
#include <stdio.h>
static int test(int a, int b, int c)
{
return a+b+c;
}
int main()
{
int a = 1;
int b = 2;
int c = 3;
int d = test(a, b, c);
printf("%d\n", d);
return 0;
}
編譯:gcc -g -Wall test.c 進入test,查看函數調用棧: Breakpoint 1, test (a=1, b=2, c=3) at test.c:7 7 return a+b+c; Missing separate debuginfos, use: debuginfo-install glibc-2.11-2.i686
(gdb) bt
#0 test (a=1, b=2, c=3) at test.c:7
#1 0x08048412 in main () at test.c:16
那麼現在查看一下寄存器:
eax 0x1 1
ecx 0x2c0187d8 738297816
edx 0x1 1
ebx 0x73fff4 7602164
esp 0xbffff048 0xbffff048
ebp 0xbffff048 0xbffff048
esi 0x0 0
edi 0x0 0
eip 0x80483c7 0x80483c7 <test+3>
eflags 0x286 [ PF SF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
得到ebp的地址為0xbffff048
,現在檢查這個地址的內存
(gdb) x /8x 0xbffff048
0xbffff048: 0xbffff078 0x08048412 0x00000001 0x00000002
0xbffff058: 0x00000003 0x0073fff4 0x00000001 0x00000002
下面分析一下這些內存的內容:
- 0xbffff078:為test的調用者,即main函數的bp地址;BP地址即為該函數的棧頂指針。
回到main中,驗證一下bp寄存器的內容:
0x08048412 in main () at test.c:16
16 int d = test(a, b, c);
Value returned is $2 = 6
(gdb) info registers
eax 0x6 6
ecx 0x39ff7a48 973044296
edx 0x1 1
ebx 0x73fff4 7602164
esp 0xbffff050 0xbffff050
ebp 0xbffff078 0xbffff078
可見BP的地址確實為0xbffff078,與之前的分析相符。
注:關於壓棧順序,參數的傳遞方式等等,都可以通過編譯選項來指定或者禁止的。本文的情況為GCC的默認行為。