探秘DWARF文件

简介

DWARF全名是Debugging With Attribute Record Formats,是一种调试信息的存放格式。DWARF是一种通用的标准格式,很多编译器对其都有支持,并且DWARF支持COCgo等多种语言。

DWARF的作用是对机器码和代码之间进行一个映射关系,用来为调试器DEBUG提供调试信息,告诉调试器代码和二进制如何关联,在代码中添加一个断点后,在程序运行到什么位置要停下来。如果想开发一个Linux调试器,DWARF是必须要学的。除此之外,DWARF在发生崩溃时提供映射信息,对崩溃堆栈进行符号化。

对于iOS开发者来说,dSYM中主要就是DWARF文件,Xcode使用的LLVMGCC编译器内核,对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_variableDW_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,基础数据类型,例如longint这类
  • DW_TAG_structure_type,结构体,一个类本质上也是个结构体,如果DW_AT_APPLE_runtime_classDW_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,类型,例如SELNSString

指针类型

除了描述单个对象外,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

DWARFMach-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定位原理

首先确定崩溃堆栈的uuiddSYM能对应上,随后才是分析崩溃堆栈的过程。

当发生崩溃时,堆栈一般如下格式,红圈内有三个地址。最前面的是崩溃堆栈,第二个是所在二进制的地址,后面是偏移量。

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中存在两个选项,DWARFDWARF with dSYM File,通常在debug时我们会将这个选项设置为DWARF,这能带来编译速度的提升。

选择DWARF方式在编译产物的dSYM文件夹下,不会出现dSYM文件,如果选择DWARF with dSYM File则会以target为纬度,生成一到多个dSYM文件。一个压缩后的DWARF文件,通常和导出的ipa包大小差不多,其内部结构比较复杂,这也是其能使编译提速的原因。

到此,对于DWARF的介绍就讲完了,感谢各位读者~

相关推荐
恋猫de小郭6 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅12 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606113 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了13 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅13 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅14 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
未来侦察班14 小时前
一晃13年过去了,苹果的Airdrop依然很坚挺。
macos·ios·苹果vision pro
崔庆才丨静觅14 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment14 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅14 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端