首先我们先观察最干净的函数,做了什么
这个函数什么都没做
void _func_1_(){}复制代码
我没来看看这个函数的调用,与函数的里面的汇编是咋样的 函数调用的汇编
0x1004228a0 <+24>: bl 0x100422824 ; _func_1_ at main.m:13复制代码
函数执行的汇编
02-汇编-函数`_func_1_:-> 0x100422824 <+0>: ret 复制代码
函数调用的汇编就是这么简单,通过bl/ret指令实现代码块的跳转实现。 首先我们回顾一下bl/ret指令的功能
- bl指令 跳转,将下一条执行的指令放入lr(X30)寄存器
- ret 返回到lr寄存器所保存的地址 执行代码
我们可以直接查看寄存器看看是不是真的
####接下来我们在函数里面调用别的函数试试 先看C代码
void _func_2_(){ _func_1_();}复制代码
函数的调用都是一样的,我们去看看函数里面发什么了什么变化
02-汇编-函数`_func_2_: 0x1022e6810 <+0>: stp x29, x30, [sp, #-0x10]! 0x1022e6814 <+4>: mov x29, sp 0x1022e6818 <+8>: bl 0x1022e680c ; _func_1_ at main.m:13 0x1022e681c <+12>: ldp x29, x30, [sp], #0x10 0x1022e6820 <+16>: ret 复制代码
接下来我们仔细观察函数发生的变化,多了以下,两句话
stp x29, x30, [sp, #-0x10]! // 1.把x29 和 x30存方到sp-8 sp-10的位置 2.sp -= 0x16 ldp x29, x30, [sp], #0x10 // 1.将sp-8 sp-10位置的值取出来,放入x29 和 x30,2.sp += 0x16复制代码
通过堆栈的操作在函数的开头保存了x30(lr),x29(fp),两个寄存器,在函数结尾的时候,又把这些值取出来,为什么要怎么做呢? 反过来想,如果不这么做,会导致什么问题?会出现一个很严重的问题!【下面纯文字解释,请耐心理解】
当我们在执行这句话的 bl 0x1022e680c 会把x30寄存器改成 0x1022e681c地址,那么 0x1022e6820这个位置的时候执行ret,代码会跳回到main函数吗?肯定是不会的,它只会跳到x30指向的位置。那么代码就会在 0x1022e681c 0x1022e6820 这两个位置不断执行,知道把堆栈弄炸,程序奔溃~~~~~~~
所以这两句汇编是为了保护代码回家的的路,以后我们在逆向别人的代码的时候,请不要不破坏~~~
####接下来我们继续深入理解函数参数的传递,及返回值的返回 先上个简单的C代码
int _func_3_(int a,int b,int c,int d,int e,int f,int g,int h,int i,int j,int k,int l){ return a+b;}复制代码
接着上调用函数的汇编解释
02-汇编-函数`main: 0x1028f2888 <+0>: sub sp, sp, #0x40 ; =0x40 0x1028f288c <+4>: stp x29, x30, [sp, #0x30] 0x1028f2890 <+8>: add x29, sp, #0x30 ; =0x30 0x1028f2894 <+12>: stur wzr, [x29, #-0x4] 0x1028f2898 <+16>: stur w0, [x29, #-0x8] 0x1028f289c <+20>: stur x1, [x29, #-0x10] 0x1028f28a0 <+24>: bl 0x1028f2818 ; _func_1_ at main.m:13 0x1028f28a4 <+28>: bl 0x1028f281c ; _func_2_ at main.m:15 0x1028f28a8 <+32>: orr w0, wzr, #0x1 ; ----华丽分割线----- 0x1028f28ac <+36>: orr w1, wzr, #0x2 0x1028f28b0 <+40>: orr w2, wzr, #0x3 0x1028f28b4 <+44>: orr w3, wzr, #0x4 0x1028f28b8 <+48>: mov w4, #0x5 0x1028f28bc <+52>: orr w5, wzr, #0x6 0x1028f28c0 <+56>: orr w6, wzr, #0x7 0x1028f28c4 <+60>: orr w7, wzr, #0x8 0x1028f28c8 <+64>: mov w8, #0x9 0x1028f28cc <+68>: mov w9, #0xa 0x1028f28d0 <+72>: mov w10, #0xb 0x1028f28d4 <+76>: orr w11, wzr, #0xc-> 0x1028f28d8 <+80>: str w8, [sp] 0x1028f28dc <+84>: str w9, [sp, #0x4] 0x1028f28e0 <+88>: str w10, [sp, #0x8] 0x1028f28e4 <+92>: str w11, [sp, #0xc] ; ----华丽分割线----- 0x1028f28e8 <+96>: bl 0x1028f2830 ; 在这里调用_func_3_函数 0x1028f28ec <+100>: stur w0, [x29, #-0x14] 0x1028f28f0 <+104>: ldur w8, [x29, #-0x14] 0x1028f28f4 <+108>: mov x30, x8 0x1028f28f8 <+112>: mov x12, sp 0x1028f28fc <+116>: str x30, [x12] 0x1028f2900 <+120>: adrp x0, 1 0x1028f2904 <+124>: add x0, x0, #0xf14 ; =0xf14 0x1028f2908 <+128>: bl 0x1028f2bfc ; symbol stub for: printf 0x1028f290c <+132>: mov w8, #0x0 0x1028f2910 <+136>: str w0, [sp, #0x18] 0x1028f2914 <+140>: mov x0, x8 0x1028f2918 <+144>: ldp x29, x30, [sp, #0x30] 0x1028f291c <+148>: add sp, sp, #0x40 ; =0x40 0x1028f2920 <+152>: ret 复制代码
两段华丽的分割线展示了函数的参数是如何传递的 细致不多看,直接看结果
-> 0x1028f28d8 <+80>: str w8, [sp] 0x1028f28dc <+84>: str w9, [sp, #0x4] 0x1028f28e0 <+88>: str w10, [sp, #0x8] 0x1028f28e4 <+92>: str w11, [sp, #0xc] 复制代码
他们把第1-第8个参数放到了x0-x7八个寄存器,第9-第12个参数,存到堆栈进行传输参数 接着我们看看返回值
02-汇编-函数`_func_3_: 0x104d46830 <+0>: sub sp, sp, #0x30 ; =0x30 0x104d46834 <+4>: ldr w8, [sp, #0x3c] 0x104d46838 <+8>: ldr w9, [sp, #0x38] 0x104d4683c <+12>: ldr w10, [sp, #0x34] 0x104d46840 <+16>: ldr w11, [sp, #0x30] 0x104d46844 <+20>: str w0, [sp, #0x2c] 0x104d46848 <+24>: str w1, [sp, #0x28] 0x104d4684c <+28>: str w2, [sp, #0x24] 0x104d46850 <+32>: str w3, [sp, #0x20] 0x104d46854 <+36>: str w4, [sp, #0x1c] 0x104d46858 <+40>: str w5, [sp, #0x18] 0x104d4685c <+44>: str w6, [sp, #0x14] 0x104d46860 <+48>: str w7, [sp, #0x10]-> 0x104d46864 <+52>: ldr w0, [sp, #0x2c] 0x104d46868 <+56>: ldr w1, [sp, #0x28] 0x104d4686c <+60>: add w0, w0, w1 ; 把 w0+w1的值放到w0,也就是参数1+参数2 0x104d46870 <+64>: str w11, [sp, #0xc] 0x104d46874 <+68>: str w10, [sp, #0x8] 0x104d46878 <+72>: str w8, [sp, #0x4] 0x104d4687c <+76>: str w9, [sp] 0x104d46880 <+80>: add sp, sp, #0x30 ; =0x30 0x104d46884 <+84>: ret 复制代码
根据我上述的备注,他们吧结果放到w0,之后返回 所以我这边做个简单的总结:
- 参数会存放到X0到X7(W0到W7)这8个寄存器里面,如果超过8个参数,就会入栈,返回值是通过x0进行返回获取
- 由于操作堆栈会比操作寄存器更消耗性能,所以我们的参数尽可能不要超过8个
####拓展: 好像了解了函数的原理之后,没啥用?不是的,这对之后的逆向很有帮助,下面简单说一下最大的用处在哪 对于了解过OC runtime的开发者来说 ,都知道OC调用函数,都是通过 objc_msgSend 来实现的