zl程序教程

您现在的位置是:首页 >  其它

当前栏目

每天学点GDB(七)

每天 GDB
2023-09-14 09:00:25 时间

近两周在做一个trouble shooting,需要对函数调用栈进行分析以找出入参和局部变量。因为在编译生成可执行程序的时候,用gcc进行了O2的优化,许多假设的函数调用栈模型都不成立了。花了一番周折,终于正确的翻译出入参和局部变量,此一旅程中的一些经验还是值得记录下来。

在32位x86系统上,函数调用栈的布局如下图所示。

0397c89f8884fe5cef54f7a1b3de79e2b7aed0c6

栈底在高地址段,栈顶在低地址段。

从栈底到栈顶的内容分别为:

保存的寄存器值 被调用函数的局部变量

如果带有调试信息,则要获取上述4个部分的值很容易,对应的指令分别如下。

info args info frame info registers info locals

如果没有调试信息,则可以根据这一模型并结合反汇编的结果来算出入参与局部变量的存储位置。针对32位的具体例子比较容易找到。

现在专门提一提在x86 64位下的不同,在x86 64下,因为寄存器数量增多,为了提高效率,入参基本上都是通过寄存器来传递。示例程序如下:

#include stdlib.h 

#include stdio.h 

#include unistd.h 

int demo_func(char* msg, int a, int b); 


0x00000000004004b8 +8 : mov %edi,-0x14(%rbp) 0x00000000004004bb +11 : mov %rsi,-0x20(%rbp) 0x00000000004004bf +15 : movq $0x400594,-0x8(%rbp) 0x00000000004004c7 +23 : movl $0x140,-0xc(%rbp) 0x00000000004004ce +30 : movl $0x64,-0x10(%rbp) 0x00000000004004d5 +37 : mov -0x10(%rbp),%edx 0x00000000004004d8 +40 : mov -0xc(%rbp),%ecx 0x00000000004004db +43 : mov -0x8(%rbp),%rax 0x00000000004004df +47 : mov %ecx,%esi 0x00000000004004e1 +49 : mov %rax,%rdi 0x00000000004004e4 +52 : callq 0x4004f0 demo_func 0x00000000004004e9 +57 : mov $0x0,%eax 0x00000000004004ee +62 : leaveq 0x00000000004004ef +63 : retq End of assembler dump.

注意callq 0x4004f0 demo_func 上的三行代码,它们表示demo_func的入参,可以看出入参是通过寄存器来进行传递的。
而寄存器在demo_func中可以会被改写,于是在改写之前,这些入参会被再次保存,它们保存在stack frame中,具体位置需要根据反汇编结果进行计算。即需要根据被调用函数的反汇编代码来计算入参。

demo_func的反汇编结果如下:

Dump of assembler code for function demo_func:

 0x00000000004004f0 +0 : push %rbp

 0x00000000004004f1 +1 : mov %rsp,%rbp

 0x00000000004004f4 +4 : mov %rdi,-0x18(%rbp)

 0x00000000004004f8 +8 : mov %esi,-0x1c(%rbp)

 0x00000000004004fb +11 : mov %edx,-0x20(%rbp)

 0x00000000004004fe +14 : mov -0x20(%rbp),%eax

 0x0000000000400501 +17 : mov -0x1c(%rbp),%edx

 0x0000000000400504 +20 : add %edx,%eax

 0x0000000000400506 +22 : mov %eax,-0x4(%rbp)

 0x0000000000400509 +25 : mov -0x4(%rbp),%eax

 0x000000000040050c +28 : pop %rbp

 0x000000000040050d +29 : retq 

End of assembler dump.

编译时如果加上-O2优化,则反汇编结果变为:

Dump of assembler code for function demo_func:

 0x00000000004004b0 +0 : lea (%rsi,%rdx,1),%eax

 0x00000000004004b3 +3 : retq 

End of assembler dump.

内容极度简化,在具体计算时,一定要根据反汇编的结果来进行。


Linux工具学习之【gdb】 vim 可以编写代码,gcc/g++ 可以编译代码,此时只最后一件神器,就能进行完整的开发工作,那就是通过 gdb 调试代码,毕竟谁都不敢保证自己的代码没有问题,所以就有调试器这种东西帮助我们定位问题,进而解决问题
gdb使用笔记 gdb是一款UNIX及UNIX-like下的调试工具,本文是对于gdb在Linux下使用的基本命令的总结gdb调试视频演示,gdb调试基础指令,gdb调试其他命令,gdb常见错误说明
如何优♂雅地学习GDB调试(一) 本章我们将带着大家高雅的学一学令众多习惯图形化页面的朋友难受的 gdb 调试,这部分知识可以选择性学习学习,以后倘若遇到一些问题时能在 Linux 内简单调试,还是很香的。然后在讲讲 gcc 和 g++,系统讲解程序运行时的各个过程。
如何优♂雅地学习GDB调试(二) 本章我们将带着大家高雅的学一学令众多习惯图形化页面的朋友难受的 gdb 调试,这部分知识可以选择性学习学习,以后倘若遇到一些问题时能在 Linux 内简单调试,还是很香的。然后在讲讲 gcc 和 g++,系统讲解程序运行时的各个过程。