iOS
身为iOS程序员的你还在为如何解决定位而烦恼吗?
前言
如果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对象
00000001949a60f8 ldr x11, [x16, #0x10]
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汇编
作者:Blacktea
链接:https://juejin.cn/post/6900725204054605838
上一篇: 小程序中动态获取列表对象信息的代码示例
下一篇: php运算符以及运算符短路