iOS Crash log符号化庖丁解牛

项目在做zombie内存监测的时候有把zombie调用栈和oc对象释放栈报上来,由于我们的crash组件是用的第三方组件,zombie栈没法和crash log一起符号化,要自己对栈进行符号化,研究了一下CrashLog的还原原理和方法。

Crash Log符号还原方法

使用xcode

这是最简单的方法,要使用xcode进行符号还原需要下面三个文件:

Crash Reports(.crash文件)
符号文件 (.dsymb文件)
应用程序文件 (appName.app文件)

把这3个文件放到同一个目录下,打开Xcode的Window菜单下的organizer,然后点击Devices tab,然后选中左边的Device Logs。然后把.crash文件拖到Device Logs或者选择下面的import导入.crash文件。

使用symbolicatecrash

symbolicatecrash是xcode自带的工具
将“.app“, “.dSYM”和 “.crash”文件放到同一个目录下,终端设置如下环境:

export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer

然后输入下面命令

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/PrivateFrameworks/DTDeviceKitBase.framework/Versions/A/Resources/symbolicatecrash appName.crash appName.app > appName.log

使用atos命令行

前面两种方法足够简单,但都不适合脚本自动化,并且我们也没有原始.crash文件,
本文重点介绍这种方法和内部的原理。
atos命令可以对指定的地址进行符号化

NAME
atos -- convert numeric addresses to symbols of binary images or processes
SYNOPSIS
atos [-o ] [-p | ] [-arch architecture][-l ] [-s ] [-printHeader] [-v] [-D] [-f ] [

...]
DESCRIPTION
The atos command converts numeric addresses to their symbolic equivalents. If full debug symbol infor-mation informationmation is available, for example in a .app.dSYM sitting beside a .app, then the output of atos will
include file name and source line number information.

符号还原原理

首先来看下需要还原的栈长啥样

0   AppName                     0x00000001009e3110 _ZNSt3__111char_traitsIcE2eqEcc + 7884972
1   AppName                     0x0000000100620f04 _ZNSt3__111char_traitsIcE2eqEcc + 3944096
2   CoreFoundation              0x0000000188e58f60  + 132
3   CoreFoundation              0x0000000188d5280c _CF_forwarding_prep_0 + 92

看栈可以知道每个frame包括image名、代码地址、代码地址对应的符号信息,现在代码地址对应的符号是一串奇怪东东,我们要做的就是把这串奇怪的东东还原成可读的信息,包括函数名、原文件名、代码行。
要还原符号信息必须解决下面问题

  • 从哪里找函数名、原文件名、代码行这些信息
  • 怎么找这些信息

从哪里找函数名、原文件名、代码行这些信息

dSYM和DWARF

dSYM(debugging SYMbol)是从Mach-O文件中抽取调试信息而得到的文件目录,发布的时候为了安全和减小安全,一般会把调试信息存储在单独的文件,dSYM实际是一个文件目录,其目录结构如下:

 |--AppName.app.dSYM
    |--Contents
      |--info.plist
      |--Resources
        |--DWARF
          |--AppName

dSYM符号信息实际存储在DWARF文件里面,DWARF (DebuggingWith Arbitrary Record Formats)是起源贝尔实验室的一种调试信息文件格式,是ELF和Mach-O等文件格式中用来存储和处理调试信息的标准格式。
DWARF文件包含所有调试信息,并且以section的形式进行存储,DWARF使用DIE(Debugging Information Entry)来存储具体信息,DIE通过树结构组织,DIE可以有兄弟节点和子节点。DWARF文件包括下面section:

.debug_abbrev              Abbreviations used in the .debug_info section
.debug_aranges             A mapping between memory address and compilation
.debug_frame               Call Frame Information
.debug_info                The core DWARF data containing DIEs
.debug_line                Line Number Program
.debug_loc                 Macro descriptions
.debug_macinfo             A lookup table for global objects and functions
.debug_pubnames            A lookup table for global objects and functions
.debug_pubtypes            A lookup table for global types
.debug_ranges              Address ranges referenced by DIEs
.debug_str                 String table used by.debug_info

其中主要信息存储在debug_info和debug_line里,debug_info存储了函数信息、变量信息等,debug_line存储了对应源代码行数信息。可以用dwarfdump工具读取dwarf文件里的section。使用dwarfdump读取下面demo dSYM文件的section

@interface DSYMDemo : NSObject
@property (nonatomic, strong) NSString* var1;
- (NSString*)test;
@end
debug_info

使用dwarfdump读取DWARF文件debug_info信息

