本文同时发表在 https://github.com/zhangyachen/zhangyachen.github.io/issues/134

之前在看汇编的时候一直是肉眼看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 单步调试汇编的更多相关文章

  1. LINUX上使用GDB单步调试Chromium Android C++代码。

    ###动机###在LINUX使用GDB单步调试Chromium Android C++代码. [1]编译android平台Chromium, 修改GN文件中编译选项:-g -O0 使得编译优化更少,便 ...

  2. 一起talk GDB吧(第二回:GDB单步调试)

    各位看官们,大家好.我们在上一回中说简单地介绍了GDB.这一回中,我们介绍GDB的调试功能:单步 调试. 闲话休提,言归正转. 让我们一起talk GDB吧! 看官们,我们先说一下什么是单步调试.大家 ...

  3. GDB单步调试程序

    linux下gdb单步调试 用 GDB 调试程序 GDB 概述———— GDB 是 GNU开源组织发布的一个强大的 UNIX下的程序调试工具.或许,各位比较喜欢那种图形界面方式的,像 VC. BCB等 ...

  4. GDB调试汇编分析

    GDB调试汇编分析 代码 本次实践我参照了许多先做了的同学的博客,有卢肖明,高其,张梓靖同学.代码借用的是卢肖明同学的代码进行调试运行. GCC编译 使用gcc -g gdbtest.c -o gdb ...

  5. 20145233 GDB调试汇编分析

    GDB调试汇编分析 代码 #include<stdio.h> short addend1 = 1; static int addend2 = 2; const static long ad ...

  6. 20145318 GDB调试汇编堆栈分析

    20145318 GDB调试汇编堆栈分析 代码 #include<stdio.h> short addend1 = 1; static int addend2 = 2; const sta ...

  7. 20145311利用gdb调试汇编代码

    利用GDB调试汇编代码 首先编写c语言原代码,我使用的是同学分析过的代码 #include<stdio.h>short addend1 = 1;static int addend2 = 2 ...

  8. 20145219 gdb调试汇编堆栈分析

    20145219 gdb调试汇编堆栈分析 代码gdbdemo.c int g(int x) { return x+19; } int f(int x) { return g(x); } int mai ...

  9. gdb调试汇编堆栈分析

    代码(src/05/gdb.c) int g(int x) { return x + 4; } int f(int x) { return g(x); } int main(void) { retur ...

随机推荐

  1. Spring-aop实现切面的四种方式 (2)

    AOP实现方式一 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http ...

  2. February 6 2017 Week 6 Monday

    There are no shortcuts to any place worth going. 任何值得去的地方,都没有捷径. Several years ago, I climbed the Hu ...

  3. 双十一问题:kafka消费能力低下原因思考

    抛去cpu.内存等机器原因,在每个分区皆分配一个进程消费的情况下,利用扩机器来提高kafka消费速率已无能为力 此时发现,在实际洪峰时段的消费速率元达不到先前压测时的消费速率 原因思考: 1.洪峰时段 ...

  4. c++ auto_ptr超简易版实现

    namespace wang{ template<class T> class shared_ptr{ public: explicit shared_ptr(T *p) : count( ...

  5. (转)浅谈PostgreSQL的索引

    1. 索引的特性 1.1 加快条件的检索的特性 当表数据量越来越大时查询速度会下降,在表的条件字段上使用索引,快速定位到可能满足条件的记录,不需要遍历所有记录. create table t(id i ...

  6. 【ACM】那些年,我们挖(WA)过的最短路

    不定时更新博客,该博客仅仅是一篇关于最短路的题集,题目顺序随机. 算法思想什么的,我就随便说(复)说(制)咯: Dijkstra算法:以起始点为中心向外层层扩展,直到扩展到终点为止.有贪心的意思. 大 ...

  7. thinkphp清除缓存

    前台 //清除缓存 $(function(){ $("#cache").click(function(){ layer.confirm('你确定要清除缓存吗?', {icon: 3 ...

  8. 配置开发环境&安装sklearn

    我的开发环境是Jupyter lab,所用的库和版本大家参考: Python 3.7.1(你的版本至少要3.4以上) Scikit-learn 0.20.0 (你的版本至少要0.19) Graphvi ...

  9. mybatis实现最简单的增删改查

    1.数据库设计 2.项目结构(针对User不用管Blogger) User.java package com.yunqing.mybatis.bean; public class User { pri ...

  10. mysql.sock问题

    Can't connect to local MySQL server through socket '/tmp/mysql.sock' 上述提示可能在启动mysql时遇到,即在/tmp/mysql. ...