1. 问题描述
通过这一周的学习,我们初步了解了存储计算机工作模型以及基本的汇编语言,现在要分析一个C语言小程序汇编出来的汇编代码如何在存储计算机工作模型上一步步地执行。
2. 解决步骤
2.1 计算机工作原理介绍
计算机的基本原理主要分为存储程序和程序控制,预先要把控制计算机如何进行操作的指令序列(称为程序)和原始数据通过输入设备输送到计算机内存中。每一条指令中明确规定了计算机从哪个地址取数,进行什么操作,然后送到什么地址去等步骤。计算机在运行时,先从内存中取出第一条指令,通过控制器的译码,按指令的要求,从存储器中取出数据进行指定的运算和逻辑操作等加工,然后再按地址把结果送到内存中去。接下来,再取出第二条指令,在控制器的指挥下完成规定操作。依此进行下去。直至遇到停止指令。
2.2 示例
2.2.1 x86-32汇编介绍
对于x86-32的计算机,有一个EIP寄存器指向内存的某一条指令,EIP是自动加一的(不是一个字节,也不是32位,而是加一条指令),虽然x86-32中每条指令占的存储空间不一样,但是它能智能地自动加到下一条指令。
2.2.2 分析过程
1. 使用vim编辑器编写一个c程序main.c
2. 使用gcc将main.c编译成一个汇编代码main.s
使用以下命令将main.c编译成一个汇编代码main.s
gcc -S -o main.s main.c -m32
用vim编辑器查看main.s汇编文件
汇编代码中所有以“.”打头的字符串都是编译器在链接阶段所需辅助信息,不会实际执行,可以删除干净,在vim编辑器的底线命令模式下输入以下命令行:
g/^\./d
得到实际执行的汇编代码
3.分析汇编代码执行过程
EIP寄存器是指向代码段中的一条条指令,即main.s中的汇编指令,从“main:”开始,它会自加一,调用call指令时它会修改EIP寄存器。EBP寄存器和ESP寄存器也特别重要,这两个寄存器总是指向一个堆栈,EBP指向栈底,而ESP指向栈顶。(注:栈底是一个相对的栈底,每个函数都有自己的函数堆栈和基地址。)另外,EAX寄存器用于暂存一些数值,函数的返回值默认使用EAX寄存器存储并返回上一级调用函数。
需要注意的是,x86体系结构栈地址是向下增长的(地址减小),这里为了便于知道堆栈存储空间的个数大小,堆栈空间的存储标号是逐渐增大的。
初始堆栈空间为
程序从main函数开始执行,即EIP寄存器指向“main:”下面的第一条汇编指令
18 pushl %ebp
将寄存器EBP的值压栈,此时堆栈空间为
下一条指令
19 movl %esp, %ebp
将寄存器ESP中的值赋值给EBP,此时堆栈空间为
下一条指令
20 subl $4, %esp
将ESP中的值(地址)减立即数4,即将ESP寄存器向下移动一个标号,此时堆栈空间为
下一条指令
21 movl $9, (%esp)
将立即数9放入ESP寄存器所指的标号2的位置,此时堆栈空间为
下一条指令
22 call f
调用call指令,实际上call指令相当于以下两条指令
pushl %eip(*)
movl f %eip(*)
即把EIP寄存器的值(由于EIP寄存器指向下一条要执行的指令,故此时EIP寄存器的值是行号23所对应的指令地址)放到了标号3的位置,ESP的值减4。然后将行号9对应的指令地址放入EIP,堆栈空间为
下一条指令
9 pushl %ebp
同上,将EBP中的值放入标号4中,堆栈空间如下
下一条指令
10 movl %esp, %ebp
同上,将ESP寄存器中的值放入EBP寄存器中,堆栈空间如下
下一条指令
11 subl $4, %esp
同上,将ESP寄存器减4,即指向下一个标号5,堆栈空间如下
下一条指令
12 movl 8(%ebp), %eax
同上,EBP寄存器的值加8,即EBP寄存器指向标号2的位置,将这个位置的值(立即数9)放入EAX寄存器中,堆栈空间如下
下一条指令
13 movl %eax, (%esp)
将EAX寄存器中的值放入ESP寄存器所指向的位置(即编号5的位置),堆栈空间如下
下一条指令
14 call 9
这条指令的作用与22号指令相似,不同的是相当于跳转到g函数,堆栈空间如下
下两条指令的作用与main和f的前两条指令作用类似,不再赘述
2 pushl %ebp
3 movl %esp, %ebp
栈空间如下
下一条指令
4 movl 8(%ebp), %eax
同上,将EBP寄存器的值加8,EBP指向标号5的位置,将其中的立即数9放到EAX寄存器中,堆栈空间如下
下一条指令
5 addl $11, %eax
将EAX寄存器中的值加立即数11,此时EAX寄存器中的值为20,堆栈空间无变化
下一条指令
6 popl %ebp
将栈顶的值放入EBP寄存器中,此时栈顶的值是标号4的地址,则EBP指向标号4,堆栈空间如下
下一条指令
7 ret
ret指令的作用相当于
popl %eip(*)
即将栈顶的值弹出给EIP寄存器,堆栈空间如下
下一条指令
15 leave
leave指令相当于以下两条指令
movl %ebp, %esp
popl %ebp
即用来撤销函数堆栈,堆栈空间如下
下一条指令
16 ret
同上一条ret指令类似,堆栈空间如下
下一条指令
23 addl $2, %eax
将EAX寄存器中的值加2,此时EAX寄存器中的值为22,堆栈空间不变
下一条指令
24 leave
撤销main函数堆栈空间,此时堆栈空间为初始状态
25 ret
main函数返回
此时EAX寄存器的值为22,也就是这个程序的最终返回结果
分析完成
3.总结
通过函数调用堆栈框架暂存函数的上下文状态信息,整个程序的执行过程变成了一个指令流,从CPU中“流”了一遍,最终堆栈空间又恢复了空栈状态。通过分析这个c语言小程序的汇编代码,让我更进一步从程序员的角度理解了存储计算机的工作过程,即可以把cpu抽象成一个for循环,因为它总是在执行next instruction(下一条指令),然后从内存里取下一条指令来执行,而cpu与内存之间用总线连接。