项目在做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 <binary-image-file>] [-p <pid> | <partial-executable-name>] [-arch architecture][-l <load-address>] [-s <slide>] [-printHeader] [-v] [-D] [-f <address-input-file>] [<address> ...]
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 <redacted> + 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 <ab90a1c5646f35dca8f8cf1ce74c767c> /var/containers/Bundle/Application/175ED3FA-7329-49DE-B54A-88EEC120412C/AppName.app/AppName
0x187ee6000 - 0x187eeffff libsystem_pthread.dylib arm64 <d8480fc3a35d3475b0d12553c761d8cb> /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 <ab90a1c5646f35dca8f8cf1ce74c767c> /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 <d8480fc3a35d3475b0d12553c761d8cb> /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