通过汇编分析方法参数
大家好,我是脑洞TF。从事iOS开发多年。这是我刚开的公众号。未来我会持续不定期更新,欢迎大家关注我。一起学习,一起探讨,一起成长,笔芯!
前言: 我们经常会在开发过程中遇到崩溃,崩溃到系统的方法里的时候,我们想要知道方法的参数什么。在不知道参数的个数和参数类型的情况下是非常头痛的。下面就通过简单的例子,了解下如何通过汇编,分析方法的参数。
一、先举个例子 例子很简单,通过3个函数学习参数是如何传递的!3个函数分别为:有2个参数、有9个参数和可变参数的函数。下面在每个方法打断点,看看汇编代码。 1、源码如下:
2、验证思路 通过以上3个方法分别验证不同的参数传递的情况,
3、阅读main函数的汇编代码 在 func 方法里面打断点,查看汇编代码:
0x102ca2200 <+160>: ldur x10, [x29, #-0x20] 0x102ca2204 <+164>: ldur x8, [x29, #-0x28]
通过以上几个例子做个总结:
4、再看看func2是如何使用参数的 首先再来回顾下调用func2之前main函数的汇编代码,如下: 0x10255e1dc <+108>: ldur x0, [x29, #-0x18] 0x10255e1e0 <+112>: ldur x1, [x29, #-0x20] 0x10255e1e4 <+116>: ldur x2, [x29, #-0x28] 0x10255e1e8 <+120>: ldur x3, [x29, #-0x30] 0x10255e1ec <+124>: ldr x4, [sp, #0x38] 0x10255e1f0 <+128>: ldr x5, [sp, #0x30] 0x10255e1f4 <+132>: ldr x6, [sp, #0x28] 0x10255e1f8 <+136>: ldr x7, [sp, #0x20] 0x10255e1fc <+140>: ldr x8, [sp, #0x18] 0x10255e200 <+144>: mov x9, sp 0x10255e204 <+148>: str x8, [x9] 0x10255e208 <+152>: bl 0x100f6a0e4 ; fun2 at main.m:14 栈内存的状态如下图:
二、总结 以上例子就是要说的所有内容。下面来总结下,通过汇编看参数的值的几个小技巧:
可是如果断点在下面这一行,x0已经修改很多次了,无法通过x0来看参数,怎么办呢?
就可以用下面的方式获取方法的第一个参数:
三、小试牛刀 随便找了个动态库,下面这段代码有3个方法分别是: pthread_key_create _resetDyldInsetLibraries _creade_backtrace_uniquing_table 从代码上看我们猜测: pthread_key_create 有2个参数 分别是2个64位的参数 _resetDyldInsetLibraries 有1个参数,应该是个字符串的地址 _creade_backtrace_uniquing_table 有2个参数,而且都是常量
怎么样,是不是挺简单的,以上都是我自己的理解也不保证完全正确,如果有错误,希望大家能够私信纠正我,谢谢!如果大家能帮忙点赞、转发、关注我,那就是小弟最大的支持了~~🌹
🌹
🌹
🌹
🌹
🌹
前言: 我们经常会在开发过程中遇到崩溃,崩溃到系统的方法里的时候,我们想要知道方法的参数什么。在不知道参数的个数和参数类型的情况下是非常头痛的。下面就通过简单的例子,了解下如何通过汇编,分析方法的参数。
一、先举个例子 例子很简单,通过3个函数学习参数是如何传递的!3个函数分别为:有2个参数、有9个参数和可变参数的函数。下面在每个方法打断点,看看汇编代码。 1、源码如下:
void fun1(int64_t a, int64_t b) {NSLog(@"fun1");}void fun2(int64_t a, int64_t b, int64_t c, int64_t d, int64_t e, int64_t f, int64_t g, int64_t h,int64_t i) {NSLog(@"fun2 %lld %lld",h,i);}void fun3(int64_t a, ...) {NSLog(@"fun3");}int main(int argc, char * argv[]) {int64_t a = 1, b = 2,c = 3,d = 4,e = 5,f = 6,g = 7,h = 8,i = 9;fun1(a, b);fun2(a, b, c, d, e, f, g, h, i);fun3(a,b,c);}
2、验证思路 通过以上3个方法分别验证不同的参数传递的情况,
-
Func1 2个参数,比较常规。
- Func2 9个参数,验证下参数超多的情况,参数如何传递。
- Func3 可变参数,验证下可变参数的情况,参数如何传递。
x0 ~ x30:可以通过w0 ~ w30来访问这31个64位寄存器的低32位,写入时会将高32位清零。其中, x0-x7 用于子程序调用时的参数传递。
pc:保存着当前CPU执行指令的地址。 sp:即x31,指向堆栈的顶部。 fp:即x29,帧指针,指向当前frame的栈底,也就是高地址。 lr:即x30,存储着函数的返回地址。3、阅读main函数的汇编代码 在 func 方法里面打断点,查看汇编代码:
(图中内存编号的每一个格子表示8个字节)
3.3.1 验证参数个数小于8个的函数
3.3.2 验证 参数个数大于8个的函数
下面再看下func2是如何传递参数的: 0x10255e1dc <+108>: ldur x0, [x29, #-0x18] 0x10255e1e0 <+112>: ldur x1, [x29, #-0x20] 0x10255e1e4 <+116>: ldur x2, [x29, #-0x28] 0x10255e1e8 <+120>: ldur x3, [x29, #-0x30] 0x10255e1ec <+124>: ldr x4, [sp, #0x38] 0x10255e1f0 <+128>: ldr x5, [sp, #0x30] 0x10255e1f4 <+132>: ldr x6, [sp, #0x28] 0x10255e1f8 <+136>: ldr x7, [sp, #0x20] 0x10255e1fc <+140>: ldr x8, [sp, #0x18] 上面是将数字1~9分别加载到x0~x8寄存器中,但不是说x0-x7才用于参数传递吗?是的,我们接着看下面几句: //将sp的地址复制到x9 0x10255e200 <+144>: mov x9, sp /* 执行上面这个指令之后x9和sp的值分别为: sp = 0x000000016b4476c0 x9 = 0x000000016b4476c0 */ 执行下面的命令之前先看下x9寄存器地址的内存值:
0x102ca2200 <+160>: ldur x10, [x29, #-0x20] 0x102ca2204 <+164>: ldur x8, [x29, #-0x28]
下面就和func2里最后一个参数的操作是一样的。将x10、x8 的内容分别存到了sp=x9 和x9+0x8 地址所指向的内存中。
通过以上几个例子做个总结:
- 参数个数<=8个是通过x0~x7寄存器存储传递的
- 参数>8或者可变参数,是通过栈内存进行传递的。
- 想要确认某个函数参数,可以从跳转函数的指令向上查找,找到修改x0或者w0寄存器的指令,这条指令附近一般都是在处理函数参数。
4、再看看func2是如何使用参数的 首先再来回顾下调用func2之前main函数的汇编代码,如下: 0x10255e1dc <+108>: ldur x0, [x29, #-0x18] 0x10255e1e0 <+112>: ldur x1, [x29, #-0x20] 0x10255e1e4 <+116>: ldur x2, [x29, #-0x28] 0x10255e1e8 <+120>: ldur x3, [x29, #-0x30] 0x10255e1ec <+124>: ldr x4, [sp, #0x38] 0x10255e1f0 <+128>: ldr x5, [sp, #0x30] 0x10255e1f4 <+132>: ldr x6, [sp, #0x28] 0x10255e1f8 <+136>: ldr x7, [sp, #0x20] 0x10255e1fc <+140>: ldr x8, [sp, #0x18] 0x10255e200 <+144>: mov x9, sp 0x10255e204 <+148>: str x8, [x9] 0x10255e208 <+152>: bl 0x100f6a0e4 ; fun2 at main.m:14 栈内存的状态如下图:
此时跳转到func2,再看看func2的汇编代码如下:
二、总结 以上例子就是要说的所有内容。下面来总结下,通过汇编看参数的值的几个小技巧:
- 如果已知函数参数个数<=8个并且为非可变参数,参数可以通过x0~x7寄存器查看。
- 如果不确定函数参数个数或者是否为可变参数的话,可以bl指令(callee方法压栈)之前和caller方法处理x0寄存器之间,是否有操作栈内存的指令。尤其注意ldr、ldur这两条指令。如果有就非常有可能存在通过栈内存传递参数的情况。
-
在callee方法中,当前的参数寄存器已经被修改过了,那么还有什么其他方法看参数吗?有!一般情况下,参数都会在fp寄存器下面,如图:
如果崩溃在下面这一行,完全可以直接看x0~x3寄存器:
就可以用下面的方式获取方法的第一个参数:
三、小试牛刀 随便找了个动态库,下面这段代码有3个方法分别是: pthread_key_create _resetDyldInsetLibraries _creade_backtrace_uniquing_table 从代码上看我们猜测: pthread_key_create 有2个参数 分别是2个64位的参数 _resetDyldInsetLibraries 有1个参数,应该是个字符串的地址 _creade_backtrace_uniquing_table 有2个参数,而且都是常量