前言
如果Crash堆栈,最后一个方法是在自己工程的源码,问题都比较好解决。但如果最后落到了系统库的方法里,并且系统库还没开源,这时候要定位原因就非常困难了。于是我们只能读汇编代码,或反汇编分析伪代码。下面介绍Hopper Disassembler,它是较常用的的反汇编工具。
下载
通过官网下载Hopper工具: www.hopperapp.com/ 。可以先使用试用版,每次可以打开30分钟。
导入
方法1 顶部导航栏,选择File - Read Executable to Disassemble..., 选择要分析的二进制文件.
方法2 直接将可执行文件/动态库/静态库/archive拖到Hopper里
默认就是AArch64(Arm64),直接确认就可以。
通过一个案例看一下一般如何分析
Crash Log基本信息
- Date/Time: 2020-11-27 06:37:36 +0000
- OS Version: iPhone OS 14.2 (18B92)
- Report Version: 104
- Exception Type: SIGSEGV
- Exception Codes: SEGV_ACCERR at 0x0
- Triggered by Thread: 63
Crash发生在63号线程,最后的栈桢是_objc_msgSend,它第44行指令出现非法内存访问SEGV_ACCERR,访问了一个内存地址为0x0的指针,0x0明显是一个非法内存地址。下面我们通过查看libobjc的汇编,尝试分析一下0x0到底是从哪里来的。
导入目标文件
打开iOS DeviceSupport路径,在目标系统14.2 (18B92)文件夹里,找到libobjc动态库,将libobjc拖到反编译工具Hopper Disassembler。
- libobjc所在路径
- /Users/xxx/Library/Developer/Xcode/iOS DeviceSupport/14.2 (18B92) arm64e/Symbols/usr/lib/libobjc.A.dylib
定位到Crash指令行
搜索_objc_msgSend,定位到_objc_msgSend的arm汇编代码。方法的首地址是00000001949a60e0,偏移量+44是00000001949a610c,这行就是发生Crash的指令。
反向寻找异常的根源
00000001949a610c add x13, x10, x12, lsl #4
报错的日志显示,访问了非法内存0x0。简单可以理解为某个寄存器的地址是0x0,执行了某一个指令,读取了这个寄存器的值。所以这是一个推理游戏,我们找到0x0地址是从哪里来的。
lsl是逻辑左移指令,add是加法指令。这一行表示将x12左移4位,相当于乘以16,然后加上x10的值,最后赋值给x13寄存器。
- x13 <= x10 + (x12 * 16)
这里读取了两个内存地址的值x10和x12,因为发生了非法内存读取,所以x10或x12其中一个的地址是0x0。
- LSL是逻辑左移
- 逻辑左移,右边统一添0。逻辑左移一位:010101010[0]
- LSR是逻辑右移
- 逻辑右移,左边统一添0。逻辑右移一位:[0]101010101
00000001949a6104 eor x12, x1, x1, lsr #7
先看一下x12的来源,看它是否可能是0x0
这里表示x1右移7位,相当于除以128,然后和x1进行异或,结果赋值给x12寄存器,推论x12不太可能为0,那么就是x10为0。
- EOR 逻辑异或
- 如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。
00000001949a6100 and x10, x11, #0xffffffffffff
再看一下x10的来源。x10等于0,x11与上0xffffffffffff结果为0,那x11就是0.
00000001949a60ec and x16, x13, #0x7ffffffffffff8
结合反编译结果 struct objc_class *cls = (struct objc_class *)(isa & 0x7ffffffffffff8);
将isa与上0xffffffff8才能得到对象所属的Class对象,这里x13是isa指针,x16是class对象
- struct objc_class : objc_object {
- struct objc_class * superclass; //基类信息结构体。
- cache_t cache; //方法缓存哈希表
- //... 其他数据成员忽略。
- };
- struct cache_t {
- struct bucket_t *buckets; //缓存方法的哈希桶数组指针,桶的数量 = mask + 1
- int mask; //桶的数量 - 1
- int occupied; //桶中已经缓存的方法数量。
- };
读取x16里的地址偏移16个字节(0x10),取出来的值是0。取偏移16个字节的值,通常是取某个对象或结构体的成员变量。从上面一行得出,x16是class对象,偏移16个字节应该是cache对象,那么应该是cache对象为空。
总结
使用反汇编工具是分析疑难问题的基础,今天介绍了Hopper Disassembler工具的使用。有空可以找一些疑难问题来分析,慢慢地就会熟悉反汇编工具和arm64汇编。
本文转载自网络,原文链接:https://juejin.cn/post/6900725204054605838
版权声明:本文转载自网络,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。本站转载出于传播更多优秀技术知识之目的,如有侵权请联系QQ/微信:153890879删除