从一段x86汇编程序看计算机是如何工作

#####################################
作者:
张卓
原创作品转载请注明出处:《Linux操作系统分析》MOOC课程 http://www.xuetangx.com/courses/course-v1:ustcX+USTC001+_/about
#####################################
1. 汇编一个简单的C程序
用C语言写一个小程序,简单即可,如下面的:
int g(int x)
{
  return x + 4;
}
int f(int x)
{
  return g(x);
}
int main(void)
{
  return f(12) + 1;
}
然后用下面的命令编译成汇编代码:
gcc -S -o main.s main.c -m32 (64位Linux虚拟机环境下适用,32位Linux环境可能会稍有不同)
得到一个main.s文件,里面以点开头的都是链接时的辅助信息;我们在看这些汇编代码时可以把他们都删除掉,留下来的就是纯汇编代码。
 1 g:
  2     pushl   %ebp
  3     movl    %esp, %ebp
  4     movl    8(%ebp), %eax
  5     addl    $4, %eax
  6     popl    %ebp
  7     ret
  8 f:
  9     pushl   %ebp                                                    
 10     movl    %esp, %ebp
 11     subl    $4, %esp       
 12     movl    8(%ebp), %eax 
 13     movl    %eax, (%esp)                                            
 14     call    g         
 15     leave                                                           
 16     ret                  
 17 mian:                   
 18     pushl   %ebp         
 19     movl    %esp, %ebp  
 20     subl    $4, %esp
 21     movl    $12, (%esp)
 22     call    f   
 23     addl    $1, %eax  
 24     leave                                                           
 25     ret                                               
2. 特殊指令含义
在分析上面的x86汇编代码之前,我们先来看一下几个特殊指令含义及其如何操作的
instruction        What it does
pushl %eax         subl $4,%esp
                   movl %eax,(%esp)
popl %eax          movl (%esp),%eax
                   addl $4, %esp
call ox12345       pushl %eip(*)
                   movl $0x12345, %eip(*)
ret                popl %eip(*)
enter              pushl %ebp
                   movl %esp,%ebp
leave              movl %ebp,%esp
                   popl %ebp
带*的指令是不能被程序员直接使用的,是伪指令。因为eip寄存器不能被直接修改,只能通过特殊指令间接修改。直接操作eip寄存器容易产生安全隐患。
3. 完整汇编程序执行过程分析
在开始分析之前,先对几个特殊的寄存器说明一下:
eip:从main开始执行一条指令并自加一,遇到call会改变eip的值。
ebp:栈底
esp:栈顶
eax:函数的返回默认使用eax寄存器存储,返回给上一级函数。

所以有汇编代码都是从main开始执行的:
17 mian:
18 pushl %ebp
19 movl %esp, %ebp
这两行代码个人理解,就是为每一个函数创建一个逻辑上的堆栈。不管ebp之前的值是多少,先把ebp入栈,再把esp的值赋给,ebp实际效果如下图:
从一段x86汇编程序看计算机是如何工作_第1张图片
 20     subl    $4, %esp
 21     movl    $12, (%esp)
这两条指令是将12入栈。
22     call    f 
call指令是调用函数,执行过程是,将eip(当前指令的下一行指令地址)入栈,将f代表的地址放入eip中,结果如下图:
从一段x86汇编程序看计算机是如何工作_第2张图片
跳转至f执行,9,10行指令和刚才的一样,直接结果就是是esp和ebp指向图中堆栈标号为4的地方。接下来的指令,
 11     subl    $4, %esp       
 12     movl    8(%ebp), %eax
 13     movl    %eax, (%esp) 
将esp指针向下移一位;再将%ebp+8地址的值,也就是12放入eax寄存器;最后将eax中的值放入栈,执行后的结果如下图:
从一段x86汇编程序看计算机是如何工作_第3张图片
 14     call    g  
call又调用g函数,执行过程和刚才一样,直接看结果:
从一段x86汇编程序看计算机是如何工作_第4张图片
跳转到第2行,开始执行,
  2     pushl   %ebp
  3     movl    %esp, %ebp
  4     movl    8(%ebp), %eax
  5     addl    $4, %eax
2,3行指令看着是不是很熟悉?对,就是很熟悉,函数一开始执行的就是它,关于堆栈的操作。4,5行指令,就是将%ebp+8地址的值,也就是12放入eax寄存器中;最后将eax中的值加4。执行结果如下图所示:
从一段x86汇编程序看计算机是如何工作_第5张图片
  6     popl    %ebp
  7     ret
popl %ebp将当前esp中的值赋给ebp同时esp指针向上移一位。ret指令等于:popl %eip。两条指令执行后,效果如下:
从一段x86汇编程序看计算机是如何工作_第6张图片
eip当前的值是指向15行的,然后从15行开始执行指令,
 15     leave                                                           
leave指令将ebp的值给esp,然后将ebp出栈,直接看栈的变化:
从一段x86汇编程序看计算机是如何工作_第7张图片
 16     ret  
函数返回,eip出栈,释放形参占用的栈资源:
从一段x86汇编程序看计算机是如何工作_第8张图片
跳到23行执行,
 23     addl    $1, %eax  
 24     leave                                                           
 25     ret  
将eax中的值加1,之后eax中的值应该是17,它将作为main函数的返回值。最后,退出。
从一段x86汇编程序看计算机是如何工作_第9张图片
可以到看到,程序执行完毕,堆栈是恢复原状的,释放了所有占用的资源。
4. 总结
计算机是由指令驱动的,从上面分析x86汇编代码执行过程,可以看出计算机执行指令是一条一条的执行,执行指令的过程其实就在操作堆栈。一个程序执行完毕,堆栈等资源随之释放。
所以计算机 工作过程,可以简单理解为: 高速运转的CPU不断地从EIP寄存器指向的CS中,拿出指令来执行,周而复始;执行指令的过程,简单来说就是操作堆栈的过程。
请使用手机"扫一扫"x

你可能感兴趣的:(从一段x86汇编程序看计算机是如何工作)