arm平台函数传递参数,反汇编实例分析

简介:

测试前,需要了解下sysv的传参方式:
1、输入参数通过r0-r3传递,多余的放入堆栈中;返回值放入r0,不够的话放入{r0,r1}或者{r0,r1,r2,r3},比如:
int foo(int a, int b, int c, int d), 输入:r0 = a, r1 = b, r2 = c, r3 = d,返回:r0 = 类型为int的retvalue
int *foo(char a, double b, int c, char d), 输入:r0 = a, r1用于对齐(double 要求8字节对齐), b = {r2, r3},c放在堆栈的sp[0]位置,d放在堆栈的sp[4]位置,这里的sp是指进入函数时的sp;返回:r0 = 类型为int *的retvalue
2、注意如果返回值是结构体,情况有些特殊:
struct client foo(int a, char b, float c), 输入:r0 = 一个strcut client *变量,由调用者给出, r1 = a, r2 = b, r3 = c;返回:strcut client *变量,和调用者给的一样

 

为了测试arm平台函数参数如何对齐,多余参数如何传递,以及如何返回一个结构体类型的变量,编写如下代码:

#include <stdio.h>
#include <stdlib.h>

typedef struct _Foo{
    int a;
    char b;
    double c;
    float d;
}Foo;

Foo test(int a, char b, double c, float d)
{
    Foo *f = (Foo *)malloc(sizeof(Foo));
    f->a = a;
    f->b = b;
    f->c = c;
    f->d = d;
    return *f;
}

