Login or Sign up

ARM C 调用传参(Calling Conversation)

Posted by: tigerz on April 26, 2011

ARM C 调用传参(Calling Conversation)

了解 C 函数的调用传参方式, 是阅读汇编语言的一个很重要的基本知识,对分析 core dump 有直接的帮助。

这里通过比较 ARM EABI 和常用的 x86 C 调用传参方式的差别, 进一步理解 ARM C 的传参方式。

一个简单的 C 程序:

$ cat main.c
int a(int p1, int p2, int p3, int p4, int p5, int p6)
{
        return p1+p2+p3+p4+p5+p6;
}

int main(int argc, char *argv[])
{
        printf("p1=%d,p2=%d,p3=%d,p4=%d,p5=%d,p6=%d", 1, 2, 3, 4, 5, 6);
        return a(1,2,3,4,5,6);
}

x86

在 x86 平台上,所有的 C 参数按照最先一个参数最先入栈 (高地址) 的顺序全部压入栈。

$ objdump -d main_x86
...
080483c4 <a>:
 80483c4:       55                      push   %ebp
 80483c5:       89 e5                   mov    %esp,%ebp
 80483c7:       8b 45 0c                mov    0xc(%ebp),%eax
 80483ca:       8b 55 08                mov    0x8(%ebp),%edx
 80483cd:       8d 04 02                lea    (%edx,%eax,1),%eax
 80483d0:       03 45 10                add    0x10(%ebp),%eax
 80483d3:       03 45 14                add    0x14(%ebp),%eax
 80483d6:       03 45 18                add    0x18(%ebp),%eax
 80483d9:       03 45 1c                add    0x1c(%ebp),%eax
 80483dc:       5d                      pop    %ebp
 80483dd:       c3                      ret

080483de <main>:
 80483de:       55                      push   %ebp
 80483df:       89 e5                   mov    %esp,%ebp
 80483e1:       83 e4 f0                and    $0xfffffff0,%esp
 80483e4:       83 ec 20                sub    $0x20,%esp
 80483e7:       c7 44 24 18 06 00 00    movl   $0x6,0x18(%esp)
 80483ee:       00
 80483ef:       c7 44 24 14 05 00 00    movl   $0x5,0x14(%esp)
 80483f6:       00
 80483f7:       c7 44 24 10 04 00 00    movl   $0x4,0x10(%esp)
 80483fe:       00
 80483ff:       c7 44 24 0c 03 00 00    movl   $0x3,0xc(%esp)
 8048406:       00
 8048407:       c7 44 24 08 02 00 00    movl   $0x2,0x8(%esp)
 804840e:       00
 804840f:       c7 44 24 04 01 00 00    movl   $0x1,0x4(%esp)
 8048416:       00
 8048417:       c7 04 24 20 85 04 08    movl   $0x8048520,(%esp)
 804841e:       e8 d1 fe ff ff          call   80482f4 <printf@plt>
 8048423:       c7 44 24 14 06 00 00    movl   $0x6,0x14(%esp)
 804842a:       00
 804842b:       c7 44 24 10 05 00 00    movl   $0x5,0x10(%esp)
 8048432:       00
 8048433:       c7 44 24 0c 04 00 00    movl   $0x4,0xc(%esp)
 804843a:       00
 804843b:       c7 44 24 08 03 00 00    movl   $0x3,0x8(%esp)
 8048442:       00
 8048443:       c7 44 24 04 02 00 00    movl   $0x2,0x4(%esp)
 804844a:       00
 804844b:       c7 04 24 01 00 00 00    movl   $0x1,(%esp)
 8048452:       e8 6d ff ff ff          call   80483c4 <a>
 8048457:       c9                      leave
 8048458:       c3                      ret
 8048459:       90                      nop
...

ARM

要据 EABI 的规范,在 ARM 平台上,前4个参数由寄存器 r0-r3 传递,4个后的参数由栈传递。跟 x86 一样也是最后一个参数在栈的最高地址。

