分析一个简单的汇编代码

部分常见的寄存器

寄存器 16位 32位 64位
累加寄存器 AX EAX RAX
基址寄存器 BX EBX RBX
计数寄存器 CX ECX RCX
数据寄存器 DX EDX RDX
堆栈基指针 BP EBP RBP
变址寄存器 SI ESI RSI
堆栈顶指针 SP ESP RSP
指令寄存器 IP EIP RIP

一个x86-64的CPU,包含一组16个存储64位值的「通用目的寄存器」。
这些寄存器用来存储「整数数据」和「指针」。

  • 最初的8086中,有8个16位寄存器,即「ax」到「sp」。
  • 扩展到IA32架构时,这些寄存器也扩展到32位,也即「eax」到「esp」。
  • 扩展到x86-64位后,原来的8个寄存器扩展成64位,即「rax」到「rsp」,然后新增了8个寄存器「r8」到「r15」。

8086:第一代单芯片、16位微处理器之一。
IA32:Intel 32位体系结构(Intel Architecture 32-bit)
Intel64:IA32的64位扩展,也称x86-64

环境信息

gcc -v
使用内建 specs。
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/lto-wrapper
目标:x86_64-redhat-linux
配置为:../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-languages=c,c++,objc,obj-c++,java,fortran,ada,go,lto --enable-plugin --enable-initfini-array --disable-libgcj --with-isl=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/isl-install --with-cloog=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/cloog-install --enable-gnu-indirect-function --with-tune=generic --with-arch_32=x86-64 --build=x86_64-redhat-linux
线程模型:posix
gcc 版本 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC)

C语言代码

int add_a_and_b(int a, int b) {return a + b;
}int main() {return add_a_and_b(8, 5);
}

汇编代码

执行gcc -S -fno-asynchronous-unwind-tables test_asm.c就可以得到汇编代码。
使用-fno-asynchronous-unwind-tables选项,是为了禁用cfi指令。

关于CFI指令的用处,有一个解释:On some architectures, exception handling must be managed with Call Frame Information directives. These directives are used in the assembly to direct exception handling. These directives are available on Linux on POWER, if, for any reason (portability of the code base, for example), the GCC generated exception handling information is not sufficient.
下述是ATT格式的汇编代码。ATT格式也是GCC、OBJDUMP等工具的默认格式。Microsoft的工具和Intel的文档,汇编代码都是Intel格式的。这两种格式不太相同,比如:movq(ATT格式)、mov(Intel格式)。GCC也可以产生Intel格式的汇编代码,只需要带上参数-masm=intel。

 .file   "test_asm.c".text.globl   add_a_and_b.type    add_a_and_b, @function
add_a_and_b:pushq   %rbp            ; (6)movq   %rsp, %rbp      ; (7)movl   %edi, -4(%rbp)  ; (8)movl   %esi, -8(%rbp)  ; (9)movl   -8(%rbp), %eax  ; (10)movl  -4(%rbp), %edx  ; (11)addl  %edx, %eax      ; (12)popq  %rbp            ; (13)ret                       ; (14).size add_a_and_b, .-add_a_and_b.globl    main.type   main, @function
main:pushq  %rbp            ; (1)movq   %rsp, %rbp      ; (2)movl   $5, %esi        ; (3)movl   $8, %edi        ; (4)call   add_a_and_b     ; (5)popq   %rbp            ; (15)ret                       ; (16).size main, .-main.ident  "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-39)".section    .note.GNU-stack,"",@progbits

(1) pushq %rbp

rbp寄存器,是ebp寄存器64位扩展。意思是扩展栈指针寄存器,存储栈中最高位数据的内存地址。
rbp寄存器的值,在(1)入栈,在(15)出栈。

这主要是为了把函数中用到的rbp寄存器的内容,恢复到函数调用前的状态。
在进入函数之前,我们无法确定rbp寄存器的值是什么,但是由于函数内部也会使用rbp寄存器,所以就需要暂时把rbp寄存器的值先存到栈里面,函数处理完成之后,再从栈中将值恢复到rbp寄存器。

在函数的入口处,将rbp的值入栈保存,在函数的出口处出栈,这是C语言编译器的规定。
这样做是为了确保函数在调用前后,rbp寄存器的值不会改变。

