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) ;把a置0。ebp-4的位置是第一个局部变量
8048361: 8b 45 08 mov 0x8(%ebp),%eax ;把参数x保存到eax。ebp+8的位置是最后一个入栈的参数,也就是第一个参数
8048364: 89 45 fc mov %eax,0xfffffffc(%ebp) ;把eax赋值给变量a
8048367: 8b 45 0c mov 0xc(%ebp),%eax ;把参数y保存到eax。ebp+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=0x12。ebp-16是局部变量x
804838a: c7 45 f4 34 00 00 00 movl $0x34,0xfffffff4(%ebp) ;y=0x34。ebp-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) ;把保存在eax的add的返回值,赋值给位于ebp-8的第三个局部变量result。注意这条指令的地址,就是add的返回地址
80483a6: b8 00 00 00 00 mov $0x0,%eax ;0作为main的返回值,保存到eax
80483ab: 83 c4 18 add $0x18,%esp ;恢复栈指针,也就是讨论stdcall和cdecl的时候总要提到的“调用者清栈”
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入口的四条指令8048372、8048376、8048379、804837f,还有与之对应的main返回之前的指令。
貌似main对esp要求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
可以看到,esp和ebp都已经恢复到了调用add之前的值。
但是,调用add的两个参数还在栈里(0xbf8df8ac、0xbf8df8b0,都在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 ;把eax置0作为main的返回值
15 }
(gdb) stepi // 注释: add $0x18,%esp ; 调用者清栈
0x080483ae 15 }
(gdb) continue
Continuing.
Program exited normally.
(gdb) quit
[test]$
GDB查看堆栈局部变量的更多相关文章
- Linux 如何使用gdb 查看core堆栈信息
转载:http://blog.csdn.net/mergerly/article/details/41994207 core dump 一般是在segmentation fault(段错误)的情况下产 ...
- gdb查看线程堆栈信息
查看堆栈:gdb -quiet -batch -ex='thread apply all bt' -p pid查看运行位置:gdb -quiet -batch -ex='thread apply al ...
- Linux程序宕掉后如何通过gdb查看出错信息
我们在编写服务端程序的时候,由于多线程并且环境复杂,程序可能在不确定条件的情况下宕掉,还不好重新,这是我们如何获取程序的出错信息,一种方法通过打日志,有时候一些错误日志也不能体现出来,这时就用到我们的 ...
- x/nfu-用gdb查看内存
用gdb查看内存 2007-12-08 12:43 用gdb查看内存 格式: x /nfu <addr> 说明x 是 examine 的缩写 n表示要显示的内存单元的个数 f表示显示方式, ...
- GDB查看内存(x 命令)
gdb查看内存命令 首先使用gdb [YourFileName].c进入gdb界面 使用examine命令,字母缩写为x查看内存地址的值.x命令语法 x/[number][format] <ad ...
- 一起talk GDB吧(第五回:GDB查看信息)
各位看官们.大家好,上一回中我们说的是GDB的调用栈调试功能,而且说了怎样使用GDB进行查看调用 栈.这一回中,我们继续介绍GDB的调试功能:查看信息.当然了.我们也会介绍怎样使用GDB查看程序 执行 ...
- GDB查看内存命令(x命令) 用gdb查看指定地址的内存内容
GDB查看内存命令(x命令) - super119 - 博客园 https://www.cnblogs.com/super119/archive/2011/11/18/2254382.html 可以使 ...
- 编程工具系列之一------使用GDB的堆栈跟踪功能
在调试程序的过程中,查看程序的函数调用堆栈是一项最基本的任务,几乎所有的图形调试器都支持这项特性. GDB调试器当然也支持这一特性,但是功能更加灵活和丰富. GDB将当前函数的栈帧编号为0,为外层函数 ...
- 【Linux】GDB查看栈信息(转)
在调试程序的过程中,查看程序的函数调用堆栈是一项最基本的任务,几乎所有的图形调试器都支持这项特性. GDB调试器当然也支持这一特性,但是功能更加灵活和丰富. GDB将当前函数的栈帧编号为0,为外层函数 ...
随机推荐
- Super expression must either be null or a function, not undefined
按照之前买的用JavaScript开发移动应用的例子来编写的,然后报了这个错.我的头部声明是这样的 var React = require('react-native'); var { Text, V ...
- 解决ubuntu下IntelliJ IDEA无法锁定到启动器的问题
参考:https://www.zhihu.com/question/31626161 在目录~/.local/share/applications 中找到以前的IDEA的.desktop文件,删掉即可
- .net core 2.0 虚拟目录下载 Android Apk 等文件
当Android 文件 Apk 放在Asp.net core wwwroot 虚拟目录下面.访问是 404,设置Content-Type类型 app.UseStaticFiles(); //设置实际目 ...
- jd-gui在Ubuntu上打不开
你在 ubuntu13.10上 安装了最新版本的 jd-gui 但是它跑不起来怎么办? 请执行如下指令: sudo apt-get install libgtk2.0-0:i386 libxxf86v ...
- hdoj:2050
#include <iostream> using namespace std; int main() { int T; while (cin >> T) { ; i < ...
- Maven私服(Repository Manager) - Nexus安装和使用(详细过程)
Maven私服的安装和使用. (注:原创文章,引用请注明来自Clement-Xu的博客!) Maven私服(即Repository Manager)的主要作用: 减少从远方仓库下载的次数,节省带宽.提 ...
- Python连接Mssql
此篇使用的是Python3.6 下载pymssql包 打开网址http://www.lfd.uci.edu/~gohlke/pythonlibs/ 用pip安装whl文件.在cmd中输入 pip in ...
- 关于pythoh面向过程开发人员三步转面向对象的补充,再加一步,四步走战略。转面向对象也可以有固定公式。
前言: oop非常非常非常重要.搞不懂oop,就玩不了python,就算能写也一定是写代码时候喜欢靠猜瞎猫碰死老鼠写得心很虚.为什么这么说呢,我也是从面向过程编程到死走过来的,一路def到死,一看到有 ...
- python目录遍历文件名称替换
# -*- coding:utf-8 -*- import os import os.path import shutil import chardet import codecs mysql_fil ...
- HOOK IDT频繁蓝屏(Window 正确 HOOK IDT)
环境 win7x64 Microsoft Windows [版本 6.1.7601]也是一个朋友 HOOK IDT 测试 问我IDT为啥老是蓝屏.结果是因为swapgs问题. 如果你知道swapgs作 ...