PowerPC 堆栈

        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

stw rA, off(rB)

        stw 相当于 push,表示32位的 rA 寄存器的值,保存到 (rB 的值 + off) 所指的位置上。stwu 表示在 stw 的基础上,同时更新 rA 的值为 (rB 的值 + off)。

lwz

lwz rA, off(rB)

        lwz 相当于 pop,表示把地址为 (rB 的值 + off) 所指位置的值,保存到32位寄存器 rA 中。


来看一简单的程序是怎样进行堆栈操作的:

//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

汇编代码主要部分,从 main 开始执行:

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 来操作栈,可能会比较麻烦。


参考:

  • 《PowerPC Linux详解——核心篇》
  • AMCC PPC440 Processor User’s Manual
  • Developing PowerPC Embedded Application Binary Interface (EABI) Compliant Programs

你可能感兴趣的:(linux,汇编,gcc,application,interface,编译器)