push和pop指令只有一个操作数,我们不需要指定将值push到哪里,以及将哪里的值pop到寄存器。
是因为,对栈进行读写的内存地址,是由rsp栈指针寄存器管理的。

push入栈和pop出栈指令执行之后,rsp寄存器存储的栈指针的值会自动更新。
因为栈是从高地址位向低地址位生长。
push指令是增加栈元素的操作,所以执行push后,rsp寄存器的值会-4(64位机器就是-8)。
pop指令是减少栈元素的操作,所以执行pop后,rsp寄存器的值会+4(64位机器就是+8)。

我们可以认为,push和pop指令,就是用来在寄存器和栈(主存)之间进行操作的。
push指令就是将寄存器的值,保存到主存中。
pop指令就是将主存中保存的值恢复到寄存器里。

(2) movq %rsp, %rbp

mov指令有这几种:movb(8位)、movw(16位)、movl(32位)、movq(64位)
mov指令的基本格式是:movx source, destination
所以上面(2)的含义是,将rsp寄存器的值,传递到rbp中,这样就形成了main函数的栈帧。

系统开始执行main函数时,会为它在内存里面建立一个帧(frame),所有main的内部变量(比如a和b)都保存在这个帧里面。
main函数执行结束后,该帧就会被回收,释放所有的内部变量,不再占用空间。

(3) movl $5, %esi

将数字5,传递到esi寄存器。

(4) movl $8, %edi

将数字8,传递到edi寄存器。

(5) call add_a_and_b

调用add_a_and_b函数。

在将函数的入口地址,设定到程序计数器之前,
call指令会把调用函数结束后,要执行的那一条指令的地址,存储在栈中(也就是主内存中)。

函数执行完毕后,执行ret指令,就会把刚刚说的保存到栈中的地址,设定到程序计数器中。

程序计数器,就是用来存储了下一条指令所在内存的地址。
CPU的控制器,会参照程序计数器的数值,从内存中读取指令,并执行。

(6) pushq %rbp

作用同(1)

(7) movq %rsp, %rbp

作用同(2),为了形成了add_a_and_b函数的栈帧

(8) movl %edi, -4(%rbp)

rbp寄存器,在步骤(7)已经被更新为rsp寄存器的值了,也就是当前add_a_and_b函数的栈帧首地址。
将edi寄存器,此时保存的值是8,传送到rbp-4的位置,也就是第一个参数入栈了

(9) movl %esi, -8(%rbp)

将esi寄存器,此时保存的值是5,传送到rbp-8的位置,也就是第二个参数入栈了

(10) movl -8(%rbp), %eax

将rbp-8地址的值,也就是5,传送到eax寄存器。
eax,累加寄存器,主要用来做加法运算。

(11) movl -4(%rbp), %edx

将rbp-4地址的值,也就是8,传送到edx寄存器。

(12) addl %edx, %eax

加法指令格式:ADD A,B //A=A+B;
将edx与eax中的数值相加,结果存在edx中

(13) popq %rbp

取出栈中最近一次写入的值并写入到rbp寄存器,其实就是步骤(6)存入栈的值。
pop指令还会将esp寄存器的地址加4,回收栈帧。
64位寄存器就是将rsp寄存器的地址加8,回收栈帧。

(14) ret

ret指令的作用,在步骤(5)中已涉及

(15) popq %rbp

上述已提到

(16) ret

ret指令的作用,在步骤(5)中已涉及

参考链接

  • 《程序是怎样跑起来的》, by 矢泽久雄
  • 汇编语言入门教程, by 阮一峰
  • 几种基本汇编指令详解
  • 一口气看完45个寄存器,CPU核心技术大揭秘

