AAPCS关于ARM寄存器的定义

AAPCS ARM 结构的一些标准做了定义,在这里我们只重点介绍函数调用部分,如图 8 所示, AAPCS ARM R0~R15 寄存器做了定义,明确了它们在函数中的职责:

图  8   AAPCS关于ARM寄存器的定义

函数调用时的规则如下:
1.  父函数与子函数间的入口参数依次通过R0~R34个寄存器传递 父函数在调用子函数前先将 参数 存入 R0~R3中,若只有一个参数则使用R0传递,2个则使用R0R1传递,依次类推,当超过4个参数时,其它参数通过栈传递 当子函数运行时,根据自身参数个数自动从R0~R3或者栈中读取参数。
2.  子函数通过R0寄存器将返回值传递给父函数 子函数返回时,将返回值存入R0,当返回到父函数时,父函数读取R0获得返回值。
3.  发生函数调用时,R0~R3是传递参数的寄存器,即使是父函数没有参数 需要 传递,子函数也可以任意更改R0~R3寄存器,无需考虑会破坏它们在父函数中保存的数值,返回父函数前无需恢复其值。AAPCS规定,发生函数调用 ,由父函数将R0~R3中有用的数据压栈,然后才能调用子函数,以防止父函数R0~R3中的 有用 数据被 子函数 破坏。
4.  R4~R11为普通的通用寄存器,若子函数需要使用这些寄存器 则需要将这些寄存器 压栈 然后再使用 ,以免破坏了这些寄存器中保存的父函数的数值,子函数返回父函数前需要 出栈恢复其数值,然后再返回父函数。AAPCS规定,发生函数调用时,父函数无需对这些寄存器进行压栈处理,若子函数需要使用这些寄存器,则由子函数负责压栈,以防止父函数R4~R11中的数据被破坏。
5.  编译器在编译时就确定了函数间的调用关系,它会使函数间的调用遵守34条规定 但编译器无法预知中断函数的调用,被中断的函数无法提前对R0~R3进行压栈处理,因此需要在中断函数里对它所使用的R 0 ~R11压栈。对于中断函数,不遵守第3条规定,遵守第5条规定。
6.  R12寄存器在某些版本的编译器下另有它用,用户程序不能使用,因此我们在编写汇编函数时也必须 对它进行压栈处理,确保 它的数值不能被破坏。
7.  R13寄存器是 堆栈 寄存器(SP), 用来保存堆栈的当前指针
8.  R14寄存器是链接寄存器(LR),用来保存函数的返回地址。
9.  R15寄存器是程序寄存器(PC), 指向程序当前的地址
上述只介绍了本手册中使用到的情形,具体的情况在编写操作系统代码时会涉及到,其它规则请请读者自行查找资料。

接下来我们再通过几个小例子熟悉一下C函数与汇编函数的调用过程。下面的C函数TestFunc1与汇编函数TestFunc2的功能是一样的。
U8 TestFunc1(void)
{
    U8 ucPara1;
    U8 ucPara2;
    U8 ucPara3;
    U8 ucPara4;
    U8 ucPara5;
    U8 ucPara6;

    ucPara1 = 1;
    ucPara2 = 2;
    ucPara3 = 3;
    ucPara4 = 4;
    ucPara5 = 5;
    ucPara6 = 6;

    return ucPara1 + ucPara2 + ucPara3 + ucPara4 + ucPara5 + ucPara6;
}


.func TestFunc2
TestFunc2:

    STMDB R13!, {R5 - R6, R10}     @R5R6R10寄存器压栈
    LDR R1, =1
    LDR R3, =2
    LDR R4, =3
    LDR R5, =4
    LDR R6, =5
    LDR R10, =6

    ADD R0, R1, R3
    ADD R0, R0, R4
    ADD R0, R0, R5
    ADD R0, R0, R6
    ADD R0, R0, R10

    LDMIA R13!, {R5 - R6, R10}     @R5R6R10寄存器出栈

    .endfunc
TestFunc2函数使用了R0R1R3R4R5R6R107个寄存器,遵循AAPCS规则, 在使用 R0R1R3 之前 并没有对 它们 压栈, R5R6R10寄存器进行了压栈保存,在函数返回前又出栈还原了这3个寄存器,这样TestFunc2函数 返回到它的父函数之后, R5R6R10寄存器的数值是没有改变的,而R0R1R3则分别被改写为了123

下面我们再来看看C函数TestFunc3调用汇编函数TestFunc4完成1+2的运算。
U8 TestFunc3(void)
{
    return TestFunc4
(1, 2);
}

    .func TestFunc4
TestFunc4:

    ADD R0, R0, R1
    BX R14;

    .endfunc
TestFunc3函数在调用TestFunc4函数前已经将参数12分别 存入 R0R1,并将返回地址存入 R14 ,然后才跳转到TestFunc4函数,发生函数调用。这时程序将运行TestFunc4函数,它将R0R1相加,将结果放入R0,需要通过R0 将返回值 返回给TestFunc3函数 此时R14中保存的就是返回TestFunc3函数的返回地址,最后TestFunc4函数跳转到R14就返回到了TestFunc3函数,TestFunc3函数从R0就可以取出TestFunc4函数计算的结果了。

下面我们再来看看汇编函数TestFunc5调用C函数TestFunc6完成1+2的运算。

.func TestFunc5
TestFunc5:

    MOV R0, #1
    MOV R1, #2
    SUB R13, R13, #4
    STR R14, [R13]
    BL TestFunc6
    LDR R14, [R13]
    ADD R13, R13, #4
    BX R14

    .endfunc

U8 TestFunc6(U8 ucPara1, U8 ucpara2)
{
    return ucPara1 + ucPara2;
}
TestFunc5函数先将参数12存入R0R1寄存器 ,准备调用 TestFunc 6函数并传递入口参数 ,然后将R14寄存器压栈,以防止使用BL指令时存入的R14返回地址破坏R14原有的数据,然后调用TestFunc6函数 在调用TestFunc6函数时BL指令会自动将“LDR R14, [R13]”这条指令的地址存入R14,这样就开始运行TestFunc6函数了。TestFunc6函数会自动从R0R1寄存器中取出参数,将计算结果存入R0,通过R0将返回值返回给TestFunc5函数。TestFunc6函数跳转回TestFunc5函数后 TestFunc5函数从栈中恢复原有的R14寄存器,完成函数调用,此时R0中的数值就是TestFunc6函数的计算结果。

当函数比较简单,不需要压栈仅使用寄存器便可以完成运算的时候,那么下面的TestFunc7函数,它的返回值是多少?
U8 *  TestFunc 7 ( void )
{

U8  ucPara1;

    ucPara1  = 1

    return & ucPara1 ;
}
按照上面的分析, 对于这个简单的函数,编译器是不会为局部变量ucPara1分配内存空间的,ucPara1只会保存在寄存器中,因此无从谈起它的地址。但这个这么简单的函数却偏偏要获取这个仅在寄存器中的局部变量 地址,遇到这种情况,编译器在编译时会特别为ucPara1专门在栈中分配内存,因此也就可以获取到它的地址了。
当然,这个函数没有任何意义,仅是举一个例子,而且写C语言时要避免发生这种情况,因为TestFunc7函数返回的是栈内局部变量的地址,当TestFunc7函数运行完后,ucPara1这个局部变量所在的栈空间已经被释放,这个栈空间很可能已经被其它变量占用,如果这时候还使用这个地址的话就可能会导致系统崩溃,新手要避免产生这个错误。
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值