$ arm-none-linux-gnueabi-objdump -d main_arm_eabi
...
0000842c <a>:
    842c:       e52db004        push    {fp}            ; (str fp, [sp, #-4]!)
    8430:       e28db000        add     fp, sp, #0
    8434:       e24dd014        sub     sp, sp, #20
    8438:       e50b0008        str     r0, [fp, #-8]
    843c:       e50b100c        str     r1, [fp, #-12]
    8440:       e50b2010        str     r2, [fp, #-16]
    8444:       e50b3014        str     r3, [fp, #-20]  ; 0xffffffec
    8448:       e51b2008        ldr     r2, [fp, #-8]
    844c:       e51b300c        ldr     r3, [fp, #-12]
    8450:       e0822003        add     r2, r2, r3
    8454:       e51b3010        ldr     r3, [fp, #-16]
    8458:       e0822003        add     r2, r2, r3
    845c:       e51b3014        ldr     r3, [fp, #-20]  ; 0xffffffec
    8460:       e0822003        add     r2, r2, r3
    8464:       e59b3004        ldr     r3, [fp, #4]
    8468:       e0822003        add     r2, r2, r3
    846c:       e59b3008        ldr     r3, [fp, #8]
    8470:       e0823003        add     r3, r2, r3
    8474:       e1a00003        mov     r0, r3
    8478:       e28bd000        add     sp, fp, #0
    847c:       e49db004        pop     {fp}            ; (ldr fp, [sp], #4)
    8480:       e12fff1e        bx      lr

00008484 <main>:
    8484:       e92d4800        push    {fp, lr}
    8488:       e28db004        add     fp, sp, #4
    848c:       e24dd018        sub     sp, sp, #24
    8490:       e50b0008        str     r0, [fp, #-8]
    8494:       e50b100c        str     r1, [fp, #-12]
    8498:       e3a03004        mov     r3, #4
    849c:       e58d3000        str     r3, [sp]
    84a0:       e3a03005        mov     r3, #5
    84a4:       e58d3004        str     r3, [sp, #4]
    84a8:       e3a03006        mov     r3, #6
    84ac:       e58d3008        str     r3, [sp, #8]
    84b0:       e59f0040        ldr     r0, [pc, #64]   ; 84f8 <main+0x74>
    84b4:       e3a01001        mov     r1, #1
    84b8:       e3a02002        mov     r2, #2
    84bc:       e3a03003        mov     r3, #3
    84c0:       ebffffab        bl      8374 <_init+0x44>
    84c4:       e3a03005        mov     r3, #5
    84c8:       e58d3000        str     r3, [sp]
    84cc:       e3a03006        mov     r3, #6
    84d0:       e58d3004        str     r3, [sp, #4]
    84d4:       e3a00001        mov     r0, #1
    84d8:       e3a01002        mov     r1, #2
    84dc:       e3a02003        mov     r2, #3
    84e0:       e3a03004        mov     r3, #4
    84e4:       ebffffd0        bl      842c <a>
    84e8:       e1a03000        mov     r3, r0
    84ec:       e1a00003        mov     r0, r3
    84f0:       e24bd004        sub     sp, fp, #4
    84f4:       e8bd8800        pop     {fp, pc}
    84f8:       000085d0        .word   0x000085d0
...

ARM EABI 的这种调用传参方式在分析调用栈时相对比 x86 复杂。

在 x86 上,直接从保存的 sp 就可以看到传给当前函数的所有参数。

在 ARM 平台上,因为前4个参数由 r0-r3 传递,并且 r0-r3 会在当前函数中被直接使用,其值并不保存,这造成如果要分析当前函数收到的参数, 需要返回到被调用前的代码分析 r0-r3 的值,需要多读几段汇编代码。后面准备写个工作中碰到的实例进行分析。

Comments on This Post:

Please Login (or Sign Up) to leave a comment