Linux内核分析之一——简单C程序的汇编代码工作过程分析

作者:姚开健

原创作品转载请注明出处

《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000


1,计算机工作及汇编基础知识

现代计算机采用冯诺依曼体系结构,即存储程序计算机。计算机将代码指令储存在内存中,然后CPU将一条一条指令从内存中读取并执行,得出结果。在32位,并采用采用AT&T汇编的计算机里,eip是即ip寄存器,储存下一条指令的地址,程序员不能修改,esp是栈顶指针,ebp是栈底指针。当调用函数时,即call f,当前eip压栈(pushl %eip),并将eip修改为函数地址;当进入函数时,即enter,当前栈底指针压栈,即pushl %ebp,并修改栈底指针与栈顶指针一致,即movl %esp, %ebp;当函数执行完时,需要执行leave(当函数中调用了其他函数才需要leave,即movl %ebp, %esp ;popl %ebp)和ret(popl %eip)。更多汇编语言知识请参考:Linux 汇编语言开发指南http://www.ibm.com/developerworks/cn/linux/l-assembly/


2,简单C程序的汇编代码工作过程分析:

C代码如下:

int g(int x)
{
 return x + 12;
}

int f(int x)
{
  return g(x);
}

int main(void)
{
  return f(12) + 12;
}


将代码编译成汇编语言:
gcc -S -o main.s main.c -m32

得到的汇编代码如下所示(已删除部分链接所需内容):
Linux内核分析之一——简单C程序的汇编代码工作过程分析_第1张图片


下面我们来分析这些汇编代码的执行过程及其堆栈变化:
首先是进入main函数,即enter:

pushl %ebp
movl %esp, %ebp


即进入函数,当前栈底指针压栈,并修改栈底指针与栈顶指针一致

Linux内核分析之一——简单C程序的汇编代码工作过程分析_第2张图片


然后栈顶指针下移,插入12这个函数参数

subl $4, %esp
movl $12, (%esp)

Linux内核分析之一——简单C程序的汇编代码工作过程分析_第3张图片
接着调用函数f,eip压栈,eip为23即上面汇编代码的第23行,接着eip被自动修改为f的地址:

call f
Linux内核分析之一——简单C程序的汇编代码工作过程分析_第4张图片
进入函数f又是enter:

pushl %ebp
movl %esp, %ebp
Linux内核分析之一——简单C程序的汇编代码工作过程分析_第5张图片 Linux内核分析之一——简单C程序的汇编代码工作过程分析_第6张图片
接着执行
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)


即栈顶指针下移(即-4),并将12送到eax寄存器(8(%ebp)的意思是ebp指针上移2次,即+8,然后取里面的12),然后将eax里的12送到esp里:

调用函数g,eip为第15行:
call g

Linux内核分析之一——简单C程序的汇编代码工作过程分析_第7张图片

进入函数g又是enter:

pushl %ebp
movl %esp, %ebp


Linux内核分析之一——简单C程序的汇编代码工作过程分析_第8张图片Linux内核分析之一——简单C程序的汇编代码工作过程分析_第9张图片


接着执行

movl 8(%ebp), %eax
addl $12, %eax

即把ebp上移2次,取出12送到eax寄存器,然后eax与12相加得24;

接着

popl %ebp

当前栈里的ebp为1984,popl %ebp之后ebp就指向1984,esp上移一次

Linux内核分析之一——简单C程序的汇编代码工作过程分析_第10张图片

接着执行

ret

ret即popl %eip,当前eip = 15,执行ret之后就退回到第15行执行

leave

leave为 movl %ebp, %esp ; popl %ebp,即把当前栈顶指针修改为当前栈底指针即1984,并把栈底指针出栈,出栈后栈底指针为1996,栈底指针为1988:

Linux内核分析之一——简单C程序的汇编代码工作过程分析_第11张图片Linux内核分析之一——简单C程序的汇编代码工作过程分析_第12张图片


接着执行

ret

即popl %eip。当前eip为23,第23行,



接着执行

addl $12, %eax

把局部变量12与当前eax寄存器里的值24相加得到36

接着执行

leave
ret

Linux内核分析之一——简单C程序的汇编代码工作过程分析_第13张图片



总结

通过这个简单的C程序的汇编代码工作过程分析,我们知道计算机总是一条一条指令地执行,下一条指令地址保存在eip寄存器中(32位计算机才是eip),当进行函数调用时,1、函数参数入栈,然后2、call,即保存当前未执行的下一条指令地址即eip入栈,计算机自动修改eip为被调用函数地址,然后才进入被调用函数中执行,3、进入被调用函数需要把调用函数的栈底指针入栈保存以便返回时可以回退到原来执行的位置,并把被调用函数的栈底指针与栈顶指针保持一致,开始被调用函数的执行。函数执行时,可以通过指令获取在栈中的函数参数值。函数执行完毕时,leave和ret指令即把栈底指针出栈,恢复到调用函数原来的位置,再出栈eip,继续在断点处的执行。

但也有一些问题尚未明白:

1,main中,为什么不直接pushl 12,而是subl $4, %esp;movl $12, (%esp);

2,f中为什么要把12送入eax里?即movl 8(%ebp), %eax;为什么不直接把8(%ebp)送到(%esp)中,而是还有经过eax周转一下?函数调用时的参数机制是怎样的?



你可能感兴趣的:(Linux内核分析之一——简单C程序的汇编代码工作过程分析)