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

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

  • 相关阅读:
    Python集成开发环境搭建
    Windows10 解决 “/”应用程序中的服务器错误
    Windows系统清除远程连接记录的方法
    C# 处理DateTime算法,取某月第1天及最后一天
    SQL语句
    3389远程连接爆破实战
    SQL Server常用SQL集合
    VS2013、VS2015中,新建项目没有看到解决方案的问题(已解决)
    我的黑苹果装机篇
    C# 使用GZip对字符串压缩和解压
  • 原文地址:https://www.cnblogs.com/thammer/p/5303379.html
Copyright © 2011-2022 走看看