dwarfdump -e --debug-info DSYMDemo.app.dSYM/Contents/Resources/DWARF/DSYMDemo > debug-info.txt
0x00034d29:     function [119] *
                low pc( 0x0000000100006adc )
                high pc( 0x0000000100006b14 )
                frame base( reg29 )
                object pointer( {0x00034d49} )
                name( "-[DSYMDemo setVar1:]" )
                decl file( "/Users/haishengding/Desktop/test/DSYMDemo/DSYMDemo/DSYMDemo.h" )
                decl line( 13 )
                prototyped( 0x01 )
                artificial( 0x01 )
                APPLE optimized( 0x01 )

可以看到DIE里面包括了函数开始地址、结束地址、函数名、原文件名、开始地址在原文件的行数。对于给定的地址,找到函数开始地址和结束地址之间包含改地址的DIE,则可以还原函数名和原文件名。

debug_line

通过debug_info还原了函数名、原文件名,剩下原文行数则通过debug_line进行还原

dwarfdump -e --debug-line DSYMDemo.app.dSYM/Contents/Resources/DWARF/DSYMDemo > debug-line.txt
Address                Line  File
------------------ ------ ------------------------------
0x0000000100006ac0     13 ~/Desktop/test/DSYMDemo/DSYMDemo/DSYMDemo.m
0x0000000100006ac0     14
0x0000000100006acc     13 ~/Desktop/test/DSYMDemo/DSYMDemo/DSYMDemo.h
0x0000000100006acc     13
0x0000000100006adc     13
0x0000000100006aec      0
0x0000000100006b14     11 ~/Desktop/test/DSYMDemo/DSYMDemo/DSYMDemo.m
0x0000000100006b14     11
0x0000000100006b28     11

可以看到debug_line里面包含了每个代码地址对应的行数。

怎么找这些信息

已经知道对于指定的地址,通过dSYM文件可以还原符号信息,但怎么拿到这个地址呢?开始的函数栈frame有一个地址,就是这个地址吗?当然没这么简单,我们知道image加载的时候都会相对基地址进行重定位,并且每次加载的基地址都不一样,函数栈frame的地址是重定位后的绝对地址,我们要的是重定位前的相对地址。

Binary Images

Binary Images:
0x100050000 - 0x101c07fff +AppName arm64  /var/containers/Bundle/Application/175ED3FA-7329-49DE-B54A-88EEC120412C/AppName.app/AppName
0x187ee6000 - 0x187eeffff  libsystem_pthread.dylib arm64  /usr/lib/system/libsystem_pthread.dylib
0x187e04000 - 0x187e28fff  libsystem_kernel.dylib arm64

可以看到Crash Log的Binary Images块包含每个image加载起止地址、image名、arm架构、uuid、image路径。

frame
0   AppName   0x00000001009e3110 _ZNSt3__111char_traitsIcE2eqEcc + 7884972
Binary Image
0x100050000 - 0x101c07fff +AppName arm64  /var/containers/Bundle/Application/175ED3FA-7329-49DE-B54A-88EEC120412C/AppName.app/AppName

frame0的地址0x00000001009e3110-0x100050000 就是函数的相对地址,使用改地址通过dSYM文件就可以还原符号。

UUID

符号还原的时候必须通过匹配的dSYM,dSYM和image是通过UUID进行关联的,两者的UUID必须一样才能正确还原,image的UUID在Binary Images可以拿到,dSYM 的UUID可以通过dwarfdump读取

dwarfdump -u -arch arm64 AppName.app.dSYM/Contents/Resources/DWARF/AppName
UUID: AB90A1C5-646F-35DC-A8F8-CF1CE74C767C (arm64) AppName.app.dSYM/Contents/Resources/DWARF/AppName

可以看到读取的跟image里的是一样的。

使用atos命令行

上面讲了符号还原的原理,实际上atos工具帮我们做了这些事情,只要通过简单的命令就看还原了

atos -o AppName.app.dSYM/Contents/Resources/DWARF/AppName -arch arm64 -l 0x100050000 0x00000001009e3110
currentCallStack (in AppName) (xxx.m:17)

其中0x100050000是image加载地址,* 0x00000001009e3110*是需要符号还原的绝对地址,atos自己会转成相对地址

系统符号

dSYM文件只有我们自己代码的符号,系统函数则必须通过系统符号文件进行还原,系统符号一般存储在~/Library/Developer/Xcode/iOS DeviceSupport目录

0x187ee6000 - 0x187eeffff  libsystem_pthread.dylib arm64  /usr/lib/system/libsystem_pthread.dylib
OS Version: iPhone OS 10.2 (14C92)

通过Crash Log文件Bianry Images和OS Version信息可找到对应的符号文件。

脚本化

知道怎么还原具体frame栈桢的符号了,通个脚本解析每个栈桢就可以自动化还原整个栈了,像buggly平台应该也是用类似的方案。

以上内容为本人工作学习中所得,如有错误之处,还请指出!

参考文件

Symbolicating Your iOS Crash Reports
Understanding and Analyzing Application Crash Reports
Introduction to the DWARF Debugging Format

你可能感兴趣的:(iOS Crash log符号化庖丁解牛)