int main(void)
{
    Foo retvalue; 
    retvalue = test(1,2,3,4);
    return retvalue.a;
}
malloc会有内存溢出,这样写是为了反汇编更简单些,编译时不开优化,使用-marm参数指定使用arm指令集,然后反汇编得到:
00000000 <test>:
   0:   e92d4810    push    {r4, fp, lr}
   4:   e28db008    add fp, sp, #8  //fp = sp + 8
   8:   e24dd01c    sub sp, sp, #28 //sp = sp -28
   c:   e50b0018    str r0, [fp, #-24]  ; 0xffffffe8 //fp[-24] = r0 = Foo * temp
  10:   e50b101c    str r1, [fp, #-28]  ; 0xffffffe4 //fp[-28] = r1 = int a
  14:   e1a03002    mov r3, r2                       //fp[-29] = r2 = char b, r3 not used
  18:   e54b301d    strb    r3, [fp, #-29]  ; 0xffffffe3

  1c:   e3a00018    mov r0, #24
  20:   ebfffffe    bl  0 <malloc>          //malloc(24)
  24:   e1a03000    mov r3, r0              //r3 = f
  28:   e50b3010    str r3, [fp, #-16]      //fp[-16] = f
  2c:   e51b3010    ldr r3, [fp, #-16]      //r3 = f

  30:   e51b201c    ldr r2, [fp, #-28]  ; 0xffffffe4 //r2 = fp[-28] = int a
  34:   e5832000    str r2, [r3]                     //f->a = r2 = a
  38:   e51b3010    ldr r3, [fp, #-16]
  3c:   e55b201d    ldrb    r2, [fp, #-29]  ; 0xffffffe3 //r2 = fp[-29] = char b
  40:   e5c32004    strb    r2, [r3, #4]                 //f->b = r2 = b

  44:   e51b2010    ldr r2, [fp, #-16]                   //r2 = f
  48:   e99b0018    ldmib   fp, {r3, r4}                //double c = {r3, r4}
  4c:   e5823008    str r3, [r2, #8]                    //f[8] = r3
  50:   e582400c    str r4, [r2, #12]                   //f[12] = r4, f->c = c

  54:   e51b3010    ldr r3, [fp, #-16]                  //r3 = f
  58:   e59b200c    ldr r2, [fp, #12]                   //r2 = float d
  5c:   e5832010    str r2, [r3, #16]                   //f->d = float d

  60:   e51b2018    ldr r2, [fp, #-24]  ; 0xffffffe8    //r2 = r0 = Foo *temp
  64:   e51b3010    ldr r3, [fp, #-16]                  //r3 = f
  68:   e1a0c002    mov ip, r2                          //ip = r0 = Foo *temp
  6c:   e1a0e003    mov lr, r3                          //lr = f
  70:   e8be000f    ldm lr!, {r0, r1, r2, r3}           //拷贝f指向的前16个字节到Foo *temp指向的
  74:   e8ac000f    stmia   ip!, {r0, r1, r2, r3}
  78:   e89e0003    ldm lr, {r0, r1}                    //拷贝后面8个字节,加起来=24=sizeof(Foo)
  7c:   e88c0003    stm ip, {r0, r1}
  80:   e51b0018    ldr r0, [fp, #-24]  ; 0xffffffe8    //返回r0 = Foo *temp

  84:   e24bd008    sub sp, fp, #8
  88:   e8bd8810    pop {r4, fp, pc}

0000008c <main>:
  8c:   e92d4810        push    {r4, fp, lr}
  90:   e28db008        add     fp, sp, #8                  //fp = sp + 8
  94:   e24dd02c        sub     sp, sp, #44     ; 0x2c      //sp = sp - 44
  98:   e24b0024        sub     r0, fp, #36     ; 0x24      //r0 = fp - 36 = &retvalue = Foo *temp
  9c:   e59f3028        ldr     r3, [pc, #40]   ; cc <main+0x40>
  a0:   e58d3008        str     r3, [sp, #8]                //float d, 放入堆栈
  a4:   e3a03000        mov     r3, #0
  a8:   e59f4020        ldr     r4, [pc, #32]   ; d0 <main+0x44>
  ac:   e88d0018        stm     sp, {r3, r4}                //double c, 放入堆栈
  b0:   e3a02002        mov     r2, #2                      //b = 2
  b4:   e3a01001        mov     r1, #1                      //a = 1
  b8:   ebfffffe        bl      0 <test>
  bc:   e51b3024        ldr     r3, [fp, #-36]  ; 0xffffffdc //r3 = retvalue.a
  c0:   e1a00003        mov     r0, r3                      //r0 = r3 = retvalue.a,main返回值
  c4:   e24bd008        sub     sp, fp, #8
  c8:   e8bd8810        pop     {r4, fp, pc}
  cc:   40800000        .word   0x40800000
  d0:   40080000        .word   0x40080000

重点分析test函数,它的参数为:
r0: struct Foo *temp,通过main函数传递过来的,用于存放struct Foo结构体
r1: int a
r2: char b, 即使是char,也独立占一个寄存器,不与其他参数共用寄存器
r3: for alignment,下一个参数是double,要求对齐为8
c: sp[0-7],多余的参数放在堆栈上,这里是double c
d: sp[8-11],float d
test函数的堆栈结构为:

下面,逐行分析test的汇编代码,来验证上述内容。

1、进入test函数时,sp指向double c的低四字节,然后push {r4, fp, lr}之后,sp指向保存r4的位置: 

   0:   e92d4810    push    {r4, fp, lr}

2、fp=sp+8, sp=sp-28:

   4:   e28db008    add fp, sp, #8  //fp = sp + 8
   8:   e24dd01c    sub sp, sp, #28 //sp = sp -28

3、把struct Foo *temp,也就是r0,存到fp-24的位置上:

   c:   e50b0018    str r0, [fp, #-24]  ; 0xffffffe8 //fp[-24] = r0 = Foo *temp

4、把int a,也就是r1,存到fp-28的位置上:

  10:   e50b101c    str r1, [fp, #-28]  ; 0xffffffe4 //fp[-28] = r1 = int a 

5、把char b,也就是r2,存到fp-29的位置上,注意只放了一个字节,还剩下三个字节没有使用;注意r3没有有效值,只是为了对齐的,所以可以直接覆盖:

  14:   e1a03002    mov r3, r2                       //fp[-29] = r2 = char b, r3 not used
  18:   e54b301d    strb    r3, [fp, #-29]  ; 0xffffffe3

6、至此,输入参数已全部保存在堆栈上(double c, float d, 一开始就在堆栈高地址上)

7、调用malloc(24)函数,因为sizeof(Foo)=24,返回值r0赋值给r3,保存在fp-16位置,也就是变量Foo *f:

  1c:   e3a00018    mov r0, #24
  20:   ebfffffe    bl  0 <malloc>          //malloc(24)
  24:   e1a03000    mov r3, r0              //r3 = f
  28:   e50b3010    str r3, [fp, #-16]      //fp[-16] = f
  2c:   e51b3010    ldr r3, [fp, #-16]      //r3 = f

8、从fp-28取出int a,保存到f+0位置上,也就是f->a=a:

  30:   e51b201c    ldr r2, [fp, #-28]  ; 0xffffffe4 //r2 = fp[-28] = int a
  34:   e5832000    str r2, [r3]                     //f->a = r2 = a

9、从fp-29取出char b,保存到f+4位置上,也就是f->b=b:

  38:   e51b3010    ldr r3, [fp, #-16]
  3c:   e55b201d    ldrb    r2, [fp, #-29]  ; 0xffffffe3 //r2 = fp[-29] = char b
  40:   e5c32004    strb    r2, [r3, #4]                 //f->b = r2 = b

10、ldmib fp, {r3, r4},地址先增加4,然后取值保存到r3,地址再增加4,取值保存到r4,地址值不回写,也就是取出double c,放入r3,r4,然后保存到f+8地址和f+12地址上,也就是f->c=c:

  44:   e51b2010    ldr r2, [fp, #-16]                   //r2 = f
  48:   e99b0018    ldmib   fp, {r3, r4}                //double c = {r3, r4}
  4c:   e5823008    str r3, [r2, #8]                    //f[8] = r3
  50:   e582400c    str r4, [r2, #12]                   //f[12] = r4, f->c = c

11、取fp+12位置的float d,保存到f+16地址,也就是f->d=d:

  54:   e51b3010    ldr r3, [fp, #-16]                  //r3 = f
  58:   e59b200c    ldr r2, [fp, #12]                   //r2 = float d
  5c:   e5832010    str r2, [r3, #16]                   //f->d = float d

12、ip = Foo * temp = [fp - 24] = r0@entry,lr = f = [fp - 16],从f指向的地址取16字节,保存到ip指向的地址,然后再取8字节,保存到ip指向的地址,也就是按值拷贝f指向的结构体到Foo *temp = r0指向的结构体:

  60:   e51b2018    ldr r2, [fp, #-24]  ; 0xffffffe8    //r2 = r0 = Foo *temp
  64:   e51b3010    ldr r3, [fp, #-16]                  //r3 = f
  68:   e1a0c002    mov ip, r2                          //ip = r0 = Foo *temp
  6c:   e1a0e003    mov lr, r3                          //lr = f
  70:   e8be000f    ldm lr!, {r0, r1, r2, r3}           //拷贝f指向的前16个字节到r0 = Foo *temp指向的
  74:   e8ac000f    stmia   ip!, {r0, r1, r2, r3}
  78:   e89e0003    ldm lr, {r0, r1}                    //拷贝后面8个字节,加起来=24=sizeof(Foo)
  7c:   e88c0003    stm ip, {r0, r1}

13、设置返回值r0,为Foo *temp,返回:

  80:   e51b0018    ldr r0, [fp, #-24]  ; 0xffffffe8    //返回r0 = Foo *temp
  84:   e24bd008    sub sp, fp, #8
  88:   e8bd8810    pop {r4, fp, pc}

然后分析main函数

main的堆栈为:

这里的SP=SP-44也就是test函数堆栈的SP@entry

main的汇编代码为:

1、进入函数后,push{r4, fp, lr},sp指向存放r4的位置:

  8c:   e92d4810        push    {r4, fp, lr}
2、fp=sp+8, sp=sp-44:
  90:   e28db008        add     fp, sp, #8                  //fp = sp + 8
  94:   e24dd02c        sub     sp, sp, #44     ; 0x2c      //sp = sp - 44
3、为临时变量Foo retvalue申请内存,&retvalue为r0 = fp-36,也就是传递给test函数的那个Foo *temp: 
  98:   e24b0024        sub     r0, fp, #36     ; 0x24      //r0 = fp - 36 = &retvalue = Foo *temp
4、取fload d的输入值,放入sp+8位置: 
  9c:   e59f3028        ldr     r3, [pc, #40]   ; cc <main+0x40>
  a0:   e58d3008        str     r3, [sp, #8]                //float d, 放入堆栈

5、取double c的输入值,放入sp+0位置:
  a4:   e3a03000        mov     r3, #0
  a8:   e59f4020        ldr     r4, [pc, #32]   ; d0 <main+0x44>
  ac:   e88d0018        stm     sp, {r3, r4}                //double c, 放入堆栈
6、设置r1 = int a = 1, r2 = char b = 2,注意到r0 = Foo * temp = &retvalue,r3用于对齐,double c 和float d已放入堆栈sp0-sp12的位置上,调用test函数:
  b0:   e3a02002        mov     r2, #2                      //b = 2
  b4:   e3a01001        mov     r1, #1                      //a = 1
  b8:   ebfffffe        bl      0 <test>
7、取retvalue.a的值到r3,然后赋值给r0,为函数main的返回值:
  bc:   e51b3024        ldr     r3, [fp, #-36]  ; 0xffffffdc //r3 = retvalue.a
  c0:   e1a00003        mov     r0, r3                      //r0 = r3 = retvalue.a,main返回值

8、main函数返回: 
  c4:   e24bd008        sub     sp, fp, #8
  c8:   e8bd8810        pop     {r4, fp, pc}
  cc:   40800000        .word   0x40800000
  d0:   40080000        .word   0x40080000

 

目录
相关文章
|
22天前
|
数据采集 监控 安全
精简高效与安全兼备:ARM32与MCU32平台上的信息协议设计新思路
精简高效与安全兼备:ARM32与MCU32平台上的信息协议设计新思路
164 1
|
4月前
|
编译器 Linux C语言
函数栈帧的创建和销毁(以C语言代码为例,汇编代码的角度分析)(上)
函数栈帧的创建和销毁(以C语言代码为例,汇编代码的角度分析)
|
7月前
|
Linux C语言
RISC-V Linux汇编启动过程分析
RISC-V Linux汇编启动过程分析
|
24天前
|
安全 Linux 数据安全/隐私保护
【SPI协议】了解ARM平台上的SPI的基本应用
【SPI协议】了解ARM平台上的SPI的基本应用
373 0
|
1月前
|
存储 人工智能 达摩院
社区供稿 | FunASR 语音大模型在 Arm Neoverse 平台上的优化实践
Arm 架构的服务器通常具备低功耗的特性,能带来更优异的能效比。相比于传统的 x86 架构服务器,Arm 服务器在相同功耗下能够提供更高的性能。这对于大模型推理任务来说尤为重要,因为大模型通常需要大量的计算资源,而能效比高的 Arm 架构服务器可以提供更好的性能和效率。
|
4月前
|
编译器 C语言
函数栈帧的创建和销毁(以C语言代码为例,汇编代码的角度分析)(下)
函数栈帧的创建和销毁(以C语言代码为例,汇编代码的角度分析)
|
8月前
|
编译器 vr&ar C语言
如何保护自己知识产权,建立代码护城河——建立自己的静态库,x86和arm平台的实例讲解
如何保护自己知识产权,建立代码护城河——建立自己的静态库,x86和arm平台的实例讲解
258 0
|
8月前
|
C语言 C++
汇编的初体验+debug加法分析【微机原理】
汇编的初体验+debug加法分析【微机原理】
70 1
|
11月前
|
关系型数据库 MySQL

热门文章

最新文章