原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
现在的计算机大多数采用的是“冯.诺依曼”体系结构,即采用存储程序的核心思想。它是将指令和数据通通存放在内存中,有eip(rip)指定下一条执行指令的地址。 在计算机底层,指令以二进制的方式被读取执行。但由于二进制指令对于人来说很难读懂,故而采用汇编语言这一和二进制之间简单映射又容易被理解的语言,帮助人们理解计算机底层的指令执行过程。 实验给出的源码如下:
int g( int x )
{
return x + 19;
}
int f( int x )
{
return g(x);
}
int main(void)
{
return f(12) + 19;
}
然后使用下面的这个命令:
gcc -S -o main.S main.c -m32
将C源码以32位机器的指令格式编译成汇编语言如图2:
我们可以看到在汇编代码中,每个函数的开头都是:
pushl %ebp
movl %esp, %ebp
这是例程的序幕工作:它将EBP压如栈中,然后把ESP复制到ebp中,使其成为新的指针。这样可以保证后面的寻址都是根据EBP为基址进行寻址。
接下来在main函数中:
subl $4, %esp
movl $12, (%esp)
第一句是将栈顶向下增长4,开辟一块新的空间; 第二句是将立即数12放到ESP所指向的存储空间;
然后是调用函数f
在f函数中,同样是进行了前两句的准备工作
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
第一句是将栈顶向下增长4,开辟一块新的空间; 然后将EBP向上增长4的位置的存储内容给eax,也就是main函数中刚刚所存储的12给eax然后又把12存储带当前栈顶位置;
然后是调用函数g;
movl 8(%ebp), %eax
addl $19, %eax
popl %ebp
由于函数调用以及准备工作将栈顶又向下移了2个位置所以第一句将EBP向上移动8的位置的值给eax;然而那个位置存储的是12;所以在执行完第二句之后eax的值就成了12+19 = 31.然后EBP出栈,这句话之后EBP就变成了调用g函数之前的值,相应的esp也要减4。
下面的return语句,将esp的值+4,然后eip指向f函数中调用g函数的下一条指令leave,而leave指令相当于下面两条指令:
Movl %ebp,%esp
Popl %ebp
执行完第一句之后,就将之前存的12弹出栈,然后第二句执行完之后,EBP从函数f的基址变为了main函数的基址; Return执行完之后,esp向上移动4个位置,指向存储eip的存储单元,这个eip指向的事调用f的下一条指令,即
addl $19, %eax
此时eax的值为19+31 = 50.然后继续执行完后面的语句,EBP和esp变为执行main函数前的原始值。
总结:
1、通过分析以上步骤,使我深刻认识到计算机底层执行指令时的流程及栈的操作; 2、也让我明白了C语言和汇编语言之间的转化关系。