根据crash.log调试BUG

前言

对每个程序员来说调试(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 TypeEXC_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中使用的)
  • 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图:
CODE图
IDA图
IDA图

  • 0x00000001037fe6a0 - 0x100094000 = 0x376A6A0
  • 0x376A6A0 + 0x100000000 = 0x10376A6A0
  • 上面2个的计算方法后面也会写个水文解释
  • 根据计算后的结果0x10376A6A0,在IDA图中,键盘按一下g,输入0x10376A6A0
  • IDA图,这时候在IDA中会直接跳转到000000010376A6A0

    1. 偷懒定位崩溃位置的方式:

    根据崩溃地址附近的字符串在源代码中搜索

  • IDA图中的23可以看出crash发生的前后有调用了fprintffflush,如果以此为线索去代码中搜索运气不好的话会有大量匹配的代码出现,因为这2个函数很常用.
  • 可以试试IDA图 1中的字符串,因为它够特殊不容易有大量匹配的代码出现
  • 从搜索结果中我们成功定位到ida图1CODE图中对应的代码5.
  • IDA图中的23以及CODE图中的46我们可以推断出crash就是CODE图里的fprintf

    2. 偷懒定位崩溃位置的方式:

    有时并没那么幸运有可搜索的字符串,这时借助F5,查看伪代码说不定能找到蛛丝马迹

  • 从伪代码中可以看出代码逻辑或者一些其他关键字然后再根据IDA图中的23去推理崩溃的地方
  • 再此就不在赘述

    3. 如果没有F5且从汇编指令中无法找到蛛丝马迹

    哈哈哈 God bless you

  • 借助工具MonkeyDev或者IPAPatch,然后查看打印的log日志(如果日志没开,那么通过hook方案去强制开启). 祝你好运
  • 查看打印的log日志. 祝你好运

猜测:根据定位到的代码以及debug.crash中的其他信息

  • CODE图中,我们知道肯定是挂在了402-418之间
  • debug.crash中我们知道挂了的原因是accessbad地址
  • 根据上面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的那次估计会疯掉
  • 换一种方式下断点:hookCODE图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中可以看到参数fileNULL
  • 所以crash的问题找到了,剩下的就是看为什么file是NULL的问题,这个就是业务逻辑相关的,就不赘述了.

Note

  1. 根据

    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中哪个参数有问题,这个等我汇编入门了以后再来水一篇水文.

  2. 水一篇没有去除符号表调试的水文

Zerlz wechat
扫码关注一个很懒的程序员!
Winter is coming, give me a penny!