02 - 汇编看函数

2021-03-25  本文已影响0人  卡布奇诺_95d2

栈区(SP & FP 寄存器)

函数调用栈

常见的函数调用开辟和恢复栈空间的汇编如下:

sub    sp, sp, #0x40             ; 拉伸0x40(64字节)空间,栈区是由高往低开辟空间
stp    x29, x30, [sp, #0x30]     ; x29、x30 寄存器入栈保护
add    x29, sp, #0x30            ; x29指向栈帧的底部
... 
ldp    x29, x30, [sp, #0x30]     ; 恢复x29、x30 寄存器的值
add    sp, sp, #0x40             ; 栈平衡
ret

上述代码可以进行部分简写:

//将sub、stp进行合并
sub    sp, sp, #0x40             ; 拉伸0x40(64字节)空间,栈区是由高往低开辟空间
stp    x29, x30, [sp, #0x40]     ; x29、x30 寄存器入栈保护
//合并之后
stp    x29, x30, [sp, #-0x40]!     

//将add、ldp进行合并
ldp    x29, x30, [sp, #0x40]     ; 恢复x29、x30 寄存器的值
add    sp, sp, #0x40             ; 栈平衡
//合并之后
ldp    x29, x30, [sp], #0x40

内存读写指令

在详细描述SP寄存器和FP寄存器之前,先来了解一下内存的读写指令。

栈区操作详解

假设有两个数,a=10,b=20,现在希望通过使用汇编使其完成两个数的交换,即结果:a=20,b=10。那要如何实现呢?
【第一步】分别将10和20存入x0、x1寄存器

mov x0, #0x0a
mov x1, #0x14

此时通过lldb断点调试读取寄存器x0、x1的内容。

(lldb) register read x0
      x0 = 0x000000000000000a
(lldb) register read x1
      x1 = 0x0000000000000014

【第二步】拉取32字节的栈空间

sub sp, sp, #0x20

在执行sub执行前后,分别查看sp的内容

//执行前
(lldb) register read sp
      sp = 0x000000016b6c5b90
      
//执行 sub sp, sp, #0x20 后
(lldb) register read sp
      sp = 0x000000016b6c5b70

【第三步】将x0、x1寄存器的内容存储至sp所指向的内存中

stp x0, x1, [sp]

在执行stp执行前后,分别查看内存中的内容

//执行前,此时内存中的数据为垃圾数据
(lldb) x/4g 0x000000016b6c5b70
0x16b6c5b70: 0x0000000104c08080 0x00000001e6cd5f08
0x16b6c5b80: 0x000000016b6c5bb0 0x000000010473df5c

//执行 stp x0, x1, [sp] 后
(lldb) x/4g 0x000000016b6c5b70
0x16b6c5b70: 0x000000000000000a 0x0000000000000014
0x16b6c5b80: 0x000000016b6c5bb0 0x000000010473df5c

【第四步】为了实现交换两个寄存器内容的功能,则根据存入内存的顺序反向读出来即可

ldp x1, x0, [sp]

在执行ldp执行前后,分别查看x0、x1寄存器的内容

(lldb) register read x0
      x0 = 0x0000000000000014
(lldb) register read x1
      x1 = 0x000000000000000a

【第五步】栈操作使用完成之后,需要恢复栈平衡

add sp, sp, #0x20

在执行add后,再查看sp的内容

(lldb) register read sp
      sp = 0x000000016b6c5b90

此时sp已经恢复至操作之前的位置

bl指令和ret指令

bl指令格式:bl 标号

ret指令格式:ret
默认使用LR(X30)寄存器的值,通过底层指令提示CPU,LR(X30)寄存器的中值为下条指令地址。

LR(X30)寄存器

X30寄存器存放的是函数的返回地址.当ret指令执行时刻,会寻找X30寄存器保存的地址值。
接下来通过分析一段代码来看一下当函数进行嵌套调用时,LR寄存器的内容变化,以及函数执行的情况?

//此处是调用汇编中定义的A函数
- (void)viewDidLoad {
    [super viewDidLoad];
    A();
}
//假设汇编代码如下:
_A:
    mov x0,#0xa0
    bl _B
    mov x0,#0xb0

_B:
    mov x0,#0xc0
    ret

【第一步】当进入汇编A函数时,lldb断点调试

Demo`A:
->  0x102611ec4 <+0>:  mov    x0, #0xa0
    0x102611ec8 <+4>:  bl     0x102611ed4               ; B
    0x102611ecc <+8>:  mov    x0, #0xb0
    0x102611ed0 <+12>: ret    
    
//此时读取LR寄存器的内容,LR寄存器中存储的是 viewDidLoad 中A后面一条指令的地址
(lldb) register read lr
      lr = 0x0000000102611f60  Demo`-[ViewController viewDidLoad] + 72 at ViewController.m:32:1

【第二步】当从汇编A函数中,跳转至B中时

Demo`B:
->  0x102611ed4 <+0>: mov    x0, #0xc0
    0x102611ed8 <+4>: ret      
    
//此时读取LR寄存器的内容,LR寄存器中存储的是 A 函数中bl _B后面一条指令的地址(0x102611ecc <+8>:  mov    x0, #0xb0)
(lldb) register read lr
      lr = 0x0000000102611ecc  Demo`A + 8

【第三步】从汇编的B函数返回时

Demo`A:
    0x102611ec4 <+0>:  mov    x0, #0xa0
    0x102611ec8 <+4>:  bl     0x102611ed4               ; B
->  0x102611ecc <+8>:  mov    x0, #0xb0
    0x102611ed0 <+12>: ret    

//从_B返回时,LR寄存器保存了下一条需要执行指令的地址,因此回到_A中的 0x102611ecc 地址处,但此处读取LR寄存器,发现其中仍然保存了 0x102611ecc 这个地址
(lldb) register read lr
      lr = 0x0000000102611ecc  Demo`A + 8

【第四步】当从汇编A函数返回时,此时程序运行情况和LR寄存器内容如下

Demo`A:
    0x102611ec4 <+0>:  mov    x0, #0xa0
    0x102611ec8 <+4>:  bl     0x102611ed4               ; B
->  0x102611ecc <+8>:  mov    x0, #0xb0
    0x102611ed0 <+12>: ret    

(lldb) register read lr
      lr = 0x0000000102611ecc  Demo`A + 8
//此时出现了奇怪的现象,本来当A执行完成时,需要从A返回至viewDidLoad,但单步调试发现,当A函数的ret执行完之后,回到了 0x102611ecc 地址处执行。并陷入(0x102611ecc->ret->0x102611ecc)的循环。

注意:如果存在函数嵌套调用,则需要对X30进行保护(如何保护将在后续章节中详解),否则会在ret时,会因为无法找到正确的函数返回地址而进入死循环。

LR(X30)寄存器的保护

上面讲到,若存在函数嵌套调用时,需要将LR(X30)寄存器保护起来,否则会因为LR(X30)寄存器的值被修改而陷入死循环。那如何对LR(X30)寄存器进行保护呢?

答:在函数跳转至其它函数之前,将LR(X30)寄存器保存到当前函数的栈区。从其它函数返回的后,再从当前函数的栈区取出保存的LR(X30)寄存器的值。

_A:
    sub sp, sp, #0x10   ;在函数调用栈中拉取16字节空间
    str x30, [sp]       ;将LR寄存器(x30)的值存储至SP寄存器,进行入栈保护
    mov x0,#0xa0        
    bl _B
    ldr x30,[sp]        ;恢复LR寄存器(x30)的值
    mov x0,#0xb0        
    add sp, sp, #0x10   ;函数栈平衡
    ret

函数的参数和返回值

此时的汇编如下:

    Demo`main:
        //拉取32字节内存空间
        0x100746250 <+0>:  sub    sp, sp, #0x20             ; =0x20 
        //对x29、x30进行保护,将其存入函数栈中
        0x100746254 <+4>:  stp    x29, x30, [sp, #0x10]
        //将x29指向sp+0x10的位置
        0x100746258 <+8>:  add    x29, sp, #0x10            ; =0x10 
        //将x29偏移-0x4,然后存入w0,此时的w0是main函数的第一个参数argc
        0x10074625c <+12>: stur   w0, [x29, #-0x4]
        //将x1存入sp所指向的位置,此时x1是main函数的第二个参数argv
        0x100746260 <+16>: str    x1, [sp]
        //将w0赋值为0x1
        0x100746264 <+20>: mov    w0, #0x1
        //将w1赋值为0x2
        0x100746268 <+24>: mov    w1, #0x2
        //将w2赋值为0x3
        0x10074626c <+28>: mov    w2, #0x3
        //将w3赋值为0x4
        0x100746270 <+32>: mov    w3, #0x4
        //先将 0x100746278 地址保存至LR寄存器,再跳转至 0x1044ce1ec 地址,即testSum
    ->  0x100746274 <+36>: bl     0x1044ce1ec               ; testSum at main.m:11
        //将w8赋值为0x0
        0x100746278 <+40>: mov    w8, #0x0
        //将x0赋值为0x8,此处x0为main函数的返回值,即返回0
        0x10074627c <+44>: mov    x0, x8
        //恢复x29, x30寄存器的值
        0x100746280 <+48>: ldp    x29, x30, [sp, #0x10]
        //恢复栈平衡
        0x100746284 <+52>: add    sp, sp, #0x20             ; =0x20 
        //函数返回
        0x100746288 <+56>: ret    
    
    Demo`testSum:
        //拉取32字节内存空间
    ->  0x1044ce1ec <+0>:  sub    sp, sp, #0x20             ; =0x20 
        //将sp偏移0x1c,然后存入w0
        0x1044ce1f0 <+4>:  str    w0, [sp, #0x1c]
        //将sp偏移0x18,然后存入w1
        0x1044ce1f4 <+8>:  str    w1, [sp, #0x18]
        //将sp偏移0x14,然后存入w2
        0x1044ce1f8 <+12>: str    w2, [sp, #0x14]
        //将sp偏移0x10,然后存入w3
        0x1044ce1fc <+16>: str    w3, [sp, #0x10]
        //将sp偏移0x1c,然后将内存中的值读取到w8,此时w8=1
        0x1044ce200 <+20>: ldr    w8, [sp, #0x1c]
        //将sp偏移0x18,然后将内存中的值读取到w9,此时w9=2
        0x1044ce204 <+24>: ldr    w9, [sp, #0x18]
        //将w8与w9相加,并将结果存入w8,此时w8=1+2=3
        0x1044ce208 <+28>: add    w8, w8, w9
        //将sp偏移0x14,然后将内存中的值读取到w9,此时w9=3
        0x1044ce20c <+32>: ldr    w9, [sp, #0x14]
        //将w8与w9相加,并将结果存入w8,此时w8=3+3=6
        0x1044ce210 <+36>: add    w8, w8, w9
        //将sp偏移0x10,然后将内存中的值读取到w9,此时w9=4
        0x1044ce214 <+40>: ldr    w9, [sp, #0x10]
        //将w8与w9相加,并将结果存入w8,此时w8=6+4=10
        0x1044ce218 <+44>: add    w8, w8, w9
        //将sp偏移0xc,然后存入w8,由于代码中没有返回值,因此此处也只是将结果存在函数调用栈而已
        0x1044ce21c <+48>: str    w8, [sp, #0xc]
        //函数执行完成,恢复栈平衡
        0x1044ce220 <+52>: add    sp, sp, #0x20             ; =0x20 
        //返回
        0x1044ce224 <+56>: ret   

【情况2】当存在8个以上参数时,寄存器的存储情况

    int testSum(int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8){
        return a0+a1+a2+a3+a4+a5+a6+a7+a8;
    }
    
    int main(int argc, char * argv[]) {
        int a = testSum(1, 2, 3, 4, 5, 6, 7, 8, 9);
        return 0;
    }

汇编如下(下面代码中与前面类似代码不做详解):

    Demo`main:
        0x104e4a230 <+0>:  sub    sp, sp, #0x30             ; =0x30 
        0x104e4a234 <+4>:  stp    x29, x30, [sp, #0x20]
        0x104e4a238 <+8>:  add    x29, sp, #0x20            ; =0x20 
        0x104e4a23c <+12>: stur   w0, [x29, #-0x4]
        0x104e4a240 <+16>: str    x1, [sp, #0x10]
        //在此之前,是拉取栈空间,将main函数的参数以及x29, x30存入栈中
        0x104e4a244 <+20>: mov    w0, #0x1
        0x104e4a248 <+24>: mov    w1, #0x2
        0x104e4a24c <+28>: mov    w2, #0x3
        0x104e4a250 <+32>: mov    w3, #0x4
        0x104e4a254 <+36>: mov    w4, #0x5
        0x104e4a258 <+40>: mov    w5, #0x6
        0x104e4a25c <+44>: mov    w6, #0x7
        0x104e4a260 <+48>: mov    w7, #0x8
        //在此之前,对w0-w7进行赋值
        //将sp赋值给x8
    ->  0x104e4a264 <+52>: mov    x8, sp
        //对w9进行赋值
        0x104e4a268 <+56>: mov    w9, #0x9
        //将w9存入x8指向的位置,此时,将w9存入了函数栈中
        0x104e4a26c <+60>: str    w9, [x8]
        //将 0x104e4a274 地址保存至LR寄存器,并跳转至 0x104e4a1b8 这个位置执行
        0x104e4a270 <+64>: bl     0x104e4a1b8               ; testSum at main.m:11
        0x104e4a274 <+68>: str    w0, [sp, #0xc]
        0x104e4a278 <+72>: mov    w9, #0x0
        0x104e4a27c <+76>: mov    x0, x9
        0x104e4a280 <+80>: ldp    x29, x30, [sp, #0x20]
        0x104e4a284 <+84>: add    sp, sp, #0x30             ; =0x30 
        0x104e4a288 <+88>: ret    
        
    Demo`testSum:
        //拉取48字节内存空间
        0x104e4a1b8 <+0>:   sub    sp, sp, #0x30             ; =0x30 
        //注意:此时是将sp+0x30处的值读取到w8。sp+0x30是前一个函数的栈空间,对于本案例,sp+0x30是main函数的栈空间
    ->  0x104e4a1bc <+4>:   ldr    w8, [sp, #0x30]
        0x104e4a1c0 <+8>:   str    w0, [sp, #0x2c]
        0x104e4a1c4 <+12>:  str    w1, [sp, #0x28]
        0x104e4a1c8 <+16>:  str    w2, [sp, #0x24]
        0x104e4a1cc <+20>:  str    w3, [sp, #0x20]
        0x104e4a1d0 <+24>:  str    w4, [sp, #0x1c]
        0x104e4a1d4 <+28>:  str    w5, [sp, #0x18]
        0x104e4a1d8 <+32>:  str    w6, [sp, #0x14]
        0x104e4a1dc <+36>:  str    w7, [sp, #0x10]
        0x104e4a1e0 <+40>:  str    w8, [sp, #0xc]
        0x104e4a1e4 <+44>:  ldr    w8, [sp, #0x2c]
        0x104e4a1e8 <+48>:  ldr    w9, [sp, #0x28]
        0x104e4a1ec <+52>:  add    w8, w8, w9
        0x104e4a1f0 <+56>:  ldr    w9, [sp, #0x24]
        0x104e4a1f4 <+60>:  add    w8, w8, w9
        0x104e4a1f8 <+64>:  ldr    w9, [sp, #0x20]
        0x104e4a1fc <+68>:  add    w8, w8, w9
        0x104e4a200 <+72>:  ldr    w9, [sp, #0x1c]
        0x104e4a204 <+76>:  add    w8, w8, w9
        0x104e4a208 <+80>:  ldr    w9, [sp, #0x18]
        0x104e4a20c <+84>:  add    w8, w8, w9
        0x104e4a210 <+88>:  ldr    w9, [sp, #0x14]
        0x104e4a214 <+92>:  add    w8, w8, w9
        0x104e4a218 <+96>:  ldr    w9, [sp, #0x10]
        0x104e4a21c <+100>: add    w8, w8, w9
        0x104e4a220 <+104>: ldr    w9, [sp, #0xc]
        0x104e4a224 <+108>: add    w0, w8, w9
        0x104e4a228 <+112>: add    sp, sp, #0x30             ; =0x30 
        0x104e4a22c <+116>: ret  

【情况3】当函数的返回值为多个字节时

    struct str {
        int a;
        int b;
        int c;
        int d;
        int f;
        int g;
    };
    struct str getStr(int a,int b,int c,int d,int f,int g){
        struct str str1;
        str1.a = a;
        str1.b = b;
        str1.c = d;
        str1.d = d;
        str1.f = f;
        str1.g = g;
        return str1;
    }
    int main(int argc, char * argv[]) {
        struct str str2 =  getStr(1, 2, 3, 4, 5, 6);
    }

汇编如下:

    Demo`main:
        0x100dfa23c <+0>:  sub    sp, sp, #0x40             ; =0x40 
        0x100dfa240 <+4>:  stp    x29, x30, [sp, #0x30]
        0x100dfa244 <+8>:  add    x29, sp, #0x30            ; =0x30 
        0x100dfa248 <+12>: stur   w0, [x29, #-0x4]
        0x100dfa24c <+16>: stur   x1, [x29, #-0x10]
        //将sp+0x8的地址存入x8
        0x100dfa250 <+20>: add    x8, sp, #0x8              ; =0x8 
        0x100dfa254 <+24>: mov    w0, #0x1
        0x100dfa258 <+28>: mov    w1, #0x2
        0x100dfa25c <+32>: mov    w2, #0x3
        0x100dfa260 <+36>: mov    w3, #0x4
        0x100dfa264 <+40>: mov    w4, #0x5
        0x100dfa268 <+44>: mov    w5, #0x6
    ->  0x100dfa26c <+48>: bl     0x100dfa158               ; getStr at main.m:20
        0x100dfa270 <+52>: mov    w9, #0x0
        0x100dfa274 <+56>: mov    x0, x9
        0x100dfa278 <+60>: ldp    x29, x30, [sp, #0x30]
        0x100dfa27c <+64>: add    sp, sp, #0x40             ; =0x40 
        0x100dfa280 <+68>: ret  
    
    Demo`getStr:
    ->  0x100dfa158 <+0>:  sub    sp, sp, #0x20             ; =0x20 
        0x100dfa15c <+4>:  str    w0, [sp, #0x1c]
        0x100dfa160 <+8>:  str    w1, [sp, #0x18]
        0x100dfa164 <+12>: str    w2, [sp, #0x14]
        0x100dfa168 <+16>: str    w3, [sp, #0x10]
        0x100dfa16c <+20>: str    w4, [sp, #0xc]
        0x100dfa170 <+24>: str    w5, [sp, #0x8]
        0x100dfa174 <+28>: ldr    w9, [sp, #0x1c]
        //重点:将w9存入x8的地址中,此时x8是main函数的栈空间
        0x100dfa178 <+32>: str    w9, [x8]
        0x100dfa17c <+36>: ldr    w9, [sp, #0x18]
        0x100dfa180 <+40>: str    w9, [x8, #0x4]
        0x100dfa184 <+44>: ldr    w9, [sp, #0x10]
        0x100dfa188 <+48>: str    w9, [x8, #0x8]
        0x100dfa18c <+52>: ldr    w9, [sp, #0x10]
        0x100dfa190 <+56>: str    w9, [x8, #0xc]
        0x100dfa194 <+60>: ldr    w9, [sp, #0xc]
        0x100dfa198 <+64>: str    w9, [x8, #0x10]
        0x100dfa19c <+68>: ldr    w9, [sp, #0x8]
        0x100dfa1a0 <+72>: str    w9, [x8, #0x14]
        0x100dfa1a4 <+76>: add    sp, sp, #0x20             ; =0x20 
        0x100dfa1a8 <+80>: ret   

总结

上一篇下一篇

猜你喜欢

热点阅读