分析一个简单的汇编代码相关推荐

  1. 用C语言实现一个简单的计算器代码

    #include <stdio.h> #include <math.h> #include <stdlib.h> //预处理指令 int main(void) {d ...

  2. 一个简单的汇编报时小闹钟

    有些简陋,但是可以变色+滴答,却做了将近8天,还有1个下午6点-早上7点的小通宵.熬得头大,但是每当解决完一个问题或者发现一处自己没有想到的地方时,心情确实格外的高兴,总体的收获就是-磨刀不误砍柴工啊 ...

  3. 如何分析一个开源工程的代码

    开放源代码的项目,通常都是不完整的,就是说:只有源代码,没有完整的产品使用说明书,没有软件开发过程中的完整文档,源码中的注释也很少.之所以会这 样,可能是因为作者们有所保留,只开放源码,不开放关键的文 ...

  4. 从零开始实现一个简单的低代码编辑器

    一.写在前面 低代码编辑器作为一种能够极大地提升开发效率的 PaaS 软件,近些年来一直收到各大公司以及各路投资方的追捧.而对于我们前端开发者来说,编辑器也是为数不多的拥有较深前端技术深度的开发场景. ...

  5. 窥探原理:实现一个简单的前端代码打包器 Roid

    roid roid 是一个极其简单的打包软件,使用 node.js 开发而成,看完本文,你可以实现一个非常简单的,但是又有实际用途的前端代码打包工具. 如果不想看教程,直接看代码的(全部注释):点击地 ...

  6. win32c语言编程实例,实例分析一个简单的Win32程序

    本文较为详细的分析了一个Win32程序的组成.结构.实现方法及运行原理,对于进行Windows程序设计有很好的借鉴参考价值.分享给大家供大家参考之用.具体分析如下: 一.Windows程序与普通C或C ...

  7. java写一个简单的浪漫代码_2020浪漫七夕:7款程序员必备表白源码(超炫酷)

    2020七夕将要来临,php中文网给大家准备了七款程序员表白专用源码,让你可以一举俘获美人心,下面就来看一看这七款表白代码大全,包含html.html5.CSS.JQ编写的一些非常简单实用的表白代码, ...

  8. java写一个简单的浪漫代码,2018浪漫七夕:7款程序员必备表白源码(超炫酷)

    2018七夕将要来临,php中文网给大家准备了七款程序员表白专用源码,让你可以一举俘获美人心,下面就来看一看这七款表白代码大全,包含html.html5.CSS.JQ编写的一些非常简单实用的表白代码, ...

  9. ios(iphone/ipad)一个简单的用代码判断当前设备的方法

    直接NSLog(@"current_device:%@",[UIDevice currentDevice].model); 即可看出它输出的是当前设备,所以根据这个字符串可简单的判 ...

最新文章

  1. 求解两个非负整数的最大公约数(C语言实现)
  2. bio和bieos哪个标注模式好_阿里巴巴和亚马逊电商模式差异?哪个电商好做
  3. 15.17 对缺乏潜在类型机制的补偿
  4. 如何解决ORA-12638: 身份证明检索失败错误
  5. 五年后的4.20地震
  6. 分布式面试 - 如何基于 dubbo 进行服务治理、服务降级、失败重试以及超时重试?
  7. 步骤一:入门Linux基础\01.Linux 简介和安装\第2章 Ubuntu系统的安装
  8. 实战Linux Bluetooth编程 (八) Class of Device
  9. 软件开发过程的一个实例
  10. Warning: Stopping rpcbind.service, but it can still be activated by:rpcbind.socket
  11. 安卓版 网易云音乐 6.4.3
  12. sequel pro 格式化sql
  13. 管道 pipe是什么?(进程通信的一种方式)
  14. 《Java解惑》系列——01表达式之谜——谜题09:半斤
  15. 关于Java单例模式的思考
  16. 以太网、网络拓扑结构分类、双绞线的传输距离和分类
  17. 计算机窗口弹出多个窗口,电脑怎么打开多个微信窗口
  18. PyCrypto —— 一个极好的信息安全python库
  19. 如何根据自己的需要培养游戏开发技能?又一篇游戏编程入门指南
  20. 小周SEO:网站关键词【杭州SEO】排名到前3名SEO技巧

热门文章

  1. ESP32----NVS使用
  2. 又发现一个免费网盘分享给大家
  3. matlab筛选表格数据导出,excel表格里怎么将筛选数据导出-Excel表格在进行筛选,我如何可以导出所有筛选出来......
  4. 2018省赛第九届蓝桥杯真题C语言B组第八题题解 日志统计
  5. 2020年国考申论备考:评价类(观点)题和理解类题目的辨析
  6. Python利用selenium简单的爬取网易云歌曲排行榜
  7. 辽宁 viewpro.php,辽宁省策划学会赴沙地沟村考察
  8. 解决笔记本连接wifi提示无法连接这个网络问题
  9. linux测试读写的工具,Linux 下的硬盘读写速度测试工具
  10. 链表实现电话簿(C++)