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

4.间接内存引用(指针反引用)需要将寄存器包含在括号中,example:movl(%eax),%ebx 将寄存器eax的值指向的内存地址中的长整型copy到ebx寄存器中。

5.offset(register)指定寄存器值与一个偏移量联用,将偏移量加到寄存器的实际值上。example: 8(%eax)指定将eax+8用作一个操作数。该表示法主要用于内存访问,例如指定与栈指针或帧指针的偏移量,以访问某些局部变量。

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

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

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

我们来分析一下 main.s 汇编代码: 

1.从main 主函数开始分析. 在IA-32系统中,ebp寄存器用于帧指针(栈顶),pushl  %ebp 将该ebp寄存器中的值压入系统栈上最低位置,这导致栈顶指针向下移动4byte,这是因为IA-32系统上需要4byte来表示一个指针(pushl中的后缀l,在AT&T汇编中表示一个long型)。

2.第3行,movl   %esp, %ebp  将esp(栈指针)寄存器 的值 copy到ebp(帧指针)寄存器中;把当前的栈指针作为本函数的帧指针。

3.第4行,subl $24,%esp 从栈指针减去0x18 byte,使得栈指针下移,将栈的空间增大了0x18=24byte;

调整栈指针,为局部变量保留空间。局部变量必须放置在栈上,在C代码中,a与b两个局部变量,两者都是整型变量,在内存中都需要4个byte。

因为栈的前4个byte保存了 帧指针的旧值(上一个活动记录),编译器将接下来的两个 4byte内存分配给了这两个局部变量。

ebp - 0xC 存着局部变量a的值 3    ; ebp - 0x8 存着局部变量b的值 4 (这里可以看到参数是从右到左 压入栈的)。

4.第5行 ,第6行 movl $0x3, -0xC(%ebp)    movl $0x4, -0x8(%ebp) : 为了向分配的内存空间设置初始值(对应C中 局部变量的初始化),编译器使用了处理器的指针反引用选项。 这两天指令通知编译器,引用“帧指针减12”得到的值 在内存中指向的位置。使用mov指令将值3 写入该位置。

编译器接下来用同样的方法处理第2个局部变量,其在栈的位置稍低,ebp - 0x8 (ebp - 8byte) 位置 ,值为4。

5.第7行,第8行设置第2个参数(b),第9行,第10行负责设置第1个参数(a)。     movl    -8(%ebp), %eax   ;   movl    %eax, 4(%esp)  ; movl    -12(%ebp), %eax;    movl    %eax, (%esp)

局部变量a和b必须用作即将调用的add过程调用的参数。编译器通过将适当的值放置在栈的末端来建立参数���表。

如前所述,第一个参数在最低部。栈指针用于查找栈的末尾。

内存中对应的位置通过指针反引用确定。将栈上的两个局部变量的值分别读入eax寄存器,然后将eax的值写入参数列表中对应的位置。(一般情况)

6.上图描述了 add()函数调用前后,栈的状态。现在可以使用call 指令调用add()函数。call指令 将eip(指令指针寄存器)压入栈,代码控制流在add例程的开始处恢复执行。

根据调用约定,例程首先将此前的帧指针(ebp)压入栈,并将栈指针(esp)赋值给 帧指针(ebp)。

过程的参数可以根据帧指针(ebp)查找。编译器知道参数就在调用函数的活动记录末尾,而在当前活动记录开始处又存储了两个4byte的值(返回地址,旧帧指针)。因此参数可以通过反引用ebp+8和ebp+12访问。

add 指令用于 加法,而eax寄存器用作工作空间。结果值就保存在该寄存器中,使它可以传递给调用函数(这里是main())。

为了返回到调用函数,需要执行以下两个操作: <a>使用pop将存储的帧指针(ebp)从栈弹出到ebp寄存器。栈帧的顶端重新恢复到main()的设置;<b>ret将返回地址从栈弹出到 eip(指令指针)寄存器,控制流转向该地址。

7.因为main()中还使用了另一个局部变量(ret)来存储add()函数的返回值,返回后需要将eax寄存器的值 copy 到ret在栈上的位置。

总结

关于AT&T汇编

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

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