GDB 单步调试汇编

栏目: 编程语言 · 发布时间: 6年前

内容简介:之前在看汇编的时候一直是肉眼看GCC -S的结果,缺点是很不直观,无法实时的看到寄存器的值,所以研究了下如何用GDB调试汇编。当然,写这篇文章更重要的一个目的是半年没有写博客了,博客要长草了。^_^我调试汇编的需求有几点:下面分享下用GDB实现上面的3点需求:

之前在看汇编的时候一直是肉眼看GCC -S的结果,缺点是很不直观,无法实时的看到寄存器的值,所以研究了下如何用GDB调试汇编。当然,写这篇文章更重要的一个目的是半年没有写博客了,博客要长草了。^_^

我调试汇编的需求有几点:

  • 能够单步进行汇编调试。
  • 能够实时看到寄存器值的变化。
  • 能够看到源代码和对应汇编的关系。

下面分享下用GDB实现上面的3点需求:

单步进行汇编调试

使用si和ni。与s与n的区别在于:s与n是 C语言 级别的单步调试,si与ni是汇编级别的单步调试。

能够实时看到寄存器值的变化。

使用gdb时增加-tui选项,打开gdb后运行 layout regs 命令。注意最好加上-tui,否则很大可能会出现花屏现象。

GDB 单步调试汇编

能够看到源代码和对应汇编的关系

在gdb中运行 set disassemble-next-line on ,表示自动反汇编后面要执行的代码。

GDB 单步调试汇编

可以清晰的看出 int c=sum(x,y); 与下面红框内的汇编指令成对应关系。

如果大家不想用这么原始的方式,可以给GDB安装插件或者使用emacs达到上面的目的,推荐两篇文章:

最后以一个小例子结束:

int sum(int x,int y){
        return x+y;
}

int main(){
        int x=10;
        int y=20;
        int c=sum(x,y);

        return 0;
}

gcc版本4.4.7,默认的优化选项。

我们单步调试下这段代码对应的汇编:

设置断点

注意如果想要把断点设置在汇编指令层次函数的开头,应该使用 b *fun 而不是 b func ,这里我们把断点设置在 b *main

分配栈帧

0x0000000000400489 <main+0>:	 55	push   %rbp
0x000000000040048a <main+1>:	 48 89 e5	mov    %rsp,%rbp
0x000000000040048d <main+4>:	 48 83 ec 10	sub    $0x10,%rsp

%rbp和%rsp表示的是当前栈帧的栈底和栈顶。其中%rbp是被调用者需要保存的寄存器。 sub $0x10,%rsp 表示为main函数分配栈帧空间。

注意这里分配了16字节的栈空间,会有4字节用不上,我个人猜测跟gcc汇编产生的 cfi_def_cfa_offset 16 有关,这个没有深究。

int x=10

0x0000000000400491 <main+8>:	 c7 45 f4 0a 00	00 00	movl   $0xa,-0xc(%rbp)

将x的值放到栈中

int y=20

0x0000000000400498 <main+15>:         c7 45 f8 14 00	00 00	movl   $0x14,-0x8(%rbp)

将y的值放到栈中

sum函数调用

0x000000000040049f <main+22>:         8b 55 f8	mov    -0x8(%rbp),%edx
 0x00000000004004a2 <main+25>:         8b 45 f4	mov    -0xc(%rbp),%eax
 0x00000000004004a5 <main+28>:         89 d6	mov    %edx,%esi
 0x00000000004004a7 <main+30>:         89 c7	mov    %eax,%edi
 0x00000000004004a9 <main+32>:         e8 c6 ff ff ff	callq  0x400474	<sum>

将x与y分别赋值到%esi和%edi中,其中%edi和%esi被规定用来传递函数的第一个和第二个参数。(一个疑问是为什么不能直接 mov -0x8(%rbp),%esi 呢?)

callq会将下一条指令的地址压入栈中,并跳到sum函数的第一条指令。

进入sum函数

0x0000000000400474 <sum+0>:	 55	push   %rbp
0x0000000000400475 <sum+1>:	 48 89 e5	mov    %rsp,%rbp
0x0000000000400478 <sum+4>:	 89 7d fc	mov    %edi,-0x4(%rbp)
0x000000000040047b <sum+7>:	 89 75 f8	mov    %esi,-0x8(%rbp)

同main函数一样,首先将%rbp保存,然后从%edi和%esi中取出函数参数。

求和

0x000000000040047e <sum+10>:	 8b 45 f8	mov    -0x8(%rbp),%eax
0x0000000000400481 <sum+13>:	 8b 55 fc	mov    -0x4(%rbp),%edx
0x0000000000400484 <sum+16>:	 8d 04 02	lea    (%rdx,%rax,1),%eax

将x和y相加,这里用到的是lea指令,关于lea指令介绍参考 LEA instruction? ,这里不赘述了。

将返回值放到%eax中,%rax寄存器规定存放函数的返回值。像 GO 语言如果函数可以有多个返回值的话,返回值是放到栈中。

sum函数收尾

0x0000000000400487 <sum+19>:	 c9	leaveq
0x0000000000400488 <sum+20>:	 c3	retq

我们先看下现在的栈:

GDB 单步调试汇编

(这里不知道为什么没有sub xx,$rsp,我猜测是gcc发现这个最后一次函数调用,之后不会有栈的增长只会有栈的回退,所以用%rsp和%rbp的结果是一样的。简单验证了下,应该是这样)。

在函数结束时首先需要回收当前函数的栈帧、恢复保存过的寄存器、恢复%rip的值,即返回地址。

leaveq指令相当于:

mov  %rbp,%rsp     
pop %rbp

作用是释放(deallocate)当前函数的栈帧并恢复被保存的寄存器的值。由此我们也可以看出%rbp的作用:记住%rsp应该回退的位置,否则函数结束时%rsp不知道该回退到哪。

req指令相当于:

pop %rip

将上面保存过的callq的下一条指令地址恢复到%rip中。

接收函数返回值

0x00000000004004ae <main+37>:         89 45 fc	mov    %eax,-0x4(%rbp)

将%eax的值放入到main函数的栈帧中。

return 0

0x00000000004004b1 <main+40>:         b8 00 00 00 00	mov    $0x0,%eax

同上面sum函数一样。

main函数收尾

0x00000000004004b6 <main+45>:         c9	leaveq
0x00000000004004b7 <main+46>:         c3	retq

如果上面%rsp和%rbp指向同一内存区域看起来不太直观的话,看下现在main函数即将结束时的栈空间:

GDB 单步调试汇编

同上面sum函数的解释一样,不再赘述。

程序运行成功退出。


以上所述就是小编给大家介绍的《GDB 单步调试汇编》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

创业小败局

创业小败局

创业家、i黑马 / 时代华文书局 / 2014-8-1 / 42.00元

让别人的失败,成为你的成功之母! 《创业小败局》由徐小平、何伯权等六位经验丰富的业界大佬,从《创业家》五年来跟踪的数千个创业案例中,精心挑选而来。21个最具代表性的失败案例,每个案例都代表了一种最常见的失败规律,也基本上覆盖了当下中国创业浪潮中,最容易遭遇的创业陷阱。失 败是有规律的。有时候创业者的选择和 行为,必然会导致失败,但当事人却因为缺乏经验而没有察觉。比如在错误心态下引入错误的合伙......一起来看看 《创业小败局》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具