作者:ahnselina
原创作品转载请注明出处
Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
本文尝试以一个简单的C程序及其汇编代码为例,分析计算机是如何执行程序的。
首先来看下计算机的基本模式:
如图,计算机基本都是这种CPU通过总线与内存相连的模式,
CPU上有各种寄存器,如下
其中比较重要的寄存器有EIP寄存器(IP:Instruction Pointer,在32位系统中,该寄存器叫EIP,64位系统中叫RIP),可以把该寄存器看做指针,它指向CPU将要执行的下一条指令。
从程序员的角度可以把计算机执行程序的过程简单理解为如下:
即:CPU在内存中取一条指令,执行,执行完后再取下一条指令,如此往复循环,其中EIP就指向内存区域CS段(代码段)。
下面通过实际例子来分析计算机执行汇编程序的过程
下面为test.c的代码:
int g(int x)
{
return x + 1;
}
int f(int x)
{
return g(x);
}
int main(void)
{
return f(6) + 3;
}
使用如下命令:gcc -S -o test.s test.c -m32
编译出汇编文件(编译出来的原始文件有些其他信息,已经被删除,剩下的干净的汇编代码如下图):
下面我们来从main函数开始分析汇编代码:
主要分析过程如图,我们做一些约定:系统默认为32位的
a.假设开始的时候堆栈的基指针%ebp和%esp都指向栈底(地址10)的地方,也就是说这时栈为空;
b.图中10-9-8-7....表示地址,也表示堆栈由高地址向低地址生长,同时每个地址之间相差4。
也就是初始状态如图①所示,
1.第19行开始执行,
pushl %epb这个入栈操作,对应有两个操作
subl $4, %esp
movl %ebp, (%esp)即通过19行的入栈,堆栈状态由图①变到图2;
2.执行
movl %esp, %ebp堆栈状态由图2变到图3;
其实这1和2两步对应了enter命令:
pushl %ebp
movl %esp,%ebp
3.执行
subl $4, %esp堆栈状态由图3变到图4,即%esp栈顶指针向下移4,到位置8的地方(图4的那个位置标记写错了);
4.执行
movl $6, (%esp)堆栈状态由图4变到图5,同时将6这个立即数存到目前%esp所指的地方;
5.执行
call f这个对应操作为:
pushl %eip(*)movl f, %eip(*)此时eip指向f函数
堆栈状态由图5变到图6;
执行完call f后,跳转到执行f函数
6.此时又是执行
pushl %ebp堆栈状态由图6变到图7;
7.执行
movel %esp, %ebp堆栈状态由图7变到图8;
8.执行
subl $4, %esp堆栈状态由图8变到图9;
9.执行
movl 8(%ebp), %eax即把(%ebp-8)--->%eax,也就是把图10中位置8里面的数值存到寄存器eax中。
10.执行
movel %eax, (%esp)堆栈状态由图9变到图10,即把6放入位置5;
11.执行call g
类似call f
堆栈状态由图10变到图11;
跳转到执行g函数
12. 执行
pushl %ebp堆栈状态由图11变到图12;
13.执行
movl %esp, %ebp堆栈状态由图12变到图13;
14.执行
<span style="font-size:18px;">movl 8(%ebp), %eax6+1 --->eax
addl $1, %eax</span>
15.执行
popl %ebp堆栈状态由图13变到图14;
16.执行ret
堆栈状态由图14变到图15;
解释:ret实际是
popl %eip(*)
eip指向第16行
17.执行leave
leave实际对应指令
movl %ebp,%esp所以堆栈状态由图15变到图16图17;
popl %ebp
18.ret
同16
堆栈状态由图17变到图18;
eip指向第24行
19.执行
addl $3, %eax此时eax寄存器中值为6+1+3;
20.执行leave
同上
堆栈状态由图18变到图19图20;
可以看到这时的堆栈状态已经恢复到最开始的状态了。
总结:计算机执行程序,需要先将程序转化成自身可以执行的命令,然后再一条一条的执行这些命令。
注意计算机是不能直接执行汇编程序的,计算机只能识别机器语言,不过通过例子可以看到汇编可以直接操作CPU的某些寄存器,因此效率比其他语言高,不过编写代码的效率就不如其他语言了。
主要参考此门课程课件:《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000