简介
DWARF
全名是Debugging With Attribute Record Formats
,是一种调试信息的存放格式。DWARF
是一种通用的标准格式,很多编译器对其都有支持,并且DWARF
支持C
、OC
、go
等多种语言。
DWARF
的作用是对机器码和代码之间进行一个映射关系,用来为调试器DEBUG
提供调试信息,告诉调试器代码和二进制如何关联,在代码中添加一个断点后,在程序运行到什么位置要停下来。如果想开发一个Linux
调试器,DWARF
是必须要学的。除此之外,DWARF
在发生崩溃时提供映射信息,对崩溃堆栈进行符号化。
对于iOS
开发者来说,dSYM
中主要就是DWARF
文件,Xcode
使用的LLVM
和GCC
编译器内核,对DWARF
支持很好。通过xcodebuild archive
命令打包时,在xcarchive
产物中默认包含dSYM
文件。
DWARF
是一个压缩文件,可以通过dwarfdump
相关命令进行信息提取,通过--debug-info
命令可以打印解压后DWARF
文件的内容。较大应用的dSYM
不要轻易尝试,内容较多,建议找个小Demo
打印看下。
objc
dwarfdump --debug-info xxxx.app.dSYM/Contents/Resources/DWARF/xxxx
DIE
高级开发语言中,代码一般都是"块"结构,所以DWARF也通过"块"的方式来描述信息。
DWARF
使用DIE(Debugging Information Entry)
来描述类信息,包括属性、方法、变量等信息。以DW_TAG
开头的都是DIE
,例如DW_TAG_variable
、DW_TAG_subprogram
这些。DWARF
包含文件名、方法名、代码行号等信息,可以将机器码转换为代码,用来进行崩溃的解析。
CU
DWARF
整体由多个DIE
构成,DIE
之间会存在父子节点、兄弟节点,最后构成一个树形结构。DWARF
最外层是DW_TAG_compile_unit
,也可以简称为CU
,一个DWARF
中会有多个CU
,其对应我们自己开发的.m
文件、framework
、三方库等目标。
DW_TAG
DW_TAG
后面的TAG
指明了当前DIE
所属的类型,例如变量、属性、方法等那种类型,例如DW_TAG_variable
就是variable
类型。以及一系列的attributed
,描述这个DIE
都有哪些信息。
下面是一个OC
方法的部分描述。
CU
中会包含下一级TAG
DW_TAG_subprogram
:表示方法DW_TAG_pointer_type
:指针类型,例如定义的静态变量,就会是这个类型DW_TAG_subroutine_type
,函数指针,例如定义了block
就会出现这个类型DW_TAG_base_type
,基础数据类型,例如long
、int
这类DW_TAG_structure_type
,结构体,一个类本质上也是个结构体,如果DW_AT_APPLE_runtime_class
是DW_LANG_ObjC
,则是一个类的结构体
再顺着DW_TAG_subprogram
寻找下一级TAG
,可以看到如下结构,这里列出一些关键信息
DW_TAG_variable
,变量,包括局部变量或者类的成员变量DW_TAG_formal_parameter
,函数的外部参数DW_TAG_lexical_block
,如果是自己声明的局部变量,外面会包一层block
DW_AT
TAG
中声明了很多DW_AT
开头的信息,这些信息用来描述DIE
,例如低地址、文件名等。DW_AT
分为基础结构和复合结构两种,基础结构不涉及指向其他DIE
的问题,相对比较简单。
依然用上面介绍CU
的图来举例,下面的结构是一个CU
,里面包含了一个方法,方法中有一个实例变量。
这里列出DW_AT
常用的一些字段,及其含义,不同的类型下显示可能会不同。
DW_AT_producer
,编译文件的编译器类型,例如iOS
一般是clang,Apple clang version 14.0.3 (clang-1403.0.22.14.1)
这种格式DW_AT_language
,代码所属语言,例如OC
代码是DW_LANG_ObjC
类型DW_AT_low_pc
,低地址,clang
一般设置断点,就是设置给低地址DW_AT_high_pc
,高地址DW_AT_name
,名称,根据所处的TAG
,显示函数名、变量名、类型等信息DW_AT_decl_line
,代码行数,定义的变量、方法调用等会有这个字段DW_AT_decl_file
,文件所在位置,绝对路径DW_AT_type
,类型,例如SEL
或NSString
指针类型
除了描述单个对象外,DWARF
也存在类似指针的概念。例如常用的id
类型,其DIE
地址位于0x0005a8d0
,其类型为objc_object
,其type
本身指向了objc_object
的首地址。一般是通过DW_AT_type
指向另一个对象的地址,一般用于声明对象类型,例如id
类型。
c
0x0005a8d0: DW_TAG_typedef
DW_AT_type (0x000000000005a8de "objc_object *")
DW_AT_name ("id")
根据首地址一路上上找,直到找到DW_TAG_structure_type
类型的DIE
,是一个基础数据类型,查找链路结束。
c
0x0005a8de: DW_TAG_pointer_type
DW_AT_type (0x000000000005a8e7 "objc_object")
0x0005a8e7: DW_TAG_structure_type
DW_AT_name ("objc_object")
DW_AT_byte_size (0x00)
uuid
DWARF
和Mach-O
都有唯一标识符,叫做uuid
,二者应该是一一对应的,在进行调试器与代码的映射时,以及符号化崩溃信息,都需要这两个uuid
匹配才行。通过dwarfdump --uuid
命令,可以查看二者的uuid
以及指令集架构。
例如bugly
这样的三方崩溃分析平台,也会检测uuid
是否匹配,否则解析的结果并不准确。
c
dwarfdump --uuid /Users/Desktop/DemoProject.app.dSYM/Contents/Resources/DWARF/DemoProject
UUID: 1358E378-2B8D-329A-A729-83B2F5F68CBD (arm64) /Users/Desktop/DemoProject.app.dSYM/Contents/Resources/DWARF/DemoProject
dwarfdump --uuid /Users/Desktop/DemoProject
UUID: 1358E378-2B8D-329A-A729-83B2F5F68CBD (arm64) /Users/Desktop/DemoProject
dSYM定位原理
首先确定崩溃堆栈的uuid
和dSYM
能对应上,随后才是分析崩溃堆栈的过程。
当发生崩溃时,堆栈一般如下格式,红圈内有三个地址。最前面的是崩溃堆栈,第二个是所在二进制的地址,后面是偏移量。
而bugly
这些三方崩溃分析平台,在我们上传dSYM
后却可以分析出比较精确的信息,其本质是对dSYM
和偏移量计算实现的。通过dwarfdump --lookup
命令可以实现类似的效果,通过传入的堆栈地址可以查找到对应堆栈的崩溃信息。
shell
dwarfdump /Users/Desktop/DemoProject.app.dSYM/Contents/Resources/DWARF/DemoProject --lookup 0x000f46b0
从下面的信息来看,崩溃位置定位的还是很明确的。
c
0x04663c50: DW_TAG_compile_unit
DW_AT_producer ("Apple clang version 14.0.3 (clang-1403.0.22.14.1)")
DW_AT_language (DW_LANG_ObjC)
DW_AT_name ("/Users/Desktop/iPhoneVideo/ViewControllers/LineShowSportHealthModule/LineShowHealth/View/Detail/HealthPanelDetailView.m")
DW_AT_LLVM_sysroot ("/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.4.sdk")
DW_AT_APPLE_sdk ("iPhoneOS16.4.sdk")
DW_AT_stmt_list (0x02510f7c)
DW_AT_comp_dir ("/Users/Desktop/iPhoneVideo/Pods")
DW_AT_APPLE_major_runtime_vers (0x02)
DW_AT_low_pc (0x00000001057cf020)
DW_AT_high_pc (0x00000001057d69e0)
0x04664245: DW_TAG_subprogram
DW_AT_low_pc (0x00000001057cfb74)
DW_AT_high_pc (0x00000001057d00c8)
DW_AT_frame_base (DW_OP_reg29 W29)
DW_AT_object_pointer (0x0466425f)
DW_AT_name ("-[HealthPanelDetailView refresh:]")
DW_AT_decl_file ("/Users/Desktop/feature/iPhoneVideo/Pods/ViewControllers/LineShowSportHealthModule/LineShowHealth/View/Detail/HealthPanelDetailView.m")
DW_AT_decl_line (54)
DW_AT_prototyped (true)
Line info: file 'HealthPanelDetailView.m', line 58, column 93, start file 'HealthPanelDetailView.m', start line 54
编译优化
在Build Settings
中的Debug Information Format
中存在两个选项,DWARF
和DWARF with dSYM File
,通常在debug
时我们会将这个选项设置为DWARF
,这能带来编译速度的提升。
选择DWARF
方式在编译产物的dSYM
文件夹下,不会出现dSYM
文件,如果选择DWARF with dSYM File
则会以target
为纬度,生成一到多个dSYM
文件。一个压缩后的DWARF
文件,通常和导出的ipa
包大小差不多,其内部结构比较复杂,这也是其能使编译提速的原因。
到此,对于DWARF
的介绍就讲完了,感谢各位读者~