每天学点GDB(七)

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

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

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

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

  1. 函数入参
  2. 返回地址
  3. 保存的寄存器值
  4. 被调用函数的局部变量

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

  1. info args
  2. info frame
  3. info registers
  4. 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); 

int main(int argc, char** argv) {
    char* info = "just a demo";
    int a,b;
    a = 320;
    b = 100;
    demo_func(info, a, b);
    return 0;
}

int demo_func(char* msg, int a, int b) {
    int sum;
    sum = a + b;
    return  sum;
}

main函数反汇编结果如下:

Dump of assembler code for function main:
   0x00000000004004b0 <+0>:    push   %rbp
   0x00000000004004b1 <+1>:    mov    %rsp,%rbp
   0x00000000004004b4 <+4>:    sub    $0x20,%rsp
   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.

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

时间: 2024-10-29 02:40:59

每天学点GDB(七)的相关文章

10天学安卓-第七天

原文:10天学安卓-第七天 我们上次学习了百度定位以及SharedPreferences的使用,不知道大家有没有注意到我们新加了一个方法: protected void onStop() { super.onStop(); mLocationClient.stop(); }   这个方法的作用是在界面停止的时候,同时停止百度定位功能.   联想到我们还有onCreate,那么这两个方法是做什么用的?是什么原理呢? 这就需要我们来了解一下Activity的生命周期. Activity生命周期 Ac

每天学点GDB(六)

<一>如何利用gdb对coredump进行分析 本篇主要讲解如何利用gdb对coredump进行分析 gdb ./demo core 查看调用堆栈 gdb)bt 查看更为完整的信息 gdb) bt full 如果是多线程,想看每个线程的调用堆栈 gdb) thread apply all bt 至于如何分析内存变量之类的,在本一系列前面的章节有专门论述,可以参考. 想调试已经在运行的程序 gdb ./demo 假设当前运行着的demo进程为1234,则在gdb中运行attach进行关联 gdb

每天学点GDB(五)

使用GDB来进行STL容器的调试 现代C++中STL使用的越来越普遍,较之其它类型,stl容器类的调试显得复杂度更好.本篇以map为例说明如何利用gdb来遍历map中的各成员变量. 源码如下: #include <map> #include <iostream> #include <string> using namespace std; int main(int arg, char** argv) { map<int, string> mapExample

每天学点GDB(一)

<一>简单示例--Hello World 在Linux环境下进行C或是C++编程,调试工具首选GDB. GDB的功能很多,一下子全弄明白似乎不太可能.那么就从最简单的使用说起吧.一谈起简单,Helloworld就成了最佳的选择了. #include <stdio.h> #include <stdlib.h> 4 int main(int argc, char** argv) { printf("hello,world\n"); return 0; }

每天学点GDB(二)

<一>如何让将调试的内容保存到外部文件里面 在上一篇提及如何在断点处打印调试信息后,程序自动继续执行.本节主要讲述如何让将调试的内容保存到外部文件里面. 默认情况下,日志是没有打开的,所有的调试信息都会在屏幕中显示,即默认是输出到stdout中的.那么有没有可能将输出到屏幕中的内容保存到文件里呢.答案自然是肯定的,这里面有个地方遇要注意一下子,具体会在下面的示例中提及. 将日志文件打开,不指定文件名的话,默认的文件名是gdb.txt. gdb)set logging on 查看更多有关logg

每天学点GDB(三)

<一>强大的反汇编能力 GDB提供了强大的反汇编能力,本节就围绕于该主题而展开. 继续以Hello.c为例. 1 2 3 4 5 6 7 #include <stdlib.h> #include <stdio.h>   int main(int argc, char** argv) {   printf("hello,world\n");   return 0; } 编译生成可执行文件 gcc -o hello -g hello.c 用gdb载入进行

每天学点GDB(四)

使用GDB进行多线程调试,查看互斥变量. 演示源码如下: #include <pthread.h> #include <stdlib.h> #include <stdio.h> #include <unistd.h> static void* thread_func(void* args); pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER; int sum = 0; #define MAX_NUM 10

每天学点GDB(八)

<一>A really simple tracing debugger 在上一篇文章中讲到了ptrace,那么我们完全可以用ptrace来写一个非常简单的trace工具,用以trace程序的具体运行过程. 用它可以很清楚的回答,使用glibc编译后的hello world是从什么地方开始运行的. (注:本文内容根据"A really simple tracing debugger"翻译而来,具体链接见参考资料一节) itrace.c #include <stdio.h

跟我学SQL:(七)从子表里删除数据

数据 在这篇文章里我要描述一下如何从表格里删除列,要删除的这些列同时还要依赖于其他表格的标准.要解决这个问题就需要一个很聪明而且完全遵守SQL92子查询声明的应用程序.   我必须提醒读者的是,尽管查询可能会遵守SQL的标准,但是众多的数据库生产商会以不同的句法支持实现SQL.以下这个解决方案应该适合于大多数数据库:但是,如果你的结果有出入,就还是应该查看一下文档.同时,由于这个查询要处理DELETE声明,所以你应该在将其应用于真实的生产环境以前在实验数据上进行测试. 需要更多的背景信息? 查看