通过反汇编理解计算机程序执行过程

来源:互联网 发布:百年孤独中的名句 知乎 编辑:程序博客网 时间:2024/05/23 00:03

李宗峰 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

本文探究下面一段代码的工作原理:

1.  int g(int x)

2.  {

3.    return x + 3;

4.  }

5.   

6.  int f(int x)

7.  {

8.    return g(x);

9.  }

10.  

11. int main(void)

12. {

13.   return f(8) + 1;

14. }

此处进行试验的环境是在virtualbox虚拟机上创建一个linux虚拟机,虚拟机为32位。Virtualbox版本:


linux虚拟机安装的系统版本:


下面开始进行试验,首先新建一个目录专门用于此次的实验,然后新建test.c文件,将上面的代码复制到test.c中,直接在宿主机和虚拟机之间进行复制的话需要安装virtualbox增强工具由于代码比较短,可以直接敲代码进去就好了。

然后将源文件编译成汇编代码,命令如下:gcc –S –o test.s test.c -m32

然后打开test.s如下图所示:


汇编代码中的以.开头的部分是不会执行的,只是用来对代码进行标记的。为此我们删除所有的无关行,得到精简的版本如下所示:

 

可以看到上面的代码跟我们的c程序源代码是对应的。

我们做这个实验的目的是了解计算机的工作过程,我们现在计算机是冯诺依曼体系的,这个体系概括起来就是程序存储执行,程序每次执行的代码存放在寄存去eip指向的地址,每次程序执行eip指向的指令然后将eip的值加“1”,指向下一条指令。由于每一条指令的长度可能不一样长,所以这里的“1”是一条指令。

像是eip为指向当前cpu要执行的指令一样,CPU(这里为80386)上的寄存器一般都有自己的用途,具体用途如下:

1. 通用寄存器(EAX、EBX、ECX、EDX、ESP、EBP、ESI、EDI)

2. 段寄存器(CS、SS、DS、ES、FS、GS)

3. 指令指针寄存器和标志寄存器(EIP、EFLAGS)

4. 系统表寄存器(GDTR、IDTR、LDTR、TR)

5. 控制寄存器(CR0、CR1、CR2、CR3、CR4)

6. 调试寄存器(DR0、DR1、DR2、DR3、DR4、DR5、DR6、DR7)

7. 测试寄存器(TR6、TR7)

具体的寄存器使用等介绍可以求助谷歌大神。

 

下面分析汇编代码的执行过程:

首先函数从main函数开始执行,可以看到main、f、g函数开始都是

pushl %ebp

movl %esp %ebp

这两行汇编代码,这两段代码的功能就是将当前的栈底保存,然后将栈底指向当前的栈顶,这样的话可以起到保存程序执行到此时的现场,便于函数执行完之后恢复现场。每个函数执行完之后最后的leave指令是这两个指令的逆执行:

 movl %esp,%ebp

 popl %ebp

每个函数leave之前到开始的第三行为程序的实际执行代码。

下面逐行分析汇编指令:

Subl $4,%esp 将栈顶减四

Movl $8,(%esp)将8存到栈中。

Call f 调用f函数

然后进入f函数分析,首先保存现场然后从第三行开始正式执行函数的任务:

Subl $4 %esp 将栈顶上移一位

Movl  8(%ebp) %eax这里8(%ebp)指向的是main函数中存放数字8的位置(在栈中放入8之后又一次压栈操作和将栈顶上移操作,两次操作使栈顶上移了8位).这一步将8放入eax中。

Movl %eax (%esp) 将eax中的值放入栈顶。

Call g 调用函数g

Movl 8(%ebp),%eax  分析压栈情况可以知道此时是将8放入eax中。

Addl $3, %eax将eax中的数值(8)加3,并返回(一般使用eax存放返回值)。

最后由于是一直返回,所以此处使用popl %ebp既可以完成弹栈操作。

 

从这段实验可以看出,计算机的执行过程为:eip始终指向当前执行的指令,每个函数执行的时候都有相对独立的堆栈,参数都是通过堆栈进行传递。

 


0 0
原创粉丝点击