内容简介:之前在看汇编的时候一直是肉眼看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中运行 set disassemble-next-line on
,表示自动反汇编后面要执行的代码。
可以清晰的看出 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
我们先看下现在的栈:
(这里不知道为什么没有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函数即将结束时的栈空间:
同上面sum函数的解释一样,不再赘述。
程序运行成功退出。
以上所述就是小编给大家介绍的《GDB 单步调试汇编》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 调试V8中JS对应的汇编代码
- iOS高级调试&逆向技术-汇编寄存器调用约定教程
- iOS汇编入门教程(一)ARM64汇编基础
- iOS 汇编入门教程(一):ARM64 汇编基础
- iOS汇编入门教程(三)汇编中的 Section 与数据存取
- iOS汇编入门教程(二)在Xcode工程中嵌入汇编代码
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Algorithms on Strings, Trees and Sequences
Dan Gusfield / Cambridge University Press / 1997-5-28 / USD 99.99
String algorithms are a traditional area of study in computer science. In recent years their importance has grown dramatically with the huge increase of electronically stored text and of molecular seq......一起来看看 《Algorithms on Strings, Trees and Sequences》 这本书的介绍吧!