通过汇编一个简单的C程序,分析汇编代码理解计算机工作原理
2016-02-28 12:08
726 查看
徐晨 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
这是一个简单的C语言程序,我们为了通过汇编语言分析其调用过程,首先将其编译成汇编代码。本实验中我们采用x86-32汇编,命令如下:
我们去掉汇编文件中用于链接过程的代码,得到下面程序:
分析一下该程序,该程序的最初入口为main, 我们从line18开始。
进入main函数中,我们首先保存现场,新开一个栈帧。具体做法为line18&19,将原来的ebp压栈,然后讲其赋值为esp,此时,新的栈帧中形成了一个空栈。
line20&21中,main函数向下移动栈顶4个byte,并将操作数23放入栈中。然后在line22调用f,对比C代码我们知道23实际上是调用函数f的参数。
call f命令可以理解为压栈eip当前值(line23),然后修改eip到f的地址(9) (注意这里是伪指令,因为eip的值不能显示),所以接下来CPU的执行进入函数f。
在函数f中,我们首先做的依然是保存现场,新开一个栈帧(line9&10),和在main函数中一样。之后我们向下移动栈顶4个byte(line11),并将ebp+8地址的值传递给eax(这个值就是23)(line12),接着将eax寄存器中的值放入esp地址指向的空间中(line13),我们可以看到(line13&14)和(line21&22)非常相似,实际上这都是调用一个含参函数的过程,不同之处在于main函数中我们直接将操作数放入栈顶(注意这里的栈顶是针对调用f之前而言),而在f中我们讲寄存器中的值放入栈顶。
line14我们调用函数g,这里和调用f的过程一样,压栈eip,将eip赋值为g的地址(line2)。
进入函数g,依然保存现场,新开栈帧(前面已分析,不再赘述)。line4我们看到,实际上是函数g在取它的参数x(也就是23),line5开始了该程序第一次对操作数的运算,将参数x+9(23+9=32)放入eax中(注意x86-32中eax是默认存储返回值的寄存器)。我们可以看到此时该函数的堆栈情况如下所示:
然后该函数直接pop %ebp,这是因为新栈帧中并没有压入任何内容,所以pop了以后,ebp就指向了f调用g之前的栈底。line7中的ret作用相当于popl %eip,也就是说g函数已经执行完了,返回到f函数中去(line15)。
我们看到line15是一条leave命令,该命令相当于movl %ebp %esp, pop %ebp,实际上我们可以理解为f函数此时已经不再需要它自己的栈帧,所以它pop了以后,ebp指向了main调用f之前的栈底。line16中的ret作用相当于popl %eip,也就是说f函数已经执行完了,返回到main函数中去(line23)。
回到main函数(line23)中,我们开始了对操作数的第二次运算(此前eax的值32),我们将该值加7并放入eax中(32+7=39)。接下来main函数执行leave,放弃自己的栈帧,将ebp指向调用main之前的栈底(该值由操作系统管理),接着line25执行ret,也就是main函数已经执行完了,返回到调用main的函数之中(操作系统管理)。
至此,整个程序执行完毕。
这是一个简单的C语言程序,我们为了通过汇编语言分析其调用过程,首先将其编译成汇编代码。本实验中我们采用x86-32汇编,命令如下:
gcc -S -o main.s main.c -m32
我们去掉汇编文件中用于链接过程的代码,得到下面程序:
分析一下该程序,该程序的最初入口为main, 我们从line18开始。
18 pushl %ebp 19 movl %esp, %ebp
进入main函数中,我们首先保存现场,新开一个栈帧。具体做法为line18&19,将原来的ebp压栈,然后讲其赋值为esp,此时,新的栈帧中形成了一个空栈。
line20&21中,main函数向下移动栈顶4个byte,并将操作数23放入栈中。然后在line22调用f,对比C代码我们知道23实际上是调用函数f的参数。
20 subl $4, %esp 21 movl $23, (%esp) 22 call f
call f命令可以理解为压栈eip当前值(line23),然后修改eip到f的地址(9) (注意这里是伪指令,因为eip的值不能显示),所以接下来CPU的执行进入函数f。
push %eip movl $9 %eip
在函数f中,我们首先做的依然是保存现场,新开一个栈帧(line9&10),和在main函数中一样。之后我们向下移动栈顶4个byte(line11),并将ebp+8地址的值传递给eax(这个值就是23)(line12),接着将eax寄存器中的值放入esp地址指向的空间中(line13),我们可以看到(line13&14)和(line21&22)非常相似,实际上这都是调用一个含参函数的过程,不同之处在于main函数中我们直接将操作数放入栈顶(注意这里的栈顶是针对调用f之前而言),而在f中我们讲寄存器中的值放入栈顶。
9 pushl %ebp 10 movl %esp, %ebp 11 subl $4, %esp 12 movl 8(%ebp), %eax 13 movl %eax, (%esp) 14 call g
line14我们调用函数g,这里和调用f的过程一样,压栈eip,将eip赋值为g的地址(line2)。
进入函数g,依然保存现场,新开栈帧(前面已分析,不再赘述)。line4我们看到,实际上是函数g在取它的参数x(也就是23),line5开始了该程序第一次对操作数的运算,将参数x+9(23+9=32)放入eax中(注意x86-32中eax是默认存储返回值的寄存器)。我们可以看到此时该函数的堆栈情况如下所示:
然后该函数直接pop %ebp,这是因为新栈帧中并没有压入任何内容,所以pop了以后,ebp就指向了f调用g之前的栈底。line7中的ret作用相当于popl %eip,也就是说g函数已经执行完了,返回到f函数中去(line15)。
2 pushl %ebp 3 movl %esp, %ebp 4 movl 8(%ebp), %eax 5 addl $9, %eax 6 popl %ebp 7 ret
我们看到line15是一条leave命令,该命令相当于movl %ebp %esp, pop %ebp,实际上我们可以理解为f函数此时已经不再需要它自己的栈帧,所以它pop了以后,ebp指向了main调用f之前的栈底。line16中的ret作用相当于popl %eip,也就是说f函数已经执行完了,返回到main函数中去(line23)。
15 leave 16 ret
回到main函数(line23)中,我们开始了对操作数的第二次运算(此前eax的值32),我们将该值加7并放入eax中(32+7=39)。接下来main函数执行leave,放弃自己的栈帧,将ebp指向调用main之前的栈底(该值由操作系统管理),接着line25执行ret,也就是main函数已经执行完了,返回到调用main的函数之中(操作系统管理)。
23 addl $7, %eax 24 leave 25 ret
至此,整个程序执行完毕。
相关文章推荐
- 2-9-扩展的线性单链表(带头结点)-线性表-第2章-《数据结构》课本源码-严蔚敏吴伟民版
- tcp有限状态机
- 005_Http之request获取客户端信息08-编码之request编码-url编码
- HelloWorld系列之--------手动下载网络页面
- Java HttpGet
- 通过loadrunner将http返回response写入文本txt中
- http://www.cnblogs.com/yjmyzz/p/dubbox-demo.html
- http://my.oschina.net/u/719192/blog/506062?p={{page}}
- Python计算机视觉:第十章 OpenCV
- JVM并发机制的探讨——内存模型、内存可见性和指令重排序 http://my.oschina.net/chihz/blog/58035
- Python计算机视觉:第九章 图像分割
- Python计算机视觉:第八章 图像类容分类
- 4.1.3.3 Android 网络状态监听的静态广播接受者和动态广播接受者Broadcast-Receiver
- 使用networkx导出关系网络并用gephi进行简单分析
- 【HTTP】Fiddler(一) - Fiddler简介
- HDU 1198 Farm Irrigation (并查集)
- windows平台HTTP代理server搭建(CCproxy)
- 005_Http之request获取客户端信息08-编码之request编码
- 2-8-双循环链表链式存储结构-线性表-第2章-《数据结构》课本源码-严蔚敏吴伟民版
- 数据结构:索引之线性索引