如何手工展开函数栈来定位问题

作者:gfree.wind@gmail.com
博客:blog.focus-linux.net     linuxfocus.blog.chinaunix.net

当程序crash的时候,我们可以通过coredump文件,来定位问题。比如使用bt命令可以完整的展开函数的调用栈。但是有些时候,部分栈的数据可能被损坏,导致gdb无法直接显示函数的调用栈。那么这时就需要我们手工展开函数栈。

关于x86的函数调用栈的示意图基本如下图所示:
如何手工展开函数栈来定位问题

关于参数的压栈顺序,上图为cdecl方式,这个可以通过编译选项修改。GCC默认使用cdecl。

下面看一下例子:
  1. #include stdlib.h>
  2. #include stdio.h>


  3. static int test(int a, int b, int c)
  4. {
  5.     return a+b+c;
  6. }

  7. int main()
  8. {
  9.     int a = 1;
  10.     int b = 2;
  11.     int c = 3;

  12.     int d = test(a, b, c);

  13.     printf("%d\n", d);

  14.     return 0;
  15. }
编译:gcc -g -Wall test.c
进入test,查看函数调用栈:
  1. Breakpoint 1, test (a=1, b=2, c=3) at test.c:7
  2. 7 return a+b+c;
  3. Missing separate debuginfos, use: debuginfo-install glibc-2.11-2.i686
  4. (gdb) bt
  5. #0 test (a=1, b=2, c=3) at test.c:7
  6. #1 0x08048412 in main () at test.c:16
那么现在查看一下寄存器:
  1. eax 0x1 1
  2. ecx 0x2c0187d8 738297816
  3. edx 0x1 1
  4. ebx 0x73fff4 7602164
  5. esp 0xbffff048 0xbffff048
  6. ebp 0xbffff048 0xbffff048
  7. esi 0x0 0
  8. edi 0x0 0
  9. eip 0x80483c7 0x80483c7 test+3>
  10. eflags 0x286 [ PF SF IF ]
  11. cs 0x73 115
  12. ss 0x7b 123
  13. ds 0x7b 123
  14. es 0x7b 123
  15. fs 0x0 0
  16. gs 0x33 51
得到ebp的地址为0xbffff048,现在检查这个地址的内存
  1. (gdb) x /8x 0xbffff048
  2. 0xbffff048: 0xbffff078 0x08048412 0x00000001 0x00000002
  3. 0xbffff058: 0x00000003 0x0073fff4 0x00000001 0x00000002
下面分析一下这些内存的内容:
1. 0xbffff078:为test的调用者,即main函数的bp地址;BP地址即为该函数的栈顶指针。
2. 0x08048412:为test的返回地址,与前面的bt的输出相符;
3. 后面的0x00000001,0x00000002,0x00000003,为传给test的三个参数,且参数顺序为由右向左压栈——注意这个顺序是可以通过改变编译参数改变的。

回到main中,验证一下bp寄存器的内容:
  1. 0x08048412 in main () at test.c:16
  2. 16 int d = test(a, b, c);
  3. Value returned is $2 = 6
  4. (gdb) info registers
  5. eax 0x6 6
  6. ecx 0x39ff7a48 973044296
  7. edx 0x1 1
  8. ebx 0x73fff4 7602164
  9. esp 0xbffff050 0xbffff050
  10. ebp 0xbffff078 0xbffff078
可见BP的地址确实为0xbffff078,与之前的分析相符。

注:关于压栈顺序,参数的传递方式等等,都可以通过编译选项来指定或者禁止的。本文的情况为GCC的默认行为。


上一篇:Echarts极地坐标图直径半径设置


下一篇:Java 设置文件只读