如何在ARM汇编函数中访问4个以上的参数?

Tom*_*Tom 5 assembly arm

在我的汇编函数中,有6个参数.当我尝试访问第四个和第五个参数时,它们是错误的,这是我在arm cortex-8a上的代码

push {r4-r8,lr}
ldr r6, [sp]
ldr r7, [sp, #4]
Run Code Online (Sandbox Code Playgroud)

我检查了[sp]内存,r4-r8有错误的值.但是,如果有3个或更少的参数,[sp]给出正确的r4-r8值.我错过了什么吗?

old*_*mer 9

为什么不试试呢?

unsigned int fun ( unsigned int, unsigned int, unsigned int, unsigned int, unsigned int );
unsigned int myfun ( void )
{
return(fun(1,2,3,4,5));
}
Run Code Online (Sandbox Code Playgroud)

组装然后拆卸

> arm-none-eabi-gcc -O2 -c fun.c -o fun.o
> arm-none-eabi-objdump -D fun.o 
Run Code Online (Sandbox Code Playgroud)

程序集输出包含

00000000 <myfun>:
   0:   e52de004    push    {lr}        ; (str lr, [sp, #-4]!)
   4:   e3a03005    mov r3, #5
   8:   e24dd00c    sub sp, sp, #12
   c:   e58d3000    str r3, [sp]
  10:   e3a01002    mov r1, #2
  14:   e3a02003    mov r2, #3
  18:   e3a03004    mov r3, #4
  1c:   e3a00001    mov r0, #1
  20:   ebfffffe    bl  0 <fun>
  24:   e28dd00c    add sp, sp, #12
  28:   e49de004    pop {lr}        ; (ldr lr, [sp], #4)
  2c:   e12fff1e    bx  lr
Run Code Online (Sandbox Code Playgroud)

前四个操作数按预期位于寄存器r0-r3中.第五个操作数虽然放在堆栈上.为什么编译器为操作数分配12个字节而不是4个字节,这是一个谜......也许看到函数会更有意义:

unsigned int fun ( unsigned int a, unsigned int b, unsigned int c, unsigned int d, unsigned int e )
{
    return(a+b+c+d-e);
}
Run Code Online (Sandbox Code Playgroud)

装配和拆卸

arm-none-eabi-gcc -O2 -c fun.c -o fun.o
arm-none-eabi-objdump -D fun.o 

00000000 <fun>:
   0:   e0811000    add r1, r1, r0
   4:   e0812002    add r2, r1, r2
   8:   e59d0000    ldr r0, [sp]
   c:   e0823003    add r3, r2, r3
  10:   e0600003    rsb r0, r0, r3
  14:   e12fff1e    bx  lr
Run Code Online (Sandbox Code Playgroud)

所以被调用者只知道操作数是堆栈中的第一件事,并不关心调用者创建的堆栈帧.因此,在这种情况下,为什么调用者分配了12个字节而不是4个字节,这是一个谜.

arm-none-eabi-gcc --version
arm-none-eabi-gcc (GCC) 4.7.2
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Run Code Online (Sandbox Code Playgroud)

看到编译器实际实现调用约定可以使得调用约定本身的读取更容易理解.或者,如果您为特定的函数原型编写这样的示例,您感兴趣的是您感兴趣的编译器,您不必阅读约定,只需让您的调用者或被调用者感兴趣,匹配编译器正在执行的操作为了自己.


Igo*_*sky 7

额外的参数在堆栈上传递,但是,在函数的入口处SP指向它们.在你的序言中,你正在推动寄存器被保存,这会改变,所以你需要考虑它.SP

r4,r5,r6,r7,r8和lr是6个寄存器,因此需要将SP偏移量调整为6*4 = 24个字节.所以,请尝试以下方法:

push {r4-r8,lr}        // 6 regs are pushed
                       // SP is decremented by 6*4 = 24 bytes
ldr r6, [sp, #(0+24)]  // get first stack arg
ldr r7, [sp, #(4+24)]  // get second stack arg
Run Code Online (Sandbox Code Playgroud)

如果你做更多的操作SP,例如为堆栈变量分配空间,你可能也必须考虑到这一点.


Cir*_*四事件 5

AAPCS标准

http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042f/IHI0042F_aapcs.pdf 5.5 参数传递包含答案。

它并不是很容易理解,因为它是一种算法,但回答问题的关键部分似乎是:

C.5 如果 NCRN 小于 r4 并且 NSAA 等于 SP,则参数在核心寄存器和堆栈之间分配。参数的第一部分被复制到从 NCRN 开始一直到 r3 的核心寄存器中。参数的其余部分被复制到堆栈中,从 NSAA 开始。NCRN 设置为 r4,NSAA 的增量为参数大小减去寄存器中传递的数量。该参数现已分配。

正如其他人所说,这意味着 4 个寄存器 + 堆栈。