算法 | 一段C语言和汇编的对应分析,揭示函数调用的本质

最近网易云课堂开放了一节叫Linux内核分析的课程 。 一直对操作系统和计算机本质很感兴趣 , 于是进去看了下 , 才第一堂课 , 老师就要求学生写一篇关于课时1的博客作为作业 。 对于这种新颖的作业形式 , 笔者相当惊讶 。 好吧 , 作为任务 , 还是完成一下吧 , 刚好需要消化一下 。 本文将会按照要求 , 将一段C语言代码编译成汇编 , 并给予分析和自己的思考 。
首先对会涉及到的一些CPU寄存器和汇编的基础知识罗列一下:
●16位、32位、64位的CPU寄存器名称有所不同 , 比如指令地址寄存器ip , 在16位中叫ip , 32位中叫eip , 64位叫rip
●32位的汇编指令通常以l结尾 , 比如movl相当于mov的含义
●ebp : 堆栈基地址 寄存器 , 这个寄存器保存的是当前执行绪的栈底地址
●esp : 堆栈栈顶 寄存器 , 这个寄存器保存的是当前执行绪的栈顶地址
●eip : 指令地址 寄存器 , 这个寄存器保存的是指令所在的地址 , CPU会不断的根据eip所指向的指令去内存取指令并执行 , 并自行累加取下一条指令逐条执行 。 eip无法直接赋值 , call、ret、jmp等指令可以起到修改eip的作用
●8(%ebp)表示先找到 ebp所指向的地址值+8后得到的地址
●栈地址值是向下增长的 , 即栈顶从高地址向低地址移动
准备工作
准备一段C代码:

int g(int x)
{
return x+5;
}
int f(int x)
{
return g(x);
}
int main(void)
{
return f(10)+1;
}
使用实验楼环境
算法 | 一段C语言和汇编的对应分析,揭示函数调用的本质
文章图片

编译成汇编代码
使用如下命令编译上面的c代码
gcc -S -o main.s main.c -m32
去掉不重要的部分后 , 得到:
汇编代码结果为:
g:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
popl %ebp
ret
f:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
movl %eax, (%esp)
call g
leave
ret
main:
pushl %ebp
movl %esp, %ebp
call f
leave
ret
分析
具体的逐步分析 , 这里就省了 , 老师课上讲的很详细了 , 这里主要是要进行思考和归纳 。
首先 , 我们看到3个C函数对应生成了3个部分的汇编代码 , 分别用函数名作为标号隔开了
int g(int x) -> g:
int f(int x) -> f:
int main(void) -> main:
我们知道程序是从main函数开始执行的 , 那么当程序被加载并运行时 , 上面的汇编代码会被加载到内存的某一个区域 。 而且 , CPU中的很多寄存器都会初始化 , 当然其中最重要的是eip , 因为eip是指向下一条将要执行的命令所在的内存地址 , 所以此时的eip应该指向main标号下的pushl %ebp:
main:
eip -> pushl %ebp
程序开始执行…
我们捆绑着看 , 首先先看这两条:
pushl %ebp
movl %esp, %ebp
再观察一下整个代码 , 有没有发现不仅仅是main函数 , 函数f和g的开头也是这两个指令 。 分析一下 , 不难得出 , 这两条指令是指将当前栈基地址压栈后 , 重新将基地址定位到栈顶 , 这个含义其实是保存好当前的基地址 , 重新开始一个新的栈 。 由于函数可以调函数 , 这里的当前基地址 , 实际上是上一个函数的栈基地址 。 例如 , 在f函数中的这两句指令 , 实际上保存的是main函数的栈基地址 。
接着来分析两句:

特别声明:本站内容均来自网友提供或互联网,仅供参考,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。