zoukankan      html  css  js  c++  java
  • GDB查看堆栈局部变量

    GDB查看堆栈局部变量

    参数从右到左入栈局部变量在栈上分配空间,听的耳朵都起茧子了。最近做项目涉及C和汇编互相调用,写代码的时候才发现没真正弄明白。自己写了个最简单的函数,用gdb跟踪了调用过程,才多少懂了一点。

    参考资料:

    http://blog.csdn.net/liigo/archive/2006/12/23/1456938.aspx

    http://blog.csdn.net/eno_rez/archive/2008/03/08/2158682.aspx

    int add(int x, int y)

    {

        int a = 0;

        a = x;

        a += y;

        return a;

    }

    int main(int argc, char *argv[])

    {

        int x, y, result;

        x = 0x12;

        y = 0x34;

        result = add(x, y);

        return 0;

    }

    编译:(Fedora6, gcc 4.1.2)

    [test]$ gcc -g -Wall -o stack stack.c

    反汇编:

    这里的汇编的格式是AT&T汇编,它的格式和我们熟悉的汇编格式不太一样,尤其要注意源操作数和目的操作数的顺序是反过来的

    [test]$ objdump -d stack > stack.dump

    [test]$ cat stack.dump

    ......

    08048354 :

     8048354:       55                      push   %ebp  ;保存调用者的帧指针

     8048355:       89 e5                   mov    %esp,%ebp  ;把当前的栈指针作为本函数的帧指针

     8048357:       83 ec 10                sub    $0x10,%esp  ;调整栈指针,为局部变量保留空间

     804835a:       c7 45 fc 00 00 00 00    movl   $0x0,0xfffffffc(%ebp)  ;a0ebp-4的位置是第一个局部变量

     8048361:       8b 45 08                mov    0x8(%ebp),%eax  ;把参数x保存到eaxebp+8的位置是最后一个入栈的参数,也就是第一个参数

     8048364:       89 45 fc                mov    %eax,0xfffffffc(%ebp)  ;eax赋值给变量a

     8048367:       8b 45 0c                mov    0xc(%ebp),%eax  ;把参数y保存到eaxebp+C的位置是倒数第二个入栈的参数,也就是第二个参数

     804836a:       01 45 fc                add    %eax,0xfffffffc(%ebp)  ;a+=y

     804836d:       8b 45 fc                mov    0xfffffffc(%ebp),%eax  ;a的值作为返回值,保存到eax

     8048370:       c9                      leave 

     8048371:       c3                      ret   

    08048372 :

     8048372:       8d 4c 24 04             lea    0x4(%esp),%ecx  ;????

     8048376:       83 e4 f0                and    $0xfffffff0,%esp  ;把栈指针16字节对齐

     8048379:       ff 71 fc                pushl  0xfffffffc(%ecx)  ;????

     804837c:       55                      push   %ebp  ;保存调用者的帧指针

     804837d:       89 e5                   mov    %esp,%ebp  ;把当前的栈指针作为本函数的帧指针

     804837f:       51                      push   %ecx  ;????

     8048380:       83 ec 18                sub    $0x18,%esp  ;调整栈指针,为局部变量保留空间

     8048383:       c7 45 f0 12 00 00 00    movl   $0x12,0xfffffff0(%ebp)  ;x=0x12ebp-16是局部变量x

     804838a:       c7 45 f4 34 00 00 00    movl   $0x34,0xfffffff4(%ebp)  ;y=0x34ebp-12是局部变量y

     8048391:       8b 45 f4                mov    0xfffffff4(%ebp),%eax  ;y保存到eax

     8048394:       89 44 24 04             mov    %eax,0x4(%esp)  ;y作为最右边的参数首先入栈

     8048398:       8b 45 f0                mov    0xfffffff0(%ebp),%eax  ;x保存到eax

     804839b:       89 04 24                mov    %eax,(%esp)  ;x第二个入栈

     804839e:       e8 b1 ff ff ff          call   8048354   ;调用add

     80483a3:       89 45 f8                mov    %eax,0xfffffff8(%ebp)  ;把保存在eaxadd的返回值,赋值给位于ebp-8的第三个局部变量result。注意这条指令的地址,就是add的返回地址

     80483a6:       b8 00 00 00 00          mov    $0x0,%eax  ;0作为main的返回值,保存到eax

     80483ab:       83 c4 18                add    $0x18,%esp  ;恢复栈指针,也就是讨论stdcallcdecl的时候总要提到的调用者清栈

     80483ae:       59                      pop    %ecx  ;

     80483af:       5d                      pop    %ebp  ;

     80483b0:       8d 61 fc                lea    0xfffffffc(%ecx),%esp  ;

     80483b3:       c3                      ret   

     80483b4:       90                      nop   

    ......

    有一点值得注意的是main在调用add之前把参数压栈的过程。

    它用的不是push指令,而是另一种方法。

    main入口调整栈指针的时候,也就是位于8048380的这条指令 sub $0x18,%esp

    不但象通常函数都要做的那样给局部变量预留了空间,还顺便把调用add的两个参数的空间也预留出来了。

    然后把参数压栈的时候,用的是mov指令。

    我不太明白这种方法有什么好处。

    另外一个不明白的就是main入口的四条指令804837280483768048379804837f,还有与之对应的main返回之前的指令。

    貌似mainesp要求16字节对齐,所以先把原来的esp压栈,然后强行把esp的低4位清0。等到返回之前再从栈里恢复原来的esp

     

    准备工作都做好了,现在开始gdb

    gdb不太熟悉的同学要注意一点,stepi命令执行之后显示出来的源代码行或者指令地址,都是即将执行的指令,而不是刚刚执行完的指令。

    我在每个stepi后面都加了注释,就是刚执行过的指令。

    [test]$ gdb -q stack

    (gdb) break main

    Breakpoint 1 at 0x8048383: file stack.c, line 11.

    gdb并没有把断点设置在main的第一条指令,而是设置在了调整栈指针为局部变量保留空间之后

    (gdb) run

    Starting program: /home/brookmill/test/stack

    Breakpoint 1, main () at stack.c:11

    11              x = 0x12;

    (gdb) stepi    // 注释: movl   $0x12,0xfffffff0(%ebp)

    12              y = 0x34;

    (gdb) stepi    // 注释: movl   $0x34,0xfffffff4(%ebp)

    13              result = add(x, y);

    (gdb) info registers esp

    esp            0xbf8df8ac       0xbf8df8ac

    (gdb) info registers ebp

    ebp            0xbf8df8c8       0xbf8df8c8

    (gdb) x/12 0xbf8df8a0

    0xbf8df8a0:     0x002daff4      0x002d9220      0xbf8df8d8      0x080483e9

    0xbf8df8b0:     0x001ca8d5      0xbf8df96c      0x00000012      0x00000034

    0xbf8df8c0:     0x001903d0      0xbf8df8e0      0xbf8df938      0x001b4dec

    这就是传说中的栈。在main准备调用add之前,先看看这里有些什么东东

    0xbf8df8c8(ebp)保存的是上一层函数的帧指针:0xbf8df938,距离这里有112字节

    0xbf8df8cc(ebp+4)保存的是main的返回地址0x001b4dec

    0xbf8df8b8(ebp-16)是局部变量x,已经赋值0x12;

    0xbf8df8bc(ebp-12)是局部变量y,已经赋值0x34;

    0xbf8df8c0(ebp-8)是局部变量result。值得注意的是,因为我们没有给result赋值,这里是一个不确定的值。局部变量如果不显式的初始化,初始值不一定是0

    现在开始调用add

    (gdb) stepi    // 注释: mov    0xfffffff4(%ebp),%eax

    0x08048394      13              result = add(x, y);

    (gdb) stepi    // 注释: mov    %eax,0x4(%esp)

    0x08048398      13              result = add(x, y);

    (gdb) x/12 0xbf8df8a0

    0xbf8df8a0:     0x002daff4      0x002d9220      0xbf8df8d8      0x080483e9

    0xbf8df8b0:     0x00000034      0xbf8df96c      0x00000012      0x00000034

    0xbf8df8c0:     0x001903d0      0xbf8df8e0      0xbf8df938      0x001b4dec

    y首先被压栈,在0xbf8df8b0

    (gdb) stepi    // 注释: mov    0xfffffff0(%ebp),%eax

    0x0804839b      13              result = add(x, y);

    (gdb) stepi    // 注释: mov    %eax,(%esp)

    0x0804839e      13              result = add(x, y);

    (gdb) x/12 0xbf8df8a0

    0xbf8df8a0:     0x002daff4      0x002d9220      0xbf8df8d8      0x00000012

    0xbf8df8b0:     0x00000034      0xbf8df96c      0x00000012      0x00000034

    0xbf8df8c0:     0x001903d0      0xbf8df8e0      0xbf8df938      0x001b4dec

    x第二个进栈,在0xbf8df8ac

     

    (gdb) stepi    // 注释: call   8048354

    add (x=18, y=52) at stack.c:2

    2       {

     

    刚刚执行了call指令,现在我们进入了add函数

    (gdb) info registers esp

    esp            0xbf8df8a8       0xbf8df8a8

    (gdb) info registers ebp

    ebp            0xbf8df8c8       0xbf8df8c8

    (gdb) x/12 0xbf8df8a0

    0xbf8df8a0:     0x002daff4      0x002d9220      0x080483a3      0x00000012

    0xbf8df8b0:     0x00000034      0xbf8df96c      0x00000012      0x00000034

    0xbf8df8c0:     0x001903d0      0xbf8df8e0      0xbf8df938      0x001b4dec

    现在esp指向0xbf8df8a8,这里保存的是add函数的返回地址,它是由call指令压栈的。

    (gdb) stepi    // 注释: push   %ebp

    0x08048355      2       {

    (gdb) stepi    // 注释: mov    %esp,%ebp

    0x08048357      2       {

    (gdb) stepi    // 注释: sub    $0x10,%esp

    3               int a = 0;

    (gdb) info registers esp

    esp            0xbf8df894       0xbf8df894

    (gdb) info registers ebp

    ebp            0xbf8df8a4       0xbf8df8a4

    (gdb) x/16 0xbf8df890

    0xbf8df890:     0x00000000      0x08049574      0xbf8df8a8      0x08048245

    0xbf8df8a0:     0x002daff4      0xbf8df8c8      0x080483a3      0x00000012

    0xbf8df8b0:     0x00000034      0xbf8df96c      0x00000012      0x00000034

    0xbf8df8c0:     0x001903d0      0xbf8df8e0      0xbf8df938      0x001b4dec

    刚刚执行完的3条指令是函数入口的定式。

    现在我们可以看到,main的栈还是原样,向下增长之后就是add的栈。

    0xbf8df8a4(ebp)保存的是上层函数main的帧指针

    0xbf8df8a8(ebp+4)保存的是返回地址

    0xbf8df8ac(ebp+8)保存的是最后一个入栈的参数x

    0xbf8df8b0(ebp+C)保存的是倒数第二个入栈的参数y

    0xbf8df8a0(ebp-4)保存的是局部变量a,现在是一个不确定值

    接下来add函数就真正开始干活了

    (gdb) stepi    // 注释: movl   $0x0,0xfffffffc(%ebp)

    4               a = x;

    (gdb) x/16 0xbf8df890

    0xbf8df890:     0x00000000      0x08049574      0xbf8df8a8      0x08048245

    0xbf8df8a0:     0x00000000      0xbf8df8c8      0x080483a3      0x00000012

    0xbf8df8b0:     0x00000034      0xbf8df96c      0x00000012      0x00000034

    0xbf8df8c0:     0x001903d0      0xbf8df8e0      0xbf8df938      0x001b4dec

    可以看到a被置0

    (gdb) stepi    // 注释: mov    0x8(%ebp),%eax

    0x08048364      4               a = x;

    (gdb) stepi    // 注释: mov    %eax,0xfffffffc(%ebp)

    5               a += y;

    (gdb) x/16 0xbf8df890

    0xbf8df890:     0x00000000      0x08049574      0xbf8df8a8      0x08048245

    0xbf8df8a0:     0x00000012      0xbf8df8c8      0x080483a3      0x00000012

    0xbf8df8b0:     0x00000034      0xbf8df96c      0x00000012      0x00000034

    0xbf8df8c0:     0x001903d0      0xbf8df8e0      0xbf8df938      0x001b4dec

    参数x(ebp+8)的值通过eax赋值给了局部变量a(ebp-4)

    (gdb) stepi    // 注释: mov    0xc(%ebp),%eax

    0x0804836a      5               a += y;

    (gdb) stepi    // 注释: add    %eax,0xfffffffc(%ebp)

    6               return a;

    (gdb) x/16 0xbf8df890

    0xbf8df890:     0x00000000      0x08049574      0xbf8df8a8      0x08048245

    0xbf8df8a0:     0x00000046      0xbf8df8c8      0x080483a3      0x00000012

    0xbf8df8b0:     0x00000034      0xbf8df96c      0x00000012      0x00000034

    0xbf8df8c0:     0x001903d0      0xbf8df8e0      0xbf8df938      0x001b4dec

    参数y(ebp+C)的值通过eax加到了局部变量a(ebp-4)

    现在要从add返回了。返回之前把局部变量a(ebp-4)保存到eax用作返回值

    (gdb) stepi    // 注释: mov    0xfffffffc(%ebp),%eax

    7       }

    (gdb) stepi    // 注释: leave

    0x08048371 in add (x=1686688, y=134513616) at stack.c:7

    7       }

    (gdb) stepi    // 注释: ret

    0x080483a3 in main () at stack.c:13

    13              result = add(x, y);

    现在我们回到了main,栈现在是这样的

    (gdb) info registers esp

    esp            0xbf8df8ac       0xbf8df8ac

    (gdb) info registers ebp

    ebp            0xbf8df8c8       0xbf8df8c8

    (gdb) x/16 0xbf8df890

    0xbf8df890:     0x00000000      0x08049574      0xbf8df8a8      0x08048245

    0xbf8df8a0:     0x00000046      0xbf8df8c8      0x080483a3      0x00000012

    0xbf8df8b0:     0x00000034      0xbf8df96c      0x00000012      0x00000034

    0xbf8df8c0:     0x001903d0      0xbf8df8e0      0xbf8df938      0x001b4dec

    可以看到,espebp都已经恢复到了调用add之前的值。

    但是,调用add的两个参数还在栈里(0xbf8df8ac0xbf8df8b0,都在esp以上)

    也就是说,被调用的函数add没有把它们从栈上清出去,需要调用方main来清理。这就是著名的调用者清栈cdecl调用方式的特点之一。

    (gdb) stepi    // 注释: mov    %eax,0xfffffff8(%ebp)

    14              return 0;

    (gdb) x/16 0xbf8df890

    0xbf8df890:     0x00000000      0x08049574      0xbf8df8a8      0x08048245

    0xbf8df8a0:     0x00000046      0xbf8df8c8      0x080483a3      0x00000012

    0xbf8df8b0:     0x00000034      0xbf8df96c      0x00000012      0x00000034

    0xbf8df8c0:     0x00000046      0xbf8df8e0      0xbf8df938      0x001b4dec

    eax得到函数add的返回值,赋值给了局部变量result(ebp-8)

    (gdb) stepi    // 注释: mov    $0x0,%eax ;eax0作为main的返回值

    15      }

    (gdb) stepi    // 注释: add    $0x18,%esp ; 调用者清栈

    0x080483ae      15      }

    (gdb) continue

    Continuing.

    Program exited normally.

    (gdb) quit

    [test]$

  • 相关阅读:
    仿windows选项卡效果拾零(收藏)
    把一个字符串分开存入一个临时表中
    DOM的基本方法
    如何判断iframe加载完毕(原创)
    javascript中showModalDialog和showModelessDialog的使用(转)
    一个sql子查询作为过滤条件的例子(原创)
    关闭窗口,弹出对话框
    设置C#程序在Windows 7 Vista下以管理员权限运行(转)
    SQL SERVER 6 视图与索引
    SQL SERVER 各类触发器的完整语法及参数说明(拓展)
  • 原文地址:https://www.cnblogs.com/welhzh/p/10335722.html
Copyright © 2011-2022 走看看