第一部分 概述
应用程序二进制接口(ABI-Application Binary Interface)定义了一组在PowerPC系统软件上编译应用程序所需要遵循的一套规则。主要包括基本数据类型,通用寄存器的使用,参数的传递规则,以及堆栈的使用等等。ABI简单明了,但是比较难以理解的是堆栈帧的使用和维护。本文重点介绍PowerPC平台上堆栈帧的使用和维护,关于ABI其它方面的内容简单的介绍一下,O(∩_∩)O~
第二部分 详细内容
2.1 PowerPC构架使用的基本数据类型
PowerPC构架定义的基本数据类型的格式和标准C相同,具体格式如下:
2.2 PowerPC构架寄存器的使用规则
PowerPC的ABI规定的寄存器的使用规则如下:
GPR0:属于易失性寄存器,ABI规定普通用户不能使用此寄存器。GCC编译器用此寄存器来保存LR寄存器,Linux-PowerPC用此寄存器来传递系统调用号码。
GPR1:属于专用寄存器,ABI规定用次寄存器来保存堆栈的栈顶指针。
备注:PowerPC构架没有独立的栈顶指针,这一点和X86体系结构是不同的。
GPR2:属于专用寄存器,ABI规定普通用户不使用才寄存器,Linux PowerPC用此寄存器来保存当前进程的进程描述符地址。
GPR3至GPR4:属于易失性寄存器,ABI使用这两个寄存器来保存函数的返回值,或者用来传递参数。
GPR5值GPR10:也属于易失性寄存器,加上GPR3和GPR4共8个寄存器用来传递函数的参数。当函数的参数超过八个时使用堆栈来传递。
GPR11至GPR12:属于易失性寄存器,ABI规定普通用户不使用该寄存器,Linux PowerPC有时用这两个寄存器来存放临时变量,但是GCC编译器没有使用这两个寄存器
GPR13:属于专用寄存器,ABI规定该寄存器sdata段的基地址指针。Linux PowerPC在系统初始化时使用该寄存器来存放临时变量。GCC有时会根据某些规则将一些常用的数据放入sdata或者sbss段中。应用程序对sdata或者sbss段数据的访问与对data和bss段数据的访问机制不同,访问sdata段的数据速度更快。
GPR14至GPR31:属于非易失性寄存器。ABI使用这些寄存器来存放一些临时变量,在应用程序中可以自由使用这些变量。
总结成一张表格如下所示:
PowerPC寄存器没有专用的Pop,Push指令来执行堆栈操作,所以PowerPC构架使用存储器访问指令stwu,lwzu来代替Push和Pop指令。PowerPC处理器使用GPR1来将这个堆栈段构成一个单向链表,这个单链表的每一个数据成员,我们称之为堆栈帧(Stack Frame),每一个函数负责维护自己的堆栈帧。
PowerPC体系结构中栈的增长方向是从高地址到低地址,堆的增长方式是从低地址到搞地址,当两者相遇时就会产生溢出。
堆栈帧的格式如下:
备注:PowerPC构架规定栈帧的长度是8字节对齐的,栈帧的长度是8的倍数,如果不足8的整数倍,就如上图所示来补字节凑8的倍数。
解释:
函数参数域(Function Parameter Area):这个区域的大小是可选的,即如果如果调用函数传递给被调用函数的参数少于六个时,用GPR4至GPR10这个六个寄存器就可以了,被调用函数的栈帧中就不需要这个区域;但如果传递的参数多于六个时就需要这个区域。
局部变量域(Local Variables Area):通上所示,如果临时寄存器的数量不足以提供给被调用函数的临时变量使用时,就会使用这个域。
CR寄存器:即使修改了CR寄存器的某一个段CRx(x=0至7),都有保存这个CR寄存器的内容。
通用寄存器GPR:当需要保存GPR寄存器中的一个寄存器器GPRn时,就需要把从GPRn到GPR31的值都保存到堆栈帧中。
浮点寄存器FPR:使用规则共GPR寄存器。
下面我们通过几个例子来说明堆栈帧的建立和使用过程:
例子一:调用Funx()函数
FunX下中开始几行汇编会为自己建立堆栈帧:
FunX: mflr %r0 ;Get Link register
stwu %r1,-88(%r1) ;Save Back chain and move SP
stw %r0,+92(%r1) ;Save Link register
stmw %r28,+72(%r1) ;Save 4 non-volatiles r28-r31
FunX的结尾几行,会移除面建立的堆栈帧,并使得SP(即GPR1)寄存器指向上一个栈帧的栈顶(即栈帧的最低地址处,也就是back chair)
代码如下:
lwz %r0,+92(%r1) ;Get saved Link register
mtlr %r0 ;Restore Link register
lmw %r28,+72(%r1) ;Restore non-volatiles
addi %r1,%r1,88 ;Remove frame from stack
blr ;Return to calling function
例子二:
#include <stdio.h>
void fun()
{
}
int main()
{
fun();
return 0;
}
建立堆栈帧和移除堆栈帧的汇编语言:
100004f4 <fun>:
100004f4: 94 21 ff f0 stwu r1,-16(r1)
100004f8: 93 e1 00 0c stw r31,12(r1)
100004fc: 7c 3f 0b 78 mr r31,r1
10000500: 39 7f 00 10 addi r11,r31,16
10000504: 83 eb ff fc lwz r31,-4(r11)
10000508: 7d 61 5b 78 mr r1,r11
1000050c: 4e 80 00 20 blr
10000510 <main>:
10000510: 94 21 ff f0 stwu r1,-16(r1)
10000514: 7c 08 02 a6 mflr r0
10000518: 90 01 00 14 stw r0,20(r1)
1000051c: 93 e1 00 0c stw r31,12(r1)
10000520: 7c 3f 0b 78 mr r31,r1
10000524: 4b ff ff d1 bl 100004f4 <fun>
10000528: 38 00 00 00 li r0,0
1000052c: 7c 03 03 78 mr r3,r0
10000530: 39 7f 00 10 addi r11,r31,16
10000534: 80 0b 00 04 lwz r0,4(r11)
10000538: 7c 08 03 a6 mtlr r0
1000053c: 83 eb ff fc lwz r31,-4(r11)
10000540: 7d 61 5b 78 mr r1,r11
10000544: 4e 80 00 20 blr
10000548: 00 01 81 a0 .long 0x181a0
分析如下图所示:
备注:
当前函数(比如上面的fun)所维护堆栈帧中的backchain存放的是调用它的函数(即main函数)堆栈帧的栈顶地址。
另外,很多C程序中进行的汇编语言函数调用比较简单,例如汇编函数的参数个数一般少于八个,使用临时的通用寄存器GPR14至GPR31就足够了。所以在这种情况下,我们只要保持堆栈中的LR和Back Chain就足够用的了。
但是正如例子二所示:虽然只需要8个字节来保存LR和和Back Chain就行了,但是GCC编译器在建立堆栈帧时还额外分配了8个字节,其中的4个字节来保存r31的值,另外4个字节的空间并没有使用起来。
备注:例子二中所描述的帧结构是PowerPC构架中最小帧结构,这种帧结构的大小为16字节。
参考资料:
Developing PowerPC Embedded Application Binary Interface (EABI) Compliant Programs