zoukankan      html  css  js  c++  java
  • arm汇编进入C函数分析,C函数压栈,出栈,传参,返回值

    • 环境及代码介绍
      • 环境和源码

      由于有时候要透彻的理解C里面的一些细节问题,所有有必要看看汇编,首先这一切的开始就是从汇编代码进入C的main函数过程。这里不使用编译器自动生成的这部分汇编代码,因为编译器自动生成的代码会涉及环境变量的传递,参数的传递等等一系列问题。以ARM汇编来进行分析。使用一个启动汇编文件和一个main.c的文件,在ARM 2440板子上调试这段程序,使用JLinkExe借助jlink来调试:

    init.s:

    1 .text
    2 .global _start
    3 _start:
    4         ldr sp,=4096  @设置堆栈指针以便调用C函数
    5         bl main
    6 loop:
    7         b loop

    main.c:

    1 void main(void)
    2 {
    3 }

       为什么main函数没有使用 int main(int argc,char **argv) 这种形式?因为我这里是使用的自己写的启动汇编文件,由它来完成从汇编到C代码的进入。

      • 寄存器介绍  

      ARM在任何一种模式下,都可以访问16个通用寄存器(R0-R15)和1-2个状态寄存器(CPSR,SPSR),只是有些寄存器是每种模式下都共用的(R0-R7),另外一些是同名但是使用的是不同硬件单元(其他,每种模式下有所不同)。这里的寄存器有些有特定用途:

        R15--PC:程序计数器,指向要取指的那条指令

        R14--LR:链接寄存器,保存发生跳转时,下一条指令的地址,方便使用BL跳回

        R13--SP:堆栈指针

        R12--IP:暂存SP值

        R11--FP: 保存堆栈frame的地址

        后面的IP, FP可能需要结合实际代码来理解。

      另外,编译器在处理C程序的时候,R0通常用作传递返回值,R1-R4用来传递函数参数。

      稍微解释下这段汇编代码的 ldr sp,=4096 ,为什么设置为4096?有2个原因:

        1.我这里使用的是nand启动,代码在内部4K SRAM里面执行。

        2.ARM压栈时采用的是满递减堆栈。

      我觉得更准确的讲是由编译器决定的,其实ARM指令里面有各种类型的堆栈操作指令而不是单单的满递减。满递减就是指堆栈的增长方向向下,堆栈指针指向堆栈的顶端。如果是空递减,它会指向堆栈顶端的下一个地址,这个地址未存放有效堆栈数据。其实这里sp = 4096这个内存地址是无法访问的,4K最大的地址是4096-4,因此进行数据压栈时,要先调整堆栈指针,然后再压入数据,这也是所有满类型堆栈要遵循的原则。

      • 反汇编分析压栈出栈      

          使用 arm-linux-objdump -DS main.elf > dump 进行反汇编

     1 00000000 <_start>:
     2 .text
     3 .global _start
     4 _start:
     5         ldr sp,=4096
     6    0:    e3a0da01     mov    sp, #4096    ; 0x1000
     7         bl main
     8    4:    eb000000     bl    c <main>
     9 
    10 00000008 <loop>:
    11 loop:
    12         b loop
    13    8:    eafffffe     b    8 <loop>
    14 
    15 0000000c <main>:
    16 void main(void)
    17 {
    18    c:    e52db004     push    {fp}        ; (str fp, [sp, #-4]!)
    19   10:    e28db000     add    fp, sp, #0
    20 }
    21   14:    e28bd000     add    sp, fp, #0
    22   18:    e8bd0800     pop    {fp}
    23   1c:    e12fff1e     bx    lr

       可以看到进入C函数第一步就是压栈操作,出C函数里面出栈操作,然后跳转返回。关于push,pop  ARM官方的文档给出的说明:

        PUSH is a synonym for STMDB sp!, reglist and POP is a synonym for LDMIA sp! reglist. PUSH and POP are the preferred mnemonics in these cases.

      仅仅是个别名而已,并且是针对sp寄存器进行操作。

      由于我这里的main过于简单,所有并看不出说明名堂,在main中增加点东西:

    1 int main(void)
    2 {
    3     int a;
    4     a = 3;
    5 
    6     return 0;
    7 }

      继续反汇编,只关注main:

     1 0000000c <main>:
     2 int main(void)
     3 {
     4    c:    e52db004     push    {fp}        ; (str fp, [sp, #-4]!)
     5   10:    e28db000     add    fp, sp, #0
     6   14:    e24dd00c     sub    sp, sp, #12
     7     int a;
     8     a = 3;
     9   18:    e3a03003     mov    r3, #3
    10   1c:    e50b3008     str    r3, [fp, #-8]
    11 
    12     return 0;
    13   20:    e3a03000     mov    r3, #0
    14 }
    15   24:    e1a00003     mov    r0, r3
    16   28:    e28bd000     add    sp, fp, #0
    17   2c:    e8bd0800     pop    {fp}
    18   30:    e12fff1e     bx    lr

    可以看到有一对互为逆向操作的指令组合 push {fp}; add fp, sp, #0 <-------> add sp, fp, #0pop {fp},在这对组合指令之间的代码是不会去修改fp的值的,这样就实现了恢复调用前fp sp的值,而在它们之间的指令是通过修改sp来访问堆栈。但是这里有个问题,此处我仅定义了一个int型变量,为何堆栈向下偏移了12个字节?按道理sp-4即可。未找到原因,虽然对于堆栈,Procedure Call Standard for the ARM Architecture,要求遵守几个约定,比如堆栈指针必须是4字节对齐,此外,对于public interface即全局的接口,要求sp 8字节对齐。这里我的main算是个public interface,因此8字节对齐必须遵守,但是sp-4也是8字节对齐啊,搞不清为什么-12。增加局部变量可以很明细看出8字节对齐的约定。

      • 传参   
     1 int foo(int a, int b, int c, int d)
     2 {
     3     int A,B,C,D;
     4     A = a;
     5     B = b;
     6     C = c;
     7     D = d;
     8 
     9     return 0;
    10 }
    11 void main(void)
    12 {
    13     int a;    
    14     a = foo(1,2,3,4);
    15 }

      反汇编:

     1 00000000 <_start>:
     2 .text
     3 .global _start
     4 _start:
     5         ldr sp,=4096
     6    0:    e3a0da01     mov    sp, #4096    ; 0x1000
     7         bl main
     8    4:    eb000014     bl    5c <main>
     9 
    10 00000008 <loop>:
    11 loop:
    12         b loop
    13    8:    eafffffe     b    8 <loop>
    14 
    15 0000000c <foo>:
    16 int foo(int a, int b, int c, int d)
    17 {
    18    c:    e52db004     push    {fp}        ; (str fp, [sp, #-4]!)
    19   10:    e28db000     add    fp, sp, #0
    20   14:    e24dd024     sub    sp, sp, #36    ; 0x24
    21   18:    e50b0018     str    r0, [fp, #-24]
    22   1c:    e50b101c     str    r1, [fp, #-28]
    23   20:    e50b2020     str    r2, [fp, #-32]
    24   24:    e50b3024     str    r3, [fp, #-36]    ; 0x24
    25     int A,B,C,D;
    26     A = a;
    27   28:    e51b3018     ldr    r3, [fp, #-24]
    28   2c:    e50b3014     str    r3, [fp, #-20]
    29     B = b;
    30   30:    e51b301c     ldr    r3, [fp, #-28]
    31   34:    e50b3010     str    r3, [fp, #-16]
    32     C = c;
    33   38:    e51b3020     ldr    r3, [fp, #-32]
    34   3c:    e50b300c     str    r3, [fp, #-12]
    35     D = d;
    36   40:    e51b3024     ldr    r3, [fp, #-36]    ; 0x24
    37   44:    e50b3008     str    r3, [fp, #-8]
    38 
    39     return 0;
    40   48:    e3a03000     mov    r3, #0
    41 }
    42   4c:    e1a00003     mov    r0, r3
    43   50:    e28bd000     add    sp, fp, #0
    44   54:    e8bd0800     pop    {fp}
    45   58:    e12fff1e     bx    lr
    46 
    47 0000005c <main>:
    48 void main(void)
    49 {
    50   5c:    e92d4800     push    {fp, lr}
    51   60:    e28db004     add    fp, sp, #4
    52   64:    e24dd008     sub    sp, sp, #8
    53     int a;    
    54     a = foo(1,2,3,4);
    55   68:    e3a00001     mov    r0, #1
    56   6c:    e3a01002     mov    r1, #2
    57   70:    e3a02003     mov    r2, #3
    58   74:    e3a03004     mov    r3, #4
    59   78:    ebffffe3     bl    c <foo>
    60   7c:    e1a03000     mov    r3, r0
    61   80:    e50b3008     str    r3, [fp, #-8]
    62 }
    63   84:    e24bd004     sub    sp, fp, #4
    64   88:    e8bd4800     pop    {fp, lr}
    65   8c:    e12fff1e     bx    lr

       可以看到参数通过R0-R3寄存器传递过去,函数里面将寄存器值压栈,要用时从栈里面取出值即可。当寄存器不够用时,总共超过4个字长度,就会通过堆栈传递了:

    void main(void)
    {
      64:    e92d4800     push    {fp, lr}
      68:    e28db004     add    fp, sp, #4
      6c:    e24dd010     sub    sp, sp, #16
        int a;    
        a = foo(1,2,3,4,5);
      70:    e3a03005     mov    r3, #5
      74:    e58d3000     str    r3, [sp]  @通过堆栈传递多出来的参数
      78:    e3a00001     mov    r0, #1
      7c:    e3a01002     mov    r1, #2
      80:    e3a02003     mov    r2, #3
      84:    e3a03004     mov    r3, #4
      88:    ebffffdf     bl    c <foo>
      8c:    e1a03000     mov    r3, r0
      90:    e50b3008     str    r3, [fp, #-8]
    }
      94:    e24bd004     sub    sp, fp, #4
      98:    e8bd4800     pop    {fp, lr}
      9c:    e12fff1e     bx    lr

       返回值好像也是通过寄存器或者堆栈传递。

  • 相关阅读:
    vue中的 computed 和 watch 的区别
    mysql8.0 初始化数据库及表名大小写问题
    sql server alwayson 调整数据文件路径
    zabbix 自定义监控 SQL Server
    mysql 创建用户及授权
    mysql 设置从库只读模式
    mysql8.0 主从复制安装及配置
    centos8.0安装mysql8.0
    centos8替换阿里数据源
    npm publish 报错 【you or one of your dependencies are requesting a package version that is forbidden by your security policy】
  • 原文地址:https://www.cnblogs.com/thammer/p/5303379.html
Copyright © 2011-2022 走看看