惠济郑州网站建设,重庆网站建设与推广,好用的网页制作软件,网站开发网站维护这行业怎么样之前在看汇编的时候一直是肉眼看GCC -S的结果#xff0c;缺点是很不直观#xff0c;无法实时的看到寄存器的值#xff0c;所以研究了下如何用GDB调试汇编。当然#xff0c;写这篇文章更重要的一个目的是半年没有写博客了#xff0c;博客要长草了。^_^调试汇编的需求有几点…之前在看汇编的时候一直是肉眼看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 csum(x,y);与下面红框内的汇编指令成对应关系。如果大家不想用这么原始的方式可以给GDB安装插件或者使用emacs达到上面的目的推荐两篇文章GDB 从裸奔到穿戴整齐GDB实用插件(peda, gef, gdbinit)全解最后以一个小例子结束int sum(int x,int y){return xy;
}int main(){int x10;int y20;int csum(x,y);return 0;
}gcc版本4.4.7默认的优化选项。我们单步调试下这段代码对应的汇编设置断点注意如果想要把断点设置在汇编指令层次函数的开头应该使用b *fun而不是b func这里我们把断点设置在b *main分配栈帧0x0000000000400489 main0: 55 push %rbp
0x000000000040048a main1: 48 89 e5 mov %rsp,%rbp
0x000000000040048d main4: 48 83 ec 10 sub $0x10,%rsp%rbp和%rsp表示的是当前栈帧的栈底和栈顶。其中%rbp是被调用者需要保存的寄存器。sub $0x10,%rsp表示为main函数分配栈帧空间。注意这里分配了16字节的栈空间会有4字节用不上我个人猜测跟gcc汇编产生的cfi_def_cfa_offset 16有关这个没有深究。int x100x0000000000400491 main8: c7 45 f4 0a 00 00 00 movl $0xa,-0xc(%rbp)将x的值放到栈中int y200x0000000000400498 main15: c7 45 f8 14 00 00 00 movl $0x14,-0x8(%rbp)将y的值放到栈中sum函数调用0x000000000040049f main22: 8b 55 f8 mov -0x8(%rbp),%edx0x00000000004004a2 main25: 8b 45 f4 mov -0xc(%rbp),%eax0x00000000004004a5 main28: 89 d6 mov %edx,%esi0x00000000004004a7 main30: 89 c7 mov %eax,%edi0x00000000004004a9 main32: e8 c6 ff ff ff callq 0x400474 sum将x与y分别赋值到%esi和%edi中其中%edi和%esi被规定用来传递函数的第一个和第二个参数。一个疑问是为什么不能直接mov -0x8(%rbp),%esi呢callq会将下一条指令的地址压入栈中并跳到sum函数的第一条指令。进入sum函数0x0000000000400474 sum0: 55 push %rbp
0x0000000000400475 sum1: 48 89 e5 mov %rsp,%rbp
0x0000000000400478 sum4: 89 7d fc mov %edi,-0x4(%rbp)
0x000000000040047b sum7: 89 75 f8 mov %esi,-0x8(%rbp)同main函数一样首先将%rbp保存然后从%edi和%esi中取出函数参数。求和0x000000000040047e sum10: 8b 45 f8 mov -0x8(%rbp),%eax
0x0000000000400481 sum13: 8b 55 fc mov -0x4(%rbp),%edx
0x0000000000400484 sum16: 8d 04 02 lea (%rdx,%rax,1),%eax将x和y相加这里用到的是lea指令关于lea指令介绍参考LEA instruction? 这里不赘述了。将返回值放到%eax中%rax寄存器规定存放函数的返回值。像GO语言如果函数可以有多个返回值的话返回值是放到栈中。sum函数收尾0x0000000000400487 sum19: c9 leaveq
0x0000000000400488 sum20: c3 retq我们先看下现在的栈(这里不知道为什么没有sub xx,$rsp我猜测是gcc发现这个最后一次函数调用之后不会有栈的增长只会有栈的回退所以用%rsp和%rbp的结果是一样的。简单验证了下应该是这样)。在函数结束时首先需要回收当前函数的栈帧、恢复保存过的寄存器、恢复%rip的值即返回地址。leaveq指令相当于mov %rbp,%rsp
pop %rbp作用是释放(deallocate)当前函数的栈帧并恢复被保存的寄存器的值。由此我们也可以看出%rbp的作用记住%rsp应该回退的位置否则函数结束时%rsp不知道该回退到哪。retq指令相当于pop %rip将上面保存过的callq的下一条指令地址恢复到%rip中。接收函数返回值0x00000000004004ae main37: 89 45 fc mov %eax,-0x4(%rbp)将%eax的值放入到main函数的栈帧中。return 00x00000000004004b1 main40: b8 00 00 00 00 mov $0x0,%eax同上面sum函数一样。main函数收尾0x00000000004004b6 main45: c9 leaveq
0x00000000004004b7 main46: c3 retq如果上面%rsp和%rbp指向同一内存区域看起来不太直观的话看下现在main函数即将结束时的栈空间同上面sum函数的解释一样不再赘述。程序运行成功退出。