PowerPC 在处理器级别上是没有实现堆栈操作的,也就是说,在 PowerPC 架构中,没有专门的堆栈操作汇编命令。但事实上 PowerPC 又使用到了堆栈,比如在函数调用、传参、返回错误码等操作。
在讲 PowerPC 堆栈处理之前,先提一下要用到的某些寄存器。首先是32个通用寄存器 GPR0 ~ GPR31。据说这32个寄存器是64位的,但是高32位的访问方式不同,通常只用低32位,所以我们暂且把他们当作32位的寄存器来用好了。
按照 PowerPC EABI (Embedded Application Binary Interface) 的说法:GPR0 由于语言的不同而用处不同,gcc 使用 GPR0 来保存 LR 寄存器的值;GPR1 用于存放堆栈指针,通常是栈顶指针;GPR3 ~ GPR4 用于传参和保存返回值;GPR5 ~ GPR10 也用于传参;GPR13 保存 sdata 段的段基址;GPR14 ~ GPR31 是可供用户自由使用的。LR 寄存器保存返回地址。
操作堆栈,通常使用到两个类型的指令:{stw, stwu} 和 lwz。
stw rA, off(rB)
lwz rA, off(rB)
来看一简单的程序是怎样进行堆栈操作的:
//hello.c #include <stdio.h> int plus(int a, int b) { return (a + b); } int main() { int a = 11; int b = 22; plus(a, b); return 0; }
$ ppc_4xxFP-gcc -S hello.c
plus: stwu 1,-32(1) ;初始化新的堆栈,用于 plus() 函数,并把 main 中的 gpr1 压入栈顶,并得到新的栈顶 new_gpr1 stw 31,28(1) ;把 gpr31 压入栈中偏移为28这个位置 mr 31,1 stw 3,8(31) ;把 gpr3 中的参数 a 压入堆栈偏移为8的位置 stw 4,12(31) ;把 gpr4 中的参数 b 压入堆栈偏移为12的位置 lwz 9,8(31) ;从堆栈中取出参数 a,保存在 gpr9 中 lwz 0,12(31) ;从堆栈中取出参数 b,保存在 gpr0 中 add 0,9,0 ;执行 a + b 语句 mr 3,0 ;把 a+b 的结果保存到 gpr3 中,这是 plus() 函数的返回值 lwz 11,0(1) ;取出 gpr1 保存在 gpr11 中 lwz 31,-4(11) ;XXX mr 1,11 ;恢复 new_gpr1 为 main 中的 gpr1 blr ;返回 main main: stwu 1,-32(1) ;初始化堆栈,大小为32字节,现在 gpr1 更新为 (orig_gpr1-32),并把 orig_gpr1 压入栈顶 mflr 0 ;把 LR 保存到 gpr0 中 stw 31,28(1) ;把 orig_gpr31 压入栈中偏移为28这个位置 stw 0,36(1) ;把 LR 压入栈中偏移为36这个位置(超出了栈的空间) mr 31,1 ;把 gpr1 保存到 gpr31 li 0,11 ;赋值语句:a=11; stw 0,12(31) ;把 a 压入栈中偏移为12的位置 li 0,22 ;赋值语句:b=22; stw 0,8(31) ;把 a 压入栈中偏移为8的位置 lwz 3,12(31) ;把 a 出栈,存到 gpr3 中 lwz 4,8(31) ;把 b 出栈,存到 gpr4 中 bl plus ;调用 plus() 函数,CPU 自动设置 LR 寄存器 li 0,0 ;把立即数0加载到 gpr0 寄存器中 mr 3,0 ;把 gpr0 的值(0)存入到 gpr3 寄存器中,这是 main 函数的返回值 lwz 11,0(1) ;把 orig_gpr1 的值存入 grp11 中 lwz 0,4(11) ;把 36(1) 的值存入 gpr0 中 mtlr 0 ;恢复 LR 寄存器的值 lwz 31,-4(11) ;XXX mr 1,11 ;恢复 gpr1 的值为 orig_gpr1 blr ;main 函数返回
那两条 lwz 31,-4(11) 不知道有什么作用。只是需要注意 stw 和 lwz 并不更新堆栈指针的值,而 stwu 会更新堆栈指针的值。
因为 stw 需要指定数据存放在栈中的偏移,这就造成编译器在编译的过程中,某些变量的存储位置在栈中可能是随机的,所以想要在内联汇编中,像 push 和 pop 那样简单的使用 stw 和 lwz 来操作栈,可能会比较麻烦。
参考: