从汇编角度分析C语言的过程调用

1.系统栈(system stack)是一个内存区,位于进程地址空间的末端。

2.在将数据压栈时,栈是自顶向下增长的,该内存区用于函数的局部变量提供内存。它也支持在调用函数时传递参数。

3.如果调用了嵌套的过程,栈会自上而下增长,并接受新的活动记录(activation record)来保存一个过程所需的所有数据。

4.当前执行过程的活动记录,由标记顶部位置的帧指针(frame point)和标记底部位置的栈指针(stack point)定义。

5.在过程执行时,虽然其顶部的限制是固定的,但底部的限制是可以扩展的(在需要更多内存空间时)。

分析栈帧(分析如下)

从汇编角度分析C语言的过程调用

上图第2个栈帧的分析如下: 

1、在栈帧顶部是返回地址,以及保存的旧的帧指针。返回地址指定了当前过程结束时代码的控制流转向的内存地址,而保存的旧的帧指针则是前一个活动记录的帧指针。在当前过程结束后,该帧指针的值可用于重建调用过程的栈帧,在试图调试调用栈回溯时,这一点很重要。

2、活动记录的主要部分是为过程调用局部变量分配的内存空间。在C中,这种变量也称为自动变量(automatic variable)。

3、在函数调用时,以参数形式传递到函数的值,存储在栈的底部。

4、所有常见的计算机体系结构都提供了以下两个栈操作指令:

push指令将一个值放置在栈上,并将栈指针esp减去该值所占用的内存字节数。栈的末端下移到更低的地址;

pop指令从栈中弹出一个值,并相应增加栈指针esp的值,也就是说,栈的末端上移了。

5、一般体系结构另外提供两个指令,用于调用和退出函数(自动返回到调用过程),它们也会自动操作栈:

call指令将指令指针的当前值压栈,跳转到被调用函数的起始地址。  call 指令 :在AT&T汇编中,call foo(foo是一个标号)等效于以下汇编指令: pushl %eip ,movl f, %eip  ;  

return指令从栈上弹出返回地址,并跳转到该地址。过程的实现必须将rerurn作为最后一条指令,由call放置在栈上的返回地址位于栈的底部(实际上是上一个活动记录的底部,当前活动记录的顶部)。  ret指令: 在AT&T汇编中,ret等效于以下汇编指令: popl %eip

过程调用两个组成步骤

1、在栈中建立参数列表。传递到被调用函数的第一个参数最后入栈(从右到左)。这使得C中可以传递可变数目的参数,然后将其从栈上逐一弹出(pop)。

2、调用call,这将指令指针的当前值(call之后的下一条指令)压栈,代码的控制流转向被调用的函数。被调用的过程负责管理帧指针ebp,需要执行下列步骤: 

前一个帧指针压栈,因而栈指针下移。

将栈指针的当前值copy给帧指针,标记当前执行函数的栈区的起始位置。

执行当前函数的代码。

在函数结束时,存储的旧帧指针位于栈的底部。其值从栈弹出到帧指针寄存器(ebp),使之指向前一个函数的栈区起始位置。现在,对当前函数执行call指令时压栈的返回地址位于栈低。

调用return,将返回地址从栈弹出。cpu转移到返回地址,代码的控制流也返回到调用函数。

具体C 语言例子分析

初看起来,这种方法似乎有些混乱,因此,我们先看一个简单的C语言例子:

从汇编角度分析C语言的过程调用

在IA-32系统上,汇编代码本身必须是AT&T表示法给出。 

AT&T汇编语法总结为以下5条规则,就足够了。 

1.寄存器通过在名称前加百分号(%)前缀引用。example:为使用eax寄存器,汇编代码中将使用%eax。(如果在C中内联汇编的话,C代码必须指定两个百分号,才能在转给汇编器的输出中形成一个百分号)。

2.源寄存器总是在目的寄存器之前指定。 example,在mov语句中,这意味着 mov a,b 将 寄存器a中的值 内容copy到寄存器b中。

3.操作数的长度由汇编语句的后缀指定。b代步byte,w代表word,l代表long。在IA-32上,将一个长整型从eax寄存器移动到ebx寄存器中,需要指定movl %eax,%ebx。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/380b5ec3faddd7d05fb7f819bf98ca95.html