前言
对每个程序员来说调试(Debug)是一个不可或缺的技能
偵錯(英语:Debug),又稱除錯,是发现和减少计算机程序或电子仪器设备中程序错误的一个过程(wiki-调试)
背景
做SDK开发,难免会有些app因为嵌入了SDK然后出现了crash,这时候就需要你具备一定的`debug`的能力找到`crash`的位置并修复.
调试前已有的资料
- 崩溃发生时所对应的ipa文件
debug.ipa
- 崩溃发生后从iPhone导出的crash文件
debug.crash
- 崩溃发生前后对app所做的操作
- 点开直播并观看直播一小段时间(大于等于10秒),上拉iPhone的控制栏,将网络切为飞行模式
CRASH重现的操作步骤:
- 重现的关键点是看直播&网络切为飞行模式
查看debug.crash
文件:(下面贴出来的就是debug.crash
的部分关键信息)
Incident Identifier: 8F6F39B3-4D20-482E-AF4B-488CA4CC08F4
CrashReporter Key: 1b5a79e53becd178a43b9ff388032fe050e7db46
Hardware Model: iPhone7,2
Process: DEBUG [531]
Path: /private/var/containers/Bundle/Application/F2859F2E-62D5-423E-B015-58CC7694454B/DEBUG.app/DEBUG
Identifier: com.hellodebug.DEBUG
Version: 1 (4.500)
Code Type: ARM-64 (Native)
Role: Foreground
Parent Process: launchd [1]
Coalition: com.hellodebug.DEBUG [715]
Date/Time: 2018-07-26 12:22:53.6037 +0800
Launch Time: 2018-07-26 12:22:30.1921 +0800
OS Version: iPhone OS 10.3.1 (14E304)
Report Version: 104
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000068
Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [0]
Triggered by Thread: 1
Filtered syslog:
None found
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0:
0 libsystem_kernel.dylib 0x000000018081f224 mach_msg_trap + 8
1 libsystem_kernel.dylib 0x000000018081f09c mach_msg + 72
2 CoreFoundation 0x00000001817f0e88 __CFRunLoopServiceMachPort + 192
3 CoreFoundation 0x00000001817eeadc __CFRunLoopRun + 1060
4 CoreFoundation 0x000000018171ed94 CFRunLoopRunSpecific + 424
5 GraphicsServices 0x0000000183188074 GSEventRunModal + 100
6 UIKit 0x00000001879d7130 UIApplicationMain + 208
7 DEBUG 0x0000000100f98954 0x100094000 + 15747412
8 libdyld.dylib 0x000000018072d59c start + 4
Thread 1 name: Dispatch queue: debugLibMtunRunningQueueName
Thread 1 Crashed:
0 libsystem_c.dylib 0x0000000180757604 flockfile + 0
1 libsystem_c.dylib 0x00000001807575c8 vfprintf_l + 36
2 libsystem_c.dylib 0x0000000180757594 fprintf + 76
3 DEBUG 0x00000001037fe6a0 0x100094000 + 58107552
4 DEBUG 0x00000001037fe10c 0x100094000 + 58106124
5 DEBUG 0x000000010381b55c 0x100094000 + 58226012
6 DEBUG 0x000000010381c06c 0x100094000 + 58228844
7 DEBUG 0x000000010381c11c 0x100094000 + 58229020
8 DEBUG 0x0000000103801dd0 0x100094000 + 58121680
9 DEBUG 0x000000010377c3dc 0x100094000 + 57574364
10 libdispatch.dylib 0x00000001806fa9e0 _dispatch_call_block_and_release + 24
11 libdispatch.dylib 0x00000001806fa9a0 _dispatch_client_callout + 16
12 libdispatch.dylib 0x0000000180708ad4 _dispatch_queue_serial_drain + 928
13 libdispatch.dylib 0x00000001806fe2cc _dispatch_queue_invoke + 884
14 libdispatch.dylib 0x0000000180708fa8 _dispatch_queue_override_invoke + 344
15 libdispatch.dylib 0x000000018070aa50 _dispatch_root_queue_drain + 540
16 libdispatch.dylib 0x000000018070a7d0 _dispatch_worker_thread3 + 124
17 libsystem_pthread.dylib 0x00000001809031d0 _pthread_wqthread + 1096
18 libsystem_pthread.dylib 0x0000000180902d7c start_wqthread + 4
Thread 1 crashed with ARM Thread State (64-bit):
x0: 0x0000000000000000 x1: 0x00000001a7af2cf8 x2: 0x0000000105c8c19e x3: 0x000000016dfba940
x4: 0x0000000000001403 x5: 0x0000000000000000 x6: 0x0000000000000000 x7: 0x0000000000000b80
x8: 0x00000001a7af2cf8 x9: 0x0000000000000000 x10: 0x000000014eb245ac x11: 0x000000014eb244ec
x12: 0x000000016dfba724 x13: 0xffffffffffffffc4 x14: 0x0000000000000000 x15: 0x0000000000000d54
x16: 0x0000000180757548 x17: 0x000000010378512c x18: 0x0000000000000000 x19: 0x000000016dfba940
x20: 0x0000000105c8c19e x21: 0x00000001a7af2cf8 x22: 0x0000000000000000 x23: 0x0000000105c8c018
x24: 0x0000000000000067 x25: 0x000000014eb244ac x26: 0x00000000ffffffff x27: 0x00000000ffffffff
x28: 0x00000000ffffffff fp: 0x000000016dfba900 lr: 0x00000001807575c8
sp: 0x000000016dfba8e0 pc: 0x0000000180757604 cpsr: 0x60000000
Binary Images:
0x100094000 - 0x106087fff DEBUG arm64 <1713c2930f223f9884567cf29951c531> /var/containers/Bundle/Application/F2859F2E-62D5-423E-B015-58CC7694454B/DEBUG.app/DEBUG
根据debug.crash
中的信息我们能得到的对我们调试有用的信息
Exception Type
为EXC_BAD_ACCESS
:- access了坏地址
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000068
:- 内核访问了无效的地址
Thread 1 Crashed:
:- 挂掉的线程就是这个
Thread 1
- 挂掉的线程就是这个
- 根据上面的结论我们需要看
Thread 1 name: Dispatch queue: debugLibMtunRunningQueueName
:- 挂掉的
thread
的名字为debugLibMtunRunningQueueName
- 挂掉的
- 根据thread的名字为
debugLibMtunRunningQueueName
:- app崩溃确实是由于我方的SDK导致(因为这个
debugLibMtunRunningQueueName
名字就是我方SDK中使用的)
- app崩溃确实是由于我方的SDK导致(因为这个
3 DEBUG 0x00000001037fe6a0 0x100094000 + 58107552
:- 程序挂在了
0 libsystem_c.dylib 0x0000000180757604 flockfile + 0
,从这往上回溯调用栈直到看到我们3 DEBUG 0x00000001037fe6a0 0x100094000 + 58107552
- 有问题的代码在
DEBUG
的这个地址0x00000001037fe6a0
- 程序挂在了
- 如果没有对代码做混淆或者去除符号表的话这里
3 DEBUG 0x00000001037fe6a0 0x100094000 + 58107552
就可以直接看到崩溃相关的代码了(有空会再写一篇没有去除符号表调试的水文) - 这里看不到什么地方出问题了,只能知道是
fprintf
这里有问题。这时我们需要结合Thread 1 crashed with ARM Thread State (64-bit):
和0x100094000 - 0x106087fff DEBUG arm64
的数据进行分析
定位:借助IDA定位崩溃在代码中的位置
在借助IDA分析前,有句话要说。如果可以请支持正版
CODE图:
IDA图
0x00000001037fe6a0 - 0x100094000 = 0x376A6A0
0x376A6A0 + 0x100000000 = 0x10376A6A0
- 上面2个的计算方法后面也会写个水文解释
- 根据计算后的结果
0x10376A6A0
,在IDA图
中,键盘按一下g
,输入0x10376A6A0
- 如
IDA图
,这时候在IDA
中会直接跳转到000000010376A6A0
1. 偷懒定位崩溃位置的方式:
根据崩溃地址附近的字符串在源代码中搜索
- 从
IDA图
中的2
和3
可以看出crash发生的前后有调用了fprintf
和fflush
,如果以此为线索去代码中搜索运气不好的话会有大量匹配的代码出现,因为这2个函数很常用. - 可以试试
IDA图
1
中的字符串,因为它够特殊不容易有大量匹配的代码出现 - 从搜索结果中我们成功定位到ida图
1
在CODE图
中对应的代码5
. - 从
IDA图
中的2
和3
以及CODE图
中的4
和6
我们可以推断出crash就是CODE图
里的fprintf
中2. 偷懒定位崩溃位置的方式:
有时并没那么幸运有可搜索的字符串,这时借助F5,查看伪代码说不定能找到蛛丝马迹
- 从伪代码中可以看出代码逻辑或者一些其他关键字然后再根据
IDA图
中的2
和3
去推理崩溃的地方 - 再此就不在赘述
3. 如果没有F5且从汇编指令中无法找到蛛丝马迹
哈哈哈
God bless you
- 借助工具
MonkeyDev
或者IPAPatch
,然后查看打印的log日志(如果日志没开,那么通过hook方案去强制开启). 祝你好运 - 查看打印的log日志. 祝你好运
猜测:根据定位到的代码以及debug.crash
中的其他信息
- 从
CODE图
中,我们知道肯定是挂在了402
-418
之间 - 从
debug.crash
中我们知道挂了的原因是access
了bad
地址 - 根据上面2个的猜测可以得出如下结论:应该是
fprintf
中的各个参数中,有个(也可能是多个)参数是无效的或者为NULL
,传说中的野指针?
验证猜测:
借助工具
MonkeyDev
或者IPAPatch
- 既然怀疑是
fprintf
中的参数有问题那么我们给fprintf
下个断点,然后崩溃的时候查看各个参数的情况就能知道答案了。似乎很快就能解决问题了 lldb
中键入如下命令:br s -n fprintf
会看到如下的输出就表示你断点下好了.Breakpoint 1: where = libsystem_c.dylib`fprintf, address = 0x000000010da95ff4
- 结果:在crash之前,Xcode会无数次停在
fprintf
的断点上,因为代码里很多地方用到了fprintf
,如果一个一个排查下去直到crash的那次估计会疯掉 换一种方式下断点:hook
CODE图
中402
行带多个参数的这个fprintf
函数,hook
代码如下(由于自己的技术很烂,所以写不了不定参数的hook,很low!)int my_fprintf(FILE * __restrict, const char * __restrict, ...); int my_fprintf(FILE * __restrict file , const char * __restrict format, ...) { printf("my fprintf hahah\n"); // return orig_fprintf(file, chars); char *stack[38]; va_list args; va_start(args, format); memcpy(stack, args, 8 * 38); va_end(args); // how to hook variadic function? fake a original copy stack. // [move to detail-1](http://jmpews.github.io/2017/08/29/pwn/%E7%9F%AD%E5%87%BD%E6%95%B0%E5%92%8C%E4%B8%8D%E5%AE%9A%E5%8F%82%E6%95%B0%E7%9A%84hook/) // [move to detail-2](https://github.com/jmpews/HookZzModules/tree/master/AntiDebugBypass) int x = orig_fprintf( file,format, stack[0], stack[1], stack[2], stack[3], stack[4], stack[5], stack[6], stack[7], stack[8], stack[9], stack[10], stack[11], stack[12], stack[13], stack[14], stack[15], stack[16], stack[17], stack[18], stack[19], stack[20], stack[21], stack[22], stack[23], stack[24], stack[25], stack[26], stack[27], stack[28], stack[29], stack[30], stack[31], stack[32], stack[33], stack[34], stack[35], stack[36], stack[37]); return x; } __attribute__((constructor)) static void entry(){ rebind_symbols((struct rebinding[1]){{"fprintf", my_fprintf, (void*)&orig_fprintf}},1); }
复现crash
- 看直播&网络切为飞行模式
- 如愿,应用crash了,从Xcode的堆栈里回溯到
my_fprintf
中可以看到参数file
是NULL
- 所以crash的问题找到了,剩下的就是看为什么file是NULL的问题,这个就是业务逻辑相关的,就不赘述了.
Note
根据
Thread 1 Crashed: 0 libsystem_c.dylib 0x0000000180757604 flockfile + 0 1 libsystem_c.dylib 0x00000001807575c8 vfprintf_l + 36 2 libsystem_c.dylib 0x0000000180757594 fprintf + 76 3 DEBUG 0x00000001037fe6a0 0x100094000 + 58107552
和
Thread 1 crashed with ARM Thread State (64-bit): x0: 0x0000000000000000 x1: 0x00000001a7af2cf8 x2: 0x0000000105c8c19e x3: 0x000000016dfba940 x4: 0x0000000000001403 x5: 0x0000000000000000 x6: 0x0000000000000000 x7: 0x0000000000000b80 x8: 0x00000001a7af2cf8 x9: 0x0000000000000000 x10: 0x000000014eb245ac x11: 0x000000014eb244ec x12: 0x000000016dfba724 x13: 0xffffffffffffffc4 x14: 0x0000000000000000 x15: 0x0000000000000d54 x16: 0x0000000180757548 x17: 0x000000010378512c x18: 0x0000000000000000 x19: 0x000000016dfba940 x20: 0x0000000105c8c19e x21: 0x00000001a7af2cf8 x22: 0x0000000000000000 x23: 0x0000000105c8c018 x24: 0x0000000000000067 x25: 0x000000014eb244ac x26: 0x00000000ffffffff x27: 0x00000000ffffffff x28: 0x00000000ffffffff fp: 0x000000016dfba900 lr: 0x00000001807575c8 sp: 0x000000016dfba8e0 pc: 0x0000000180757604 cpsr: 0x60000000
是可以算出当时crash时堆栈所处的状态,理论上是可以人工回溯到fprintf中哪个参数有问题,这个等我汇编入门了以后再来水一篇水文.
水一篇没有去除符号表调试的水文