llvm-debuginfo-analyzer - 打印低级调试信息的逻辑表示。¶
摘要¶
llvm-debuginfo-analyzer [选项] [文件名 …]
描述¶
llvm-debuginfo-analyzer 解析二进制目标文件中的调试和文本段,并以逻辑视图的形式打印其内容,逻辑视图是一种人类可读的表示形式,与原始用户源代码的结构非常接近。支持的目标文件格式包括 ELF、Mach-O、WebAssembly、PDB 和 COFF。
逻辑视图抽象了与嵌入在目标文件中的调试信息的不同低级表示形式相关的复杂性。llvm-debuginfo-analyzer 生成调试信息的规范视图,而不管其格式如何。假设调试信息正确地表示相同的原始源代码,则无论目标文件格式如何,都将看到相同的逻辑视图。
逻辑视图包含以下逻辑元素:类型、作用域、符号和行,这些是 C/C++ 编程语言中使用的基本软件元素。每个逻辑元素都有一组属性,例如类型、类、函数、变量、参数等。--attribute
可用于指定打印逻辑元素时要包含哪些属性。逻辑元素可能具有描述特定元素类型的种类。例如,作用域可以具有函数、类、命名空间的种类值。
llvm-debuginfo-analyzer 默认打印预定义的逻辑元素和属性布局。命令行选项可用于控制打印的元素(--print
),使用特定的布局(--report
),匹配给定的模式(--select
,--select-offsets
)。此外,可以使用(--select-lines
,--select-scopes
,--select-symbols
,--select-types
)将输出限制为指定的逻辑元素。
llvm-debuginfo-analyzer 还可以比较一组逻辑视图(--compare
),以查找差异并识别任何目标文件中可能的调试信息语法问题(--warning
)。
选项¶
llvm-debuginfo-analyzer 选项分为几个类别,每个类别都针对不同的目的进行定制
常规¶
本节介绍用于显示用法、版本、响应文件等的标准帮助选项。
- -h, --help¶
显示此命令的帮助和用法。(更多信息请参见 –help-hidden)。
- --help-list¶
显示此命令的帮助和用法,但不将选项分组到类别中(更多信息请参见 –help-list-hidden)。
显示所有可用选项。
- --print-all-options¶
在命令行解析后打印所有选项值。
- --print-options¶
在命令行解析后打印非默认选项
- --version¶
显示工具的版本。
- @<FILE>¶
从<FILE>读取命令行选项。
如果未指定输入文件,llvm-debuginfo-analyzer 默认读取a.out,并在找不到输入文件时返回错误。
如果使用-作为输入文件,llvm-debuginfo-analyzer 将从其标准输入流读取输入。
属性¶
以下选项启用为打印的元素提供的属性。属性根据添加的数据类型(例如:二进制文件中的内部偏移量、位置描述符、寄存器名称、用户源文件名、其他元素转换、工具链名称、二进制文件格式等)分为几类。
- --attribute=<value[,value,...]>¶
其中value是以下列表中的一个选项。
=all: Include all the below attributes. =extended: Add low-level attributes. =standard: Add standard high-level attributes.
以下属性描述了逻辑元素的最常见信息。它们有助于识别词法作用域级别;跨模块的元素可见性(全局、局部);生成二进制文件的工具链名称。
=global: Element referenced across Compile Units. =format: Object file format name. =level: Lexical scope level (File=0, Compile Unit=1). =local: Element referenced only in the Compile Unit. =producer: Toolchain identification name.
以下属性描述了来自用户源代码的文件和目录名称,其中声明或定义了元素;跨模块具有公共可见性的函数。这些选项允许将元素映射到其用户代码位置,以进行交叉引用目的。
=directories: Directories referenced in the debug information. =filename: Filename where the element is defined. =files: Files referenced in the debug information. =pathname: Pathname where the object is defined. =publics: Function names that are public.
以下属性描述了其他逻辑元素源转换,以便显示内置类型(int、bool 等);模板实例化期间使用的参数和参数;父名称层次结构;数组维度信息;编译器生成的元素以及与类型别名关联的基础类型。
=argument: Template parameters replaced by its arguments. =base: Base types (int, bool, etc.). =generated: Compiler generated elements. =encoded: Template arguments encoded in the template name. =qualified: The element type include parents in its name. =reference: Element declaration and definition references. =subrange: Subrange encoding information for arrays. =typename: Template parameters. =underlying: Underlying type for type definitions.
以下属性描述了符号或作用域的调试位置信息。它包括符号百分比覆盖率以及位置布局中的任何间隙;确定附加到函数的代码部分的范围。当使用描述符时,将显示目标处理器的寄存器。
=coverage: Symbol location coverage. =gaps: Missing debug location (gaps). =location: Symbol debug location. =range: Debug location ranges. =register: Processor register names.
以下属性与低级细节相关联,例如:二进制文件中的偏移量;为了区分特定实例而添加到内联函数的行中的鉴别器;调试行状态机寄存器;编译器(内联)或链接器优化(死代码消除)丢弃的元素;MS 工具链在 PDB 中生成的系统编译单元。
=discarded: Discarded elements by the linker. =discriminator: Discriminators for inlined function instances. =inserted: Generated inlined abstract references. =linkage: Object file linkage name. =offset: Debug information offset. =qualifier: Line qualifiers (Newstatement, BasicBlock, etc). =zero: Zero line numbers.
以下属性描述了PE/COFF文件格式的特定信息。它包括 MS 运行时类型。
=system: Display PDB's MS system elements.
上述属性被分组为标准和扩展类别,可以启用它们。
标准组包含那些添加足够信息来描述逻辑元素并可以在处理调试信息时涵盖正常情况的属性。
=base =coverage =directories =discriminator =filename =files =format =level =producer =publics =range =reference =zero
扩展组包含那些需要更深入了解调试信息才能理解的属性。当需要较低级别的细节时,它们才是有用的。
=argument =discarded =encoded =gaps =generated =global =inserted =linkage =local =location =offset =operation =pathname =qualified =qualifier =register =subrange =system =typename
打印¶
以下选项描述了要打印的元素。使用的布局由--report
确定。在树形布局中,所有元素都会打印其包含的词法作用域,即使没有明确指定。
- --print=<value[,value,...]>¶
其中value是以下列表中的一个选项。
=all: Include all the below attributes.
以下选项打印请求的元素;在任何给定的选择条件(
--select
)下,只有与之匹配的元素才会被打印。元素值是一种便捷的方式,可以一次性指定指令、行、作用域、符号和类型。=elements: Instructions, lines, scopes, symbols and types. =instructions: Assembler instructions for code sections. =lines: Source lines referenced in the debug information. =scopes: Lexical blocks (function, class, namespace, etc). =symbols: Symbols (variable, member, parameter, etc). =types: Types (pointer, reference, type alias, etc).
以下选项打印在创建元素期间收集的信息,例如:作用域对调试信息的贡献;已创建、打印或匹配的元素摘要(
--select
);视图创建期间产生的警告。=sizes: Debug Information scopes contributions. =summary: Summary of elements allocated, selected or printed. =warnings: Warnings detected.
注意:–print=sizes选项是特定于ELF的。
输出¶
以下选项描述了如何控制打印逻辑元素时生成的输出。
- --output-file=<path>¶
将输出重定向到由<path>指定的的文件,其中-是标准输出流。
llvm-debuginfo-analyzer具有拆分视图的概念。当从复杂的二进制格式重定向输出时,它会被分割成单独的文件,每个文件包含单个编译单元的逻辑视图输出。
- --output-folder=<name>¶
当指定–output=split时,写入每个编译单元文件的文件夹。
- --output-level=<level>¶
仅打印最多给定词法级别值的元素。输入文件位于词法级别零,编译单元位于词法级别一。
- --output=<value[,value,...]>¶
其中value是以下列表中的一个选项。
=all: Include all the below outputs.
=json: Use JSON as the output format (Not implemented). =split: Split the output by Compile Units. =text: Use a free form text output.
- --output-sort=<key>¶
在输出中对元素进行排序时的主键(默认值:行)。按逻辑元素种类排序需要熟悉元素种类选择选项(
--select-lines
,--select-scopes
,--select-symbols
,--select-types
),因为这些选项描述了不同的逻辑元素种类。=kind: Sort by element kind. =line: Sort by element line number. =name: Sort by element name. =offset: Sort by element offset.
报告¶
根据正在执行的任务(打印、比较、选择),支持多种布局以更合适的方式显示元素,使输出更容易理解。
- --report=<value[,value,...]>¶
其中value是以下列表中的一个选项。
=all: Include all the below reports.
=children: Elements and children are displayed in a tree format. =list: Elements are displayed in a tabular format. =parents: Elements and parents are displayed in a tree format. =view: Elements, parents and children are displayed in a tree format.
列表布局以表格形式呈现逻辑元素,没有任何父子关系。这可能是显示与特定条件匹配的元素的首选方式,以便在比较逻辑视图时更容易找到差异。
子节点、父节点和视图布局以树状格式显示元素,其中作用域表示其节点,类型、符号、行和其他作用域表示子节点。该布局显示了元素之间的词法作用域关系,二进制文件是树的根(级别0),每个编译单元都是子节点(级别1)。
子节点布局包含与任何给定条件(--select
)或(--compare
)匹配的元素及其子节点。
父节点布局包含与任何给定条件(--select
)或(--compare
)匹配的元素及其父节点。
组合的视图布局包含与任何给定条件(--select
)或(--compare
)匹配的元素,及其父节点和子节点。
备注:
当使用选择条件(
--select
)但没有指定报告选项时,将选择列表布局。比较模式始终使用视图布局。
选择¶
打印元素时,可以包含不同的数据,并且这些数据会因(--attribute
)而异,从与二进制文件直接关联的数据(偏移量)到高级细节(如覆盖率、词法作用域级别、位置)。由于打印的输出可能达到相当大的大小,因此一些选择选项可以启用特定元素的打印。
模式匹配可以忽略大小写(--select-nocase
),并扩展为使用正则表达式(--select-regex
)。
元素¶
以下选项允许打印与给定<pattern>、偏移量<value>或元素<condition>匹配的元素。
- --select=<pattern>¶
打印名称或行号与给定<pattern>匹配的所有元素。
- --select-offsets=<value[,value,...]>¶
打印偏移量与给定值匹配的所有元素。请参阅
--attribute
选项。
- --select-elements=<condition[,condition,...]>¶
打印满足给定<condition>的所有元素。其中condition是以下列表中的一个选项。
=discarded: Discarded elements by the linker. =global: Element referenced across Compile Units. =optimized: Optimized inlined abstract references.
- --select-regex¶
在使用
--select
选项进行选择时,将任何<pattern>字符串视为正则表达式。如果指定了--select-nocase
,则正则表达式将不区分大小写。
如果<pattern>条件过于宽泛,可以指定更具选择性的选项来定位特定类别的元素:行(--select-lines
)、作用域(--select-scopes
)、符号(--select-symbols
)和类型(--select-types
)。
这些选项需要了解调试信息格式(DWARF、CodeView),因为给定的kind描述了一种非常具体的元素类型。
行¶
以下选项允许打印与给定<kind>匹配的行。给定的条件描述了调试行状态机寄存器。
- --select-lines=<kind[,kind,...]>¶
其中kind是以下列表中的一个选项。
=AlwaysStepInto: marks an always step into. =BasicBlock: Marks a new basic block. =Discriminator: Line that has a discriminator. =EndSequence: Marks the end in the sequence of lines. =EpilogueBegin: Marks the start of a function epilogue. =LineDebug: Lines that correspond to debug lines. =LineAssembler: Lines that correspond to disassembly text. =NeverStepInto: marks a never step into. =NewStatement: Marks a new statement. =PrologueEnd: Marks the end of a function prologue.
作用域¶
以下选项允许打印与给定<kind>匹配的作用域。
- --select-scopes=<kind[,kind,...]>¶
其中kind是以下列表中的一个选项。
=Aggregate: A class, structure or union. =Array: An array. =Block: A generic block (lexical block or exception block). =CallSite: A call site. =CatchBlock: An exception block. =Class: A class. =CompileUnit: A compile unit. =EntryPoint: A subroutine entry point. =Enumeration: An enumeration. =Function: A function. =FunctionType: A function pointer. =InlinedFunction: An inlined function. =Label: A label. =LexicalBlock: A lexical block. =Namespace: A namespace. =Root: The element representing the main scope. =Structure: A structure. =Subprogram: A subprogram. =Template: A template definition. =TemplateAlias: A template alias. =TemplatePack: A template pack. =TryBlock: An exception try block. =Union: A union.
符号¶
以下选项允许打印与给定<kind>匹配的符号。
- --select-symbols=<kind[,kind,...]>¶
其中kind是以下列表中的一个选项。
=CallSiteParameter: A call site parameter. =Constant: A constant symbol. =Inheritance: A base class. =Member: A member class. =Parameter: A parameter to function. =Unspecified: Unspecified parameters to function. =Variable: A variable.
类型¶
以下选项允许打印与给定<kind>匹配的类型。
- --select-types=<kind[,kind,...]>¶
其中kind是以下列表中的一个选项。
=Base: Base type (integer, boolean, etc). =Const: Constant specifier. =Enumerator: Enumerator. =Import: Import declaration. =ImportDeclaration: Import declaration. =ImportModule: Import module. =Pointer: Pointer type. =PointerMember: Pointer to member function. =Reference: Reference type. =Restrict: Restrict specifier. =RvalueReference: R-value reference. =Subrange: Array subrange. =TemplateParam: Template parameter. =TemplateTemplateParam: Template template parameter. =TemplateTypeParam: Template type parameter. =TemplateValueParam: Template value parameter. =Typedef: Type definition. =Unspecified: Unspecified type. =Volatile: Volatile specifier.
比较¶
在处理调试信息时,有些情况下,打印元素并不是正确的方法。例如,当我们关注同一工具链的不同版本引起的影响,或特定编译器优化带来的影响时。
对于这些情况,我们需要查看哪些元素被添加或删除了。由于调试信息格式复杂,使用常规的 diff 工具查找这些元素非常困难,甚至在处理不同的调试格式时是不可能的。
llvm-debuginfo-analyzer 支持逻辑元素比较,可以找到由不同工具链版本甚至不同调试信息格式生成的逻辑视图之间的语义差异。
当比较来自不同调试格式创建的逻辑视图时,其准确性取决于调试信息对用户代码的表示程度。例如,从包含 DWARF 调试信息的二进制文件创建的逻辑视图可能包含比从包含 CodeView 调试信息的二进制文件创建的逻辑视图更详细的数据。
以下选项描述了要比较的元素。
- --compare=<value[,value,...]>¶
其中value是以下列表中的一个选项。
=all: Include all the below elements.
=lines: Include lines. =scopes: Include scopes. =symbols: Include symbols. =types: Include types.
llvm-debuginfo-analyzer 将命令行上的第一个二进制文件作为**参考**,第二个作为**目标**。为了获得更具描述性的报告,比较会进行两次。参考和目标视图会互换,以便生成目标视图中**缺失**的元素以及参考视图中**新增**的元素。
请参阅 --report
选项,了解如何描述比较报告。
警告¶
在读取输入目标文件时,llvm-debuginfo-analyzer 可以检测原始调试信息中的问题。这些问题可能不被认为会影响打印逻辑视图的目的,但它们可以指示调试信息的质量,并可能暴露生成调试信息的问题。
以下选项描述了要记录的警告,以便在以后打印时使用,如果它们被 --print
请求。
- --warning=<value[,value,...]>¶
其中value是以下列表中的一个选项。
=all: Include all the below warnings.
以下选项在创建逻辑视图期间收集其他信息,包括符号的无效覆盖范围值和位置;无效代码范围;值为零的行。
=coverages: Invalid symbol coverages values. =lines: Debug lines that are zero. =locations: Invalid symbol locations. =ranges: Invalid code ranges.
内部¶
为了更好地理解逻辑视图,可能需要访问更详细的内部信息。此类数据将有助于识别已处理的调试信息或不正确的逻辑元素管理。通常,这些类型的选项仅在调试版本中可用。
llvm-debuginfo-analyzer 在发布和调试版本中都支持这些高级选项,但仅在调试版本中生成的唯一 ID 除外。
- --internal=<value[,value,...]>¶
其中value是以下列表中的一个选项。
=all: Include all the below options.
以下选项允许检查逻辑视图的完整性;收集已处理或未实现的调试标签;忽略逻辑元素行号,以便在使用外部比较工具时促进逻辑视图比较;打印用于调用 llvm-debuginfo-analyzer 的命令行选项。
=id: Print unique element ID. =cmdline: Print command line. =integrity: Check elements integrity. =none: Ignore element line number. =tag: Debug information tags.
注意:对于 ELF 格式,收集的标签表示未处理的调试标签。对于 PE/COFF 格式,它们表示已处理的标签。
示例¶
本节包含一些真实的二进制文件,以展示如何使用 llvm-debuginfo-analyzer 打印逻辑视图并诊断可能的调试信息问题。
测试用例 1 - 常规选项¶
以下示例用于展示 llvm-debuginfo-analyzer 生成的不同输出。我们使用 Clang(-O0 -g)为 X86 ELF 目标编译了示例。
1 using INTPTR = const int *;
2 int foo(INTPTR ParamPtr, unsigned ParamUnsigned, bool ParamBool) {
3 if (ParamBool) {
4 typedef int INTEGER;
5 const INTEGER CONSTANT = 7;
6 return CONSTANT;
7 }
8 return ParamUnsigned;
9 }
打印模式¶
在此模式下,llvm-debuginfo-analyzer 会根据标准模式(包括正则表达式)打印逻辑视图或其一部分,以选择要包含在输出中的逻辑元素类型。
基本细节¶
以下命令打印所有逻辑元素的基本详细信息,并按调试信息内部偏移量排序;它包括其词法级别和调试信息格式。
llvm-debuginfo-analyzer --attribute=level,format
--output-sort=offset
--print=scopes,symbols,types,lines,instructions
test-dwarf-clang.o
或
llvm-debuginfo-analyzer --attribute=level,format
--output-sort=offset
--print=elements
test-dwarf-clang.o
每一行代表调试信息中存在的元素。第一列表示作用域级别,后面是关联的行号(如果有),最后是元素的描述。
Logical View:
[000] {File} 'test-dwarf-clang.o' -> elf64-x86-64
[001] {CompileUnit} 'test.cpp'
[002] 2 {Function} extern not_inlined 'foo' -> 'int'
[003] 2 {Parameter} 'ParamPtr' -> 'INTPTR'
[003] 2 {Parameter} 'ParamUnsigned' -> 'unsigned int'
[003] 2 {Parameter} 'ParamBool' -> 'bool'
[003] {Block}
[004] 5 {Variable} 'CONSTANT' -> 'const INTEGER'
[004] 5 {Line}
[004] {Code} 'movl $0x7, -0x1c(%rbp)'
[004] 6 {Line}
[004] {Code} 'movl $0x7, -0x4(%rbp)'
[004] {Code} 'jmp 0x6'
[004] 8 {Line}
[004] {Code} 'movl -0x14(%rbp), %eax'
[003] 4 {TypeAlias} 'INTEGER' -> 'int'
[003] 2 {Line}
[003] {Code} 'pushq %rbp'
[003] {Code} 'movq %rsp, %rbp'
[003] {Code} 'movb %dl, %al'
[003] {Code} 'movq %rdi, -0x10(%rbp)'
[003] {Code} 'movl %esi, -0x14(%rbp)'
[003] {Code} 'andb $0x1, %al'
[003] {Code} 'movb %al, -0x15(%rbp)'
[003] 3 {Line}
[003] {Code} 'testb $0x1, -0x15(%rbp)'
[003] {Code} 'je 0x13'
[003] 8 {Line}
[003] {Code} 'movl %eax, -0x4(%rbp)'
[003] 9 {Line}
[003] {Code} 'movl -0x4(%rbp), %eax'
[003] {Code} 'popq %rbp'
[003] {Code} 'retq'
[003] 9 {Line}
[002] 1 {TypeAlias} 'INTPTR' -> '* const int'
仔细检查后,我们可以看到可能存在潜在的调试问题
[003] {Block}
[003] 4 {TypeAlias} 'INTEGER' -> 'int'
‘INTEGER’ 定义位于级别 [003],与匿名 {Block}('if' 语句的'true' 分支)位于同一词法作用域,而在原始源代码中,typedef 语句显然位于该块内,因此 ‘INTEGER’ 定义也应位于块内的级别 [004]。
选择逻辑元素¶
以下打印所有名称或类型中包含 ‘inte’ 或 ‘movl’ 的指令、符号和类型,使用制表符布局并给出匹配次数。
llvm-debuginfo-analyzer --attribute=level
--select-nocase --select-regex
--select=INTe --select=movl
--report=list
--print=symbols,types,instructions,summary
test-dwarf-clang.o
Logical View:
[000] {File} 'test-dwarf-clang.o'
[001] {CompileUnit} 'test.cpp'
[003] {Code} 'movl $0x7, -0x1c(%rbp)'
[003] {Code} 'movl $0x7, -0x4(%rbp)'
[003] {Code} 'movl %eax, -0x4(%rbp)'
[003] {Code} 'movl %esi, -0x14(%rbp)'
[003] {Code} 'movl -0x14(%rbp), %eax'
[003] {Code} 'movl -0x4(%rbp), %eax'
[003] 4 {TypeAlias} 'INTEGER' -> 'int'
[004] 5 {Variable} 'CONSTANT' -> 'const INTEGER'
-----------------------------
Element Total Found
-----------------------------
Scopes 3 0
Symbols 4 1
Types 2 1
Lines 17 6
-----------------------------
Total 26 8
比较模式¶
在此模式下,llvm-debuginfo-analyzer 会比较逻辑视图以生成报告,其中包含缺失或新增的逻辑元素。这对于查找不同工具链版本或甚至完全不同的工具链(例如,生成 DWARF 的编译器可以直接与生成 CodeView 的完全不同的编译器进行比较)生成的调试信息中的语义差异非常有用。
鉴于前面的示例,我们通过与另一个编译器进行比较,发现了上述调试信息问题(与之前 ‘typedef int INTEGER’ 的无效作用域位置相关)。
使用 GCC 生成 test-dwarf-gcc.o,我们可以应用选择模式和打印模式以获得以下逻辑视图输出。
llvm-debuginfo-analyzer --attribute=level
--select-regex --select-nocase --select=INTe
--report=list
--print=symbols,types
test-dwarf-clang.o test-dwarf-gcc.o
Logical View:
[000] {File} 'test-dwarf-clang.o'
[001] {CompileUnit} 'test.cpp'
[003] 4 {TypeAlias} 'INTEGER' -> 'int'
[004] 5 {Variable} 'CONSTANT' -> 'const INTEGER'
Logical View:
[000] {File} 'test-dwarf-gcc.o'
[001] {CompileUnit} 'test.cpp'
[004] 4 {TypeAlias} 'INTEGER' -> 'int'
[004] 5 {Variable} 'CONSTANT' -> 'const INTEGER'
输出显示两个对象包含相同的元素。但是 ‘typedef INTEGER’ 位于不同的作用域级别。GCC 生成的对象显示 ‘4’,这是正确的值。
请注意,GCC 不需要生成与 Clang 相同或相似的 DWARF 才能进行比较。我们只比较语义。比较 MSVC 和 Clang 生成的 CodeView 调试信息时也是如此。
有两种比较方法:逻辑视图和逻辑元素。
逻辑视图¶
它将逻辑视图作为一个整体单元进行比较;对于匹配,每个比较的逻辑元素必须具有相同的父级和子级。
使用 llvm-debuginfo-analyzer 的比较功能,可以在更全局的上下文中看到该问题,其中可能包括逻辑视图。
输出以视图形式显示**缺失(-)、新增(+)**元素,通过交换参考和目标对象文件提供更多上下文。
llvm-debuginfo-analyzer --attribute=level
--compare=types
--report=view
--print=symbols,types
test-dwarf-clang.o test-dwarf-gcc.o
Reference: 'test-dwarf-clang.o'
Target: 'test-dwarf-gcc.o'
Logical View:
[000] {File} 'test-dwarf-clang.o'
[001] {CompileUnit} 'test.cpp'
[002] 1 {TypeAlias} 'INTPTR' -> '* const int'
[002] 2 {Function} extern not_inlined 'foo' -> 'int'
[003] {Block}
[004] 5 {Variable} 'CONSTANT' -> 'const INTEGER'
+[004] 4 {TypeAlias} 'INTEGER' -> 'int'
[003] 2 {Parameter} 'ParamBool' -> 'bool'
[003] 2 {Parameter} 'ParamPtr' -> 'INTPTR'
[003] 2 {Parameter} 'ParamUnsigned' -> 'unsigned int'
-[003] 4 {TypeAlias} 'INTEGER' -> 'int'
输出显示合并视图路径(参考和目标)以及缺失和新增的元素。
逻辑元素¶
它比较各个逻辑元素,而不考虑它们的父级是否相同。对于这两种比较方法,相等标准包括名称、源代码位置、类型、词法作用域级别。
llvm-debuginfo-analyzer --attribute=level
--compare=types
--report=list
--print=symbols,types,summary
test-dwarf-clang.o test-dwarf-gcc.o
Reference: 'test-dwarf-clang.o'
Target: 'test-dwarf-gcc.o'
(1) Missing Types:
-[003] 4 {TypeAlias} 'INTEGER' -> 'int'
(1) Added Types:
+[004] 4 {TypeAlias} 'INTEGER' -> 'int'
----------------------------------------
Element Expected Missing Added
----------------------------------------
Scopes 4 0 0
Symbols 0 0 0
Types 2 1 1
Lines 0 0 0
----------------------------------------
Total 6 1 1
更改参考和目标顺序
llvm-debuginfo-analyzer --attribute=level
--compare=types
--report=list
--print=symbols,types,summary
test-dwarf-gcc.o test-dwarf-clang.o
Reference: 'test-dwarf-gcc.o'
Target: 'test-dwarf-clang.o'
(1) Missing Types:
-[004] 4 {TypeAlias} 'INTEGER' -> 'int'
(1) Added Types:
+[003] 4 {TypeAlias} 'INTEGER' -> 'int'
----------------------------------------
Element Expected Missing Added
----------------------------------------
Scopes 4 0 0
Symbols 0 0 0
Types 2 1 1
Lines 0 0 0
----------------------------------------
Total 6 1 1
由于参考和目标已切换,因此第一个案例中的新增类型现在被列为缺失类型。
测试用例 2 - 汇编指令¶
以下示例用于展示 llvm-debuginfo-analyzer 生成的不同输出。我们使用最新版本的 Clang、GCC 和 MSVC(-O0 -g)为 Windows 和 Linux 编译了 X86 Codeview 和 ELF 目标的示例。
1 extern int printf(const char * format, ... );
2
3 int main()
4 {
5 printf("Hello, World\n");
6 return 0;
7 }
这些是 llvm-debuginfo-analyzer 为 3 个不同的编译器(MSVC、Clang 和 GCC)生成的逻辑视图,在 Windows 和 Linux 上发出不同的调试信息格式(CodeView、DWARF)。
llvm-debuginfo-analyzer --attribute=level,format,producer
--print=lines,instructions
hello-world-codeview-clang.o
hello-world-codeview-msvc.o
hello-world-dwarf-clang.o
hello-world-dwarf-gcc.o
CodeView - Clang (Windows)¶
Logical View:
[000] {File} 'hello-world-codeview-clang.o' -> COFF-x86-64
[001] {CompileUnit} 'hello-world.cpp'
[002] {Producer} 'clang version 14.0.0'
[002] {Function} extern not_inlined 'main' -> 'int'
[003] 4 {Line}
[003] {Code} 'subq $0x28, %rsp'
[003] {Code} 'movl $0x0, 0x24(%rsp)'
[003] 5 {Line}
[003] {Code} 'leaq (%rip), %rcx'
[003] {Code} 'callq 0x0'
[003] 6 {Line}
[003] {Code} 'xorl %eax, %eax'
[003] {Code} 'addq $0x28, %rsp'
[003] {Code} 'retq'
CodeView - MSVC (Windows)¶
Logical View:
[000] {File} 'hello-world-codeview-msvc.o' -> COFF-i386
[001] {CompileUnit} 'hello-world.cpp'
[002] {Producer} 'Microsoft (R) Optimizing Compiler'
[002] {Function} extern not_inlined 'main' -> 'int'
[003] 4 {Line}
[003] {Code} 'pushl %ebp'
[003] {Code} 'movl %esp, %ebp'
[003] 5 {Line}
[003] {Code} 'pushl $0x0'
[003] {Code} 'calll 0x0'
[003] {Code} 'addl $0x4, %esp'
[003] 6 {Line}
[003] {Code} 'xorl %eax, %eax'
[003] 7 {Line}
[003] {Code} 'popl %ebp'
[003] {Code} 'retl'
DWARF - Clang (Linux)¶
Logical View:
[000] {File} 'hello-world-dwarf-clang.o' -> elf64-x86-64
[001] {CompileUnit} 'hello-world.cpp'
[002] {Producer} 'clang version 14.0.0'
[002] 3 {Function} extern not_inlined 'main' -> 'int'
[003] 4 {Line}
[003] {Code} 'pushq %rbp'
[003] {Code} 'movq %rsp, %rbp'
[003] {Code} 'subq $0x10, %rsp'
[003] {Code} 'movl $0x0, -0x4(%rbp)'
[003] 5 {Line}
[003] {Code} 'movabsq $0x0, %rdi'
[003] {Code} 'movb $0x0, %al'
[003] {Code} 'callq 0x0'
[003] 6 {Line}
[003] {Code} 'xorl %eax, %eax'
[003] {Code} 'addq $0x10, %rsp'
[003] {Code} 'popq %rbp'
[003] {Code} 'retq'
[003] 6 {Line}
DWARF - GCC (Linux)¶
Logical View:
[000] {File} 'hello-world-dwarf-gcc.o' -> elf64-x86-64
[001] {CompileUnit} 'hello-world.cpp'
[002] {Producer} 'GNU C++14 9.3.0'
[002] 3 {Function} extern not_inlined 'main' -> 'int'
[003] 4 {Line}
[003] {Code} 'endbr64'
[003] {Code} 'pushq %rbp'
[003] {Code} 'movq %rsp, %rbp'
[003] 5 {Line}
[003] {Code} 'leaq (%rip), %rdi'
[003] {Code} 'movl $0x0, %eax'
[003] {Code} 'callq 0x0'
[003] 6 {Line}
[003] {Code} 'movl $0x0, %eax'
[003] 7 {Line}
[003] {Code} 'popq %rbp'
[003] {Code} 'retq'
[003] 7 {Line}
逻辑视图显示混合的行和汇编指令,允许比较不同工具链生成的代码。
测试用例 3 - 类型定义的错误词法作用域¶
以下示例用于展示 llvm-debuginfo-analyzer 生成的不同输出。我们使用最新版本的 Clang、GCC 和 MSVC(-O0 -g)为 X86 Codeview 和 ELF 目标编译了示例。
1 int bar(float Input) { return (int)Input; }
2
3 unsigned foo(char Param) {
4 typedef int INT; // ** Definition for INT **
5 INT Value = Param;
6 {
7 typedef float FLOAT; // ** Definition for FLOAT **
8 {
9 FLOAT Added = Value + Param;
10 Value = bar(Added);
11 }
12 }
13 return Value + Param;
14 }
上述测试用于说明在 Clang 编译器中发现的作用域问题:PR44884 (Bugs LLVM) / PR44229 (GitHub LLVM)
第 4 行和第 7 行包含 2 个类型定义,定义在不同的词法作用域中。
4 typedef int INT;
7 typedef float FLOAT;
这些是 llvm-debuginfo-analyzer 为 3 个不同的编译器(MSVC、Clang 和 GCC)生成的逻辑视图,在不同平台上发出不同的调试信息格式(CodeView、DWARF)。
llvm-debuginfo-analyzer --attribute=level,format,producer
--print=symbols,types,lines
--output-sort=kind
pr-44884-codeview-clang.o
pr-44884-codeview-msvc.o
pr-44884-dwarf-clang.o
pr-44884-dwarf-gcc.o
CodeView - Clang (Windows)¶
Logical View:
[000] {File} 'pr-44884-codeview-clang.o' -> COFF-x86-64
[001] {CompileUnit} 'pr-44884.cpp'
[002] {Producer} 'clang version 14.0.0'
[002] {Function} extern not_inlined 'bar' -> 'int'
[003] {Parameter} 'Input' -> 'float'
[003] 1 {Line}
[002] {Function} extern not_inlined 'foo' -> 'unsigned'
[003] {Block}
[004] {Variable} 'Added' -> 'float'
[004] 9 {Line}
[004] 10 {Line}
[003] {Parameter} 'Param' -> 'char'
[003] {TypeAlias} 'FLOAT' -> 'float'
[003] {TypeAlias} 'INT' -> 'int'
[003] {Variable} 'Value' -> 'int'
[003] 3 {Line}
[003] 5 {Line}
[003] 13 {Line}
CodeView - MSVC (Windows)¶
Logical View:
[000] {File} 'pr-44884-codeview-msvc.o' -> COFF-i386
[001] {CompileUnit} 'pr-44884.cpp'
[002] {Producer} 'Microsoft (R) Optimizing Compiler'
[002] {Function} extern not_inlined 'bar' -> 'int'
[003] {Variable} 'Input' -> 'float'
[003] 1 {Line}
[002] {Function} extern not_inlined 'foo' -> 'unsigned'
[003] {Block}
[004] {Block}
[005] {Variable} 'Added' -> 'float'
[004] {TypeAlias} 'FLOAT' -> 'float'
[004] 9 {Line}
[004] 10 {Line}
[003] {TypeAlias} 'INT' -> 'int'
[003] {Variable} 'Param' -> 'char'
[003] {Variable} 'Value' -> 'int'
[003] 3 {Line}
[003] 5 {Line}
[003] 13 {Line}
[003] 14 {Line}
DWARF - Clang (Linux)¶
Logical View:
[000] {File} 'pr-44884-dwarf-clang.o' -> elf64-x86-64
[001] {CompileUnit} 'pr-44884.cpp'
[002] {Producer} 'clang version 14.0.0'
[002] 1 {Function} extern not_inlined 'bar' -> 'int'
[003] 1 {Parameter} 'Input' -> 'float'
[003] 1 {Line}
[003] 1 {Line}
[003] 1 {Line}
[002] 3 {Function} extern not_inlined 'foo' -> 'unsigned int'
[003] {Block}
[004] 9 {Variable} 'Added' -> 'FLOAT'
[004] 9 {Line}
[004] 9 {Line}
[004] 9 {Line}
[004] 9 {Line}
[004] 9 {Line}
[004] 10 {Line}
[004] 10 {Line}
[004] 10 {Line}
[004] 13 {Line}
[003] 3 {Parameter} 'Param' -> 'char'
[003] 7 {TypeAlias} 'FLOAT' -> 'float'
[003] 4 {TypeAlias} 'INT' -> 'int'
[003] 5 {Variable} 'Value' -> 'INT'
[003] 3 {Line}
[003] 5 {Line}
[003] 5 {Line}
[003] 13 {Line}
[003] 13 {Line}
[003] 13 {Line}
[003] 13 {Line}
DWARF - GCC (Linux)¶
Logical View:
[000] {File} 'pr-44884-dwarf-gcc.o' -> elf32-littlearm
[001] {CompileUnit} 'pr-44884.cpp'
[002] {Producer} 'GNU C++14 10.2.1 20201103'
[002] 1 {Function} extern not_inlined 'bar' -> 'int'
[003] 1 {Parameter} 'Input' -> 'float'
[003] 1 {Line}
[003] 1 {Line}
[003] 1 {Line}
[002] 3 {Function} extern not_inlined 'foo' -> 'unsigned int'
[003] {Block}
[004] {Block}
[005] 9 {Variable} 'Added' -> 'FLOAT'
[005] 9 {Line}
[005] 9 {Line}
[005] 9 {Line}
[005] 10 {Line}
[005] 13 {Line}
[004] 7 {TypeAlias} 'FLOAT' -> 'float'
[003] 3 {Parameter} 'Param' -> 'char'
[003] 4 {TypeAlias} 'INT' -> 'int'
[003] 5 {Variable} 'Value' -> 'INT'
[003] 3 {Line}
[003] 5 {Line}
[003] 13 {Line}
[003] 14 {Line}
[003] 14 {Line}
从前面的逻辑视图中,我们可以看到 Clang 编译器 **在相同的词法作用域 (3) 中发出了两个 typedef**,这是错误的。GCC 和 MSVC 为这两个 typedef 发出了正确的词法作用域。
使用 llvm-debuginfo-analyzer 的选择功能,我们可以生成一个简单的表格输出,仅显示属于 **Typedef** 的逻辑类型。
llvm-debuginfo-analyzer --attribute=level,format
--output-sort=name
--select-types=Typedef
--report=list
--print=types
pr-44884-*.o
Logical View:
[000] {File} 'pr-44884-codeview-clang.o' -> COFF-x86-64
[001] {CompileUnit} 'pr_44884.cpp'
[003] {TypeAlias} 'FLOAT' -> 'float'
[003] {TypeAlias} 'INT' -> 'int'
Logical View:
[000] {File} 'pr-44884-codeview-msvc.o' -> COFF-i386
[001] {CompileUnit} 'pr_44884.cpp'
[004] {TypeAlias} 'FLOAT' -> 'float'
[003] {TypeAlias} 'INT' -> 'int'
Logical View:
[000] {File} 'pr-44884-dwarf-clang.o' -> elf64-x86-64
[001] {CompileUnit} 'pr_44884.cpp'
[003] 7 {TypeAlias} 'FLOAT' -> 'float'
[003] 4 {TypeAlias} 'INT' -> 'int'
Logical View:
[000] {File} 'pr-44884-dwarf-gcc.o' -> elf32-littlearm
[001] {CompileUnit} 'pr_44884.cpp'
[004] 7 {TypeAlias} 'FLOAT' -> 'float'
[003] 4 {TypeAlias} 'INT' -> 'int'
它还显示,CodeView 调试信息不会为这些逻辑类型生成源代码行号。逻辑视图按类型名称排序。
测试用例 4 - 缺少嵌套枚举¶
以下示例用于展示 llvm-debuginfo-analyzer 生成的不同输出。我们使用最新版本的 Clang、GCC 和 MSVC(-O0 -g)为 X86 Codeview 和 ELF 目标编译了示例。
1 struct Struct {
2 union Union {
3 enum NestedEnum { RED, BLUE };
4 };
5 Union U;
6 };
7
8 Struct S;
9 int test() {
10 return S.U.BLUE;
11 }
以上测试用于说明在 Clang 编译器中发现的一个作用域问题:PR46466 (LLVM 错误库) / PR45811 (GitHub LLVM)
这些是 llvm-debuginfo-analyzer 为 3 个不同的编译器(MSVC、Clang 和 GCC)生成的逻辑视图,在不同平台上发出不同的调试信息格式(CodeView、DWARF)。
llvm-debuginfo-analyzer --attribute=level,format,producer
--output-sort=name
--print=symbols,types
pr-46466-codeview-clang.o
pr-46466-codeview-msvc.o
pr-46466-dwarf-clang.o
pr-46466-dwarf-gcc.o
CodeView - Clang (Windows)¶
Logical View:
[000] {File} 'pr-46466-codeview-clang.o' -> COFF-x86-64
[001] {CompileUnit} 'pr-46466.cpp'
[002] {Producer} 'clang version 14.0.0'
[002] {Variable} extern 'S' -> 'Struct'
[002] 1 {Struct} 'Struct'
[003] {Member} public 'U' -> 'Union'
[003] 2 {Union} 'Union'
[004] 3 {Enumeration} 'NestedEnum' -> 'int'
[005] {Enumerator} 'BLUE' = '0x1'
[005] {Enumerator} 'RED' = '0x0'
CodeView - MSVC (Windows)¶
Logical View:
[000] {File} 'pr-46466-codeview-msvc.o' -> COFF-i386
[001] {CompileUnit} 'pr-46466.cpp'
[002] {Producer} 'Microsoft (R) Optimizing Compiler'
[002] {Variable} extern 'S' -> 'Struct'
[002] 1 {Struct} 'Struct'
[003] {Member} public 'U' -> 'Union'
[003] 2 {Union} 'Union'
[004] 3 {Enumeration} 'NestedEnum' -> 'int'
[005] {Enumerator} 'BLUE' = '0x1'
[005] {Enumerator} 'RED' = '0x0'
DWARF - Clang (Linux)¶
Logical View:
[000] {File} 'pr-46466-dwarf-clang.o' -> elf64-x86-64
[001] {CompileUnit} 'pr-46466.cpp'
[002] {Producer} 'clang version 14.0.0'
[002] 8 {Variable} extern 'S' -> 'Struct'
[002] 1 {Struct} 'Struct'
[003] 5 {Member} public 'U' -> 'Union'
DWARF - GCC (Linux)¶
Logical View:
[000] {File} 'pr-46466-dwarf-gcc.o' -> elf64-x86-64
[001] {CompileUnit} 'pr-46466.cpp'
[002] {Producer} 'GNU C++14 9.3.0'
[002] 8 {Variable} extern 'S' -> 'Struct'
[002] 1 {Struct} 'Struct'
[003] 5 {Member} public 'U' -> 'Union'
[003] 2 {Union} 'Union'
[004] 3 {Enumeration} 'NestedEnum' -> 'unsigned int'
[005] {Enumerator} 'BLUE' = '0x1'
[005] {Enumerator} 'RED' = '0x0'
从前面的逻辑视图中,我们可以看到 Clang 编译器生成的 DWARF 调试信息不包含对枚举值 **RED** 和 **BLUE** 的任何引用。GCC 生成的 DWARF、Clang 和 MSVC 生成的 CodeView 则包含这些引用。
使用 llvm-debuginfo-analyzer 的选择功能,我们可以生成一个逻辑视图,仅显示属于 **Enumerator** 及其父级的逻辑类型。逻辑视图按类型名称排序。
llvm-debuginfo-analyzer --attribute=format,level
--output-sort=name
--select-types=Enumerator
--report=parents
--print=types
pr-46466-*.o
Logical View:
[000] {File} 'pr-46466-codeview-clang.o' -> COFF-x86-64
[001] {CompileUnit} 'pr-46466.cpp'
[002] 1 {Struct} 'Struct'
[003] 2 {Union} 'Union'
[004] 3 {Enumeration} 'NestedEnum' -> 'int'
[005] {Enumerator} 'BLUE' = '0x1'
[005] {Enumerator} 'RED' = '0x0'
Logical View:
[000] {File} 'pr-46466-codeview-msvc.o' -> COFF-i386
[001] {CompileUnit} 'pr-46466.cpp'
[002] 1 {Struct} 'Struct'
[003] 2 {Union} 'Union'
[004] 3 {Enumeration} 'NestedEnum' -> 'int'
[005] {Enumerator} 'BLUE' = '0x1'
[005] {Enumerator} 'RED' = '0x0'
Logical View:
[000] {File} 'pr-46466-dwarf-clang.o' -> elf64-x86-64
[001] {CompileUnit} 'pr-46466.cpp'
Logical View:
[000] {File} 'pr-46466-dwarf-gcc.o' -> elf64-x86-64
[001] {CompileUnit} 'pr-46466.cpp'
[002] 1 {Struct} 'Struct'
[003] 2 {Union} 'Union'
[004] 3 {Enumeration} 'NestedEnum' -> 'unsigned int'
[005] {Enumerator} 'BLUE' = '0x1'
[005] {Enumerator} 'RED' = '0x0'
使用 llvm-debuginfo-analyzer 的选择功能,我们可以生成一个简单的表格输出,其中包含对属于 **Enumerator** 的逻辑类型的摘要。逻辑视图按类型名称排序。
llvm-debuginfo-analyzer --attribute=format,level
--output-sort=name
--select-types=Enumerator
--print=types,summary
pr-46466-*.o
Logical View:
[000] {File} 'pr-46466-codeview-clang.o' -> COFF-x86-64
[001] {CompileUnit} 'pr-46466.cpp'
[005] {Enumerator} 'BLUE' = '0x1'
[005] {Enumerator} 'RED' = '0x0'
-----------------------------
Element Total Found
-----------------------------
Scopes 5 0
Symbols 2 0
Types 6 2
Lines 0 0
-----------------------------
Total 13 2
Logical View:
[000] {File} 'pr-46466-codeview-msvc.o' -> COFF-i386
[001] {CompileUnit} 'pr-46466.cpp'
[005] {Enumerator} 'BLUE' = '0x1'
[005] {Enumerator} 'RED' = '0x0'
-----------------------------
Element Total Found
-----------------------------
Scopes 5 0
Symbols 2 0
Types 7 2
Lines 0 0
-----------------------------
Total 14 2
Logical View:
[000] {File} 'pr-46466-dwarf-clang.o' -> elf64-x86-64
[001] {CompileUnit} 'pr-46466.cpp'
-----------------------------
Element Total Found
-----------------------------
Scopes 4 0
Symbols 0 0
Types 0 0
Lines 0 0
-----------------------------
Total 4 0
Logical View:
[000] {File} 'pr-46466-dwarf-gcc.o' -> elf64-x86-64
[001] {CompileUnit} 'pr-46466.cpp'
[005] {Enumerator} 'BLUE' = '0x1'
[005] {Enumerator} 'RED' = '0x0'
-----------------------------
Element Total Found
-----------------------------
Scopes 5 0
Symbols 0 0
Types 2 2
Lines 0 0
-----------------------------
Total 7 2
从“**Found**”列下打印的值可以看出,在 Clang 生成的 DWARF 调试信息中没有找到任何 **类型**。
测试用例 5 - 变量的错误词法作用域¶
以下示例用于展示 llvm-debuginfo-analyzer 生成的不同输出。我们使用最新版本的 Clang、GCC 和 MSVC(-O0 -g)为 X86 Codeview 和 ELF 目标编译了示例。
// definitions.h
#ifdef _MSC_VER
#define forceinline __forceinline
#elif defined(__clang__)
#if __has_attribute(__always_inline__)
#define forceinline inline __attribute__((__always_inline__))
#else
#define forceinline inline
#endif
#elif defined(__GNUC__)
#define forceinline inline __attribute__((__always_inline__))
#else
#define forceinline inline
#error
#endif
由于测试依赖于内联编译器选项,因此以上头文件定义了 forceinline。
#include "definitions.h"
1 #include "definitions.h"
2 forceinline int InlineFunction(int Param) {
3 int Var_1 = Param;
4 {
5 int Var_2 = Param + Var_1;
6 Var_1 = Var_2;
7 }
8 return Var_1;
9 }
10
11 int test(int Param_1, int Param_2) {
12 int A = Param_1;
13 A += InlineFunction(Param_2);
14 return A;
15 }
以上测试用于说明在 Clang 编译器中发现的一个变量问题:PR43860 (LLVM 错误库) / PR43205 (GitHub)
这些是 llvm-debuginfo-analyzer 为 3 个不同的编译器(MSVC、Clang 和 GCC)生成的逻辑视图,在不同平台上发出不同的调试信息格式(CodeView、DWARF)。
llvm-debuginfo-analyzer --attribute=level,format,producer
--output-sort=name
--print=symbols
pr-43860-codeview-clang.o
pr-43860-codeview-msvc.o
pr-43860-dwarf-clang.o
pr-43860-dwarf-gcc.o
CODEVIEW - Clang (Windows)¶
Logical View:
[000] {File} 'pr-43860-codeview-clang.o' -> COFF-x86-64
[001] {CompileUnit} 'pr-43860.cpp'
[002] {Producer} 'clang version 14.0.0'
[002] 2 {Function} inlined 'InlineFunction' -> 'int'
[003] {Parameter} '' -> 'int'
[002] {Function} extern not_inlined 'test' -> 'int'
[003] {Variable} 'A' -> 'int'
[003] {InlinedFunction} inlined 'InlineFunction' -> 'int'
[004] {Parameter} 'Param' -> 'int'
[004] {Variable} 'Var_1' -> 'int'
[004] {Variable} 'Var_2' -> 'int'
[003] {Parameter} 'Param_1' -> 'int'
[003] {Parameter} 'Param_2' -> 'int'
CODEVIEW - MSVC (Windows)¶
Logical View:
[000] {File} 'pr-43860-codeview-msvc.o' -> COFF-i386
[001] {CompileUnit} 'pr-43860.cpp'
[002] {Producer} 'Microsoft (R) Optimizing Compiler'
[002] {Function} extern not_inlined 'InlineFunction' -> 'int'
[003] {Block}
[004] {Variable} 'Var_2' -> 'int'
[003] {Variable} 'Param' -> 'int'
[003] {Variable} 'Var_1' -> 'int'
[002] {Function} extern not_inlined 'test' -> 'int'
[003] {Variable} 'A' -> 'int'
[003] {Variable} 'Param_1' -> 'int'
[003] {Variable} 'Param_2' -> 'int'
DWARF - Clang (Linux)¶
Logical View:
[000] {File} 'pr-43860-dwarf-clang.o' -> elf64-x86-64
[001] {CompileUnit} 'pr-43860.cpp'
[002] {Producer} 'clang version 14.0.0'
[002] 2 {Function} extern inlined 'InlineFunction' -> 'int'
[003] {Block}
[004] 5 {Variable} 'Var_2' -> 'int'
[003] 2 {Parameter} 'Param' -> 'int'
[003] 3 {Variable} 'Var_1' -> 'int'
[002] 11 {Function} extern not_inlined 'test' -> 'int'
[003] 12 {Variable} 'A' -> 'int'
[003] 14 {InlinedFunction} inlined 'InlineFunction' -> 'int'
[004] {Block}
[005] {Variable} 'Var_2' -> 'int'
[004] {Parameter} 'Param' -> 'int'
[004] {Variable} 'Var_1' -> 'int'
[003] 11 {Parameter} 'Param_1' -> 'int'
[003] 11 {Parameter} 'Param_2' -> 'int'
DWARF - GCC (Linux)¶
Logical View:
[000] {File} 'pr-43860-dwarf-gcc.o' -> elf64-x86-64
[001] {CompileUnit} 'pr-43860.cpp'
[002] {Producer} 'GNU C++14 9.3.0'
[002] 2 {Function} extern declared_inlined 'InlineFunction' -> 'int'
[003] {Block}
[004] 5 {Variable} 'Var_2' -> 'int'
[003] 2 {Parameter} 'Param' -> 'int'
[003] 3 {Variable} 'Var_1' -> 'int'
[002] 11 {Function} extern not_inlined 'test' -> 'int'
[003] 12 {Variable} 'A' -> 'int'
[003] 13 {InlinedFunction} declared_inlined 'InlineFunction' -> 'int'
[004] {Block}
[005] {Variable} 'Var_2' -> 'int'
[004] {Parameter} 'Param' -> 'int'
[004] {Variable} 'Var_1' -> 'int'
[003] 11 {Parameter} 'Param_1' -> 'int'
[003] 11 {Parameter} 'Param_2' -> 'int'
从前面的逻辑视图中,我们可以看到 Clang 编译器生成的 CodeView 调试信息显示变量 **Var_1** 和 **Var_2** 在函数 **InlineFuction** 中位于相同的词法作用域 (**4**)。GCC/Clang 生成的 DWARF 和 MSVC 生成的 CodeView 将这些变量显示在其正确的词法作用域:分别为 **3** 和 **4**。
使用 llvm-debuginfo-analyzer 的选择功能,我们可以生成一个简单的表格输出,仅显示名称中包含 var 模式的逻辑元素。逻辑视图按变量名称排序。
llvm-debuginfo-analyzer --attribute=level,format
--output-sort=name
--select-regex --select-nocase --select=Var
--report=list
--print=symbols
pr-43860-*.o
Logical View:
[000] {File} 'pr-43860-codeview-clang.o' -> COFF-x86-64
[001] {CompileUnit} 'pr-43860.cpp'
[004] {Variable} 'Var_1' -> 'int'
[004] {Variable} 'Var_2' -> 'int'
Logical View:
[000] {File} 'pr-43860-codeview-msvc.o' -> COFF-i386
[001] {CompileUnit} 'pr-43860.cpp'
[003] {Variable} 'Var_1' -> 'int'
[004] {Variable} 'Var_2' -> 'int'
Logical View:
[000] {File} 'pr-43860-dwarf-clang.o' -> elf64-x86-64
[001] {CompileUnit} 'pr-43860.cpp'
[004] {Variable} 'Var_1' -> 'int'
[003] 3 {Variable} 'Var_1' -> 'int'
[005] {Variable} 'Var_2' -> 'int'
[004] 5 {Variable} 'Var_2' -> 'int'
Logical View:
[000] {File} 'pr-43860-dwarf-gcc.o' -> elf64-x86-64
[001] {CompileUnit} 'pr-43860.cpp'
[004] {Variable} 'Var_1' -> 'int'
[003] 3 {Variable} 'Var_1' -> 'int'
[005] {Variable} 'Var_2' -> 'int'
[004] 5 {Variable} 'Var_2' -> 'int'
它还显示,CodeView 调试信息不会为这些逻辑符号生成源代码行号。逻辑视图按类型名称排序。
测试用例 6 - 完整逻辑视图¶
对于高级用户,llvm-debuginfo-analyzer 可以显示低级信息,其中包括调试信息部分中的偏移量、调试位置操作数、链接名称等。
llvm-debuginfo-analyzer --attribute=all
--print=all
test-dwarf-clang.o
Logical View:
[0x0000000000][000] {File} 'test-dwarf-clang.o' -> elf64-x86-64
[0x000000000b][001] {CompileUnit} 'test.cpp'
[0x000000000b][002] {Producer} 'clang version 12.0.0'
{Directory} ''
{File} 'test.cpp'
{Public} 'foo' [0x0000000000:0x000000003a]
[0x000000000b][002] {Range} Lines 2:9 [0x0000000000:0x000000003a]
[0x00000000bc][002] {BaseType} 'bool'
[0x0000000099][002] {BaseType} 'int'
[0x00000000b5][002] {BaseType} 'unsigned int'
[0x00000000a0][002] {Source} '/test.cpp'
[0x00000000a0][002] 1 {TypeAlias} 'INTPTR' -> [0x00000000ab]'* const int'
[0x000000002a][002] 2 {Function} extern not_inlined 'foo' -> [0x0000000099]'int'
[0x000000002a][003] {Range} Lines 2:9 [0x0000000000:0x000000003a]
[0x000000002a][003] {Linkage} 0x2 '_Z3fooPKijb'
[0x0000000071][003] {Block}
[0x0000000071][004] {Range} Lines 5:8 [0x000000001c:0x000000002f]
[0x000000007e][004] 5 {Variable} 'CONSTANT' -> [0x00000000c3]'const INTEGER'
[0x000000007e][005] {Coverage} 100.00%
[0x000000007f][005] {Location}
[0x000000007f][006] {Entry} Stack Offset: -28 (0xffffffffffffffe4) [DW_OP_fbreg]
[0x000000001c][004] 5 {Line} {NewStatement} '/test.cpp'
[0x000000001c][004] {Code} 'movl $0x7, -0x1c(%rbp)'
[0x0000000023][004] 6 {Line} {NewStatement} '/test.cpp'
[0x0000000023][004] {Code} 'movl $0x7, -0x4(%rbp)'
[0x000000002a][004] {Code} 'jmp 0x6'
[0x000000002f][004] 8 {Line} {NewStatement} '/test.cpp'
[0x000000002f][004] {Code} 'movl -0x14(%rbp), %eax'
[0x0000000063][003] 2 {Parameter} 'ParamBool' -> [0x00000000bc]'bool'
[0x0000000063][004] {Coverage} 100.00%
[0x0000000064][004] {Location}
[0x0000000064][005] {Entry} Stack Offset: -21 (0xffffffffffffffeb) [DW_OP_fbreg]
[0x0000000047][003] 2 {Parameter} 'ParamPtr' -> [0x00000000a0]'INTPTR'
[0x0000000047][004] {Coverage} 100.00%
[0x0000000048][004] {Location}
[0x0000000048][005] {Entry} Stack Offset: -16 (0xfffffffffffffff0) [DW_OP_fbreg]
[0x0000000055][003] 2 {Parameter} 'ParamUnsigned' -> [0x00000000b5]'unsigned int'
[0x0000000055][004] {Coverage} 100.00%
[0x0000000056][004] {Location}
[0x0000000056][005] {Entry} Stack Offset: -20 (0xffffffffffffffec) [DW_OP_fbreg]
[0x000000008d][003] 4 {TypeAlias} 'INTEGER' -> [0x0000000099]'int'
[0x0000000000][003] 2 {Line} {NewStatement} '/test.cpp'
[0x0000000000][003] {Code} 'pushq %rbp'
[0x0000000001][003] {Code} 'movq %rsp, %rbp'
[0x0000000004][003] {Code} 'movb %dl, %al'
[0x0000000006][003] {Code} 'movq %rdi, -0x10(%rbp)'
[0x000000000a][003] {Code} 'movl %esi, -0x14(%rbp)'
[0x000000000d][003] {Code} 'andb $0x1, %al'
[0x000000000f][003] {Code} 'movb %al, -0x15(%rbp)'
[0x0000000012][003] 3 {Line} {NewStatement} {PrologueEnd} '/test.cpp'
[0x0000000012][003] {Code} 'testb $0x1, -0x15(%rbp)'
[0x0000000016][003] {Code} 'je 0x13'
[0x0000000032][003] 8 {Line} '/test.cpp'
[0x0000000032][003] {Code} 'movl %eax, -0x4(%rbp)'
[0x0000000035][003] 9 {Line} {NewStatement} '/test.cpp'
[0x0000000035][003] {Code} 'movl -0x4(%rbp), %eax'
[0x0000000038][003] {Code} 'popq %rbp'
[0x0000000039][003] {Code} 'retq'
[0x000000003a][003] 9 {Line} {NewStatement} {EndSequence} '/test.cpp'
-----------------------------
Element Total Printed
-----------------------------
Scopes 3 3
Symbols 4 4
Types 5 5
Lines 25 25
-----------------------------
Total 37 37
Scope Sizes:
189 (100.00%) : [0x000000000b][001] {CompileUnit} 'test.cpp'
110 ( 58.20%) : [0x000000002a][002] 2 {Function} extern not_inlined 'foo' -> [0x0000000099]'int'
27 ( 14.29%) : [0x0000000071][003] {Block}
Totals by lexical level:
[001]: 189 (100.00%)
[002]: 110 ( 58.20%)
[003]: 27 ( 14.29%)
“**作用域大小**”表显示了每个作用域对调试信息的字节贡献,可用于确定同一工具链不同版本之间 DWARF 部分的意外大小变化。
[0x000000002a][002] 2 {Function} extern not_inlined 'foo' -> [0x0000000099]'int'
[0x000000002a][003] {Range} Lines 2:9 [0x0000000000:0x000000003a]
[0x000000002a][003] {Linkage} 0x2 '_Z3fooPKijb'
[0x0000000071][003] {Block}
[0x0000000071][004] {Range} Lines 5:8 [0x000000001c:0x000000002f]
[0x000000007e][004] 5 {Variable} 'CONSTANT' -> [0x00000000c3]'const INTEGER'
[0x000000007e][005] {Coverage} 100.00%
[0x000000007f][005] {Location}
[0x000000007f][006] {Entry} Stack Offset: -28 (0xffffffffffffffe4) [DW_OP_fbreg]
**{Range}** 属性描述逻辑作用域的行范围。对于这种情况,函数 **foo** 位于 **2** 行和 **9** 行之间。
**{Coverage}** 和 **{Location}** 属性描述逻辑符号的调试位置和覆盖范围。对于已优化代码,覆盖范围值会降低,从而影响程序的可调试性。
WebAssembly 支持¶
以下示例用于显示 llvm-debuginfo-analyzer 生成的 WebAssembly 输出。我们使用 Clang 将示例编译为 WebAssembly 32 位目标 (-O0 -g –target=wasm32)。
1 using INTPTR = const int *;
2 int foo(INTPTR ParamPtr, unsigned ParamUnsigned, bool ParamBool) {
3 if (ParamBool) {
4 typedef int INTEGER;
5 const INTEGER CONSTANT = 7;
6 return CONSTANT;
7 }
8 return ParamUnsigned;
9 }
打印基本详细信息¶
以下命令打印所有逻辑元素的基本详细信息,并按调试信息内部偏移量排序;它包括其词法级别和调试信息格式。
llvm-debuginfo-analyzer --attribute=level,format
--output-sort=offset
--print=scopes,symbols,types,lines,instructions
test-clang.o
或
llvm-debuginfo-analyzer --attribute=level,format
--output-sort=offset
--print=elements
test-clang.o
每一行代表调试信息中存在的元素。第一列表示作用域级别,后面是关联的行号(如果有),最后是元素的描述。
Logical View:
[000] {File} 'test-clang.o' -> WASM
[001] {CompileUnit} 'test.cpp'
[002] 2 {Function} extern not_inlined 'foo' -> 'int'
[003] 2 {Parameter} 'ParamPtr' -> 'INTPTR'
[003] 2 {Parameter} 'ParamUnsigned' -> 'unsigned int'
[003] 2 {Parameter} 'ParamBool' -> 'bool'
[003] {Block}
[004] 5 {Variable} 'CONSTANT' -> 'const INTEGER'
[004] 5 {Line}
[004] {Code} 'i32.const 7'
[004] {Code} 'local.set 10'
[004] {Code} 'local.get 5'
[004] {Code} 'local.get 10'
[004] {Code} 'i32.store 12'
[004] 6 {Line}
[004] {Code} 'i32.const 7'
[004] {Code} 'local.set 11'
[004] {Code} 'local.get 5'
[004] {Code} 'local.get 11'
[004] {Code} 'i32.store 28'
[004] {Code} 'br 1'
[004] - {Line}
[004] {Code} 'end'
[003] 4 {TypeAlias} 'INTEGER' -> 'int'
[003] 2 {Line}
[003] {Code} 'nop'
[003] {Code} 'end'
[003] {Code} 'i64.div_s'
[003] {Code} 'global.get 0'
[003] {Code} 'local.set 3'
[003] {Code} 'i32.const 32'
[003] {Code} 'local.set 4'
[003] {Code} 'local.get 3'
[003] {Code} 'local.get 4'
[003] {Code} 'i32.sub'
[003] {Code} 'local.set 5'
[003] {Code} 'local.get 5'
[003] {Code} 'local.get 0'
[003] {Code} 'i32.store 24'
[003] {Code} 'local.get 5'
[003] {Code} 'local.get 1'
[003] {Code} 'i32.store 20'
[003] {Code} 'local.get 2'
[003] {Code} 'local.set 6'
[003] {Code} 'local.get 5'
[003] {Code} 'local.get 6'
[003] {Code} 'i32.store8 19'
[003] 3 {Line}
[003] {Code} 'local.get 5'
[003] {Code} 'i32.load8_u 19'
[003] {Code} 'local.set 7'
[003] 3 {Line}
[003] {Code} 'i32.const 1'
[003] {Code} 'local.set 8'
[003] {Code} 'local.get 7'
[003] {Code} 'local.get 8'
[003] {Code} 'i32.and'
[003] {Code} 'local.set 9'
[003] {Code} 'block'
[003] {Code} 'block'
[003] {Code} 'local.get 9'
[003] {Code} 'i32.eqz'
[003] {Code} 'br_if 0'
[003] 8 {Line}
[003] {Code} 'local.get 5'
[003] {Code} 'i32.load 20'
[003] {Code} 'local.set 12'
[003] 8 {Line}
[003] {Code} 'local.get 5'
[003] {Code} 'local.get 12'
[003] {Code} 'i32.store 28'
[003] - {Line}
[003] {Code} 'end'
[003] 9 {Line}
[003] {Code} 'local.get 5'
[003] {Code} 'i32.load 28'
[003] {Code} 'local.set 13'
[003] {Code} 'local.get 13'
[003] {Code} 'return'
[003] {Code} 'end'
[003] 9 {Line}
[003] {Code} 'unreachable'
[002] 1 {TypeAlias} 'INTPTR' -> '* const int'
选择逻辑元素¶
以下打印所有名称或类型中包含 **‘block’** 或 **‘.store’** 的 指令、符号和 类型,使用制表符布局并给出匹配数量。
llvm-debuginfo-analyzer --attribute=level
--select-nocase --select-regex
--select=BLOCK --select=.store
--report=list
--print=symbols,types,instructions,summary
test-clang.o
Logical View:
[000] {File} 'test-clang.o'
[001] {CompileUnit} 'test.cpp'
[003] {Code} 'block'
[003] {Code} 'block'
[004] {Code} 'i32.store 12'
[003] {Code} 'i32.store 20'
[003] {Code} 'i32.store 24'
[004] {Code} 'i32.store 28'
[003] {Code} 'i32.store 28'
[003] {Code} 'i32.store8 19'
-----------------------------
Element Total Printed
-----------------------------
Scopes 3 0
Symbols 4 0
Types 2 0
Lines 62 8
-----------------------------
Total 71 8
比较模式¶
鉴于前面的示例,我们通过与另一个编译器进行比较,发现了上述调试信息问题(与之前 ‘typedef int INTEGER’ 的无效作用域位置相关)。
使用 GCC 生成 test-dwarf-gcc.o,我们可以应用选择模式和打印模式以获得以下逻辑视图输出。
llvm-debuginfo-analyzer --attribute=level
--select-regex --select-nocase --select=INTe
--report=list
--print=symbols,types
test-clang.o test-dwarf-gcc.o
Logical View:
[000] {File} 'test-clang.o'
[001] {CompileUnit} 'test.cpp'
[003] 4 {TypeAlias} 'INTEGER' -> 'int'
[004] 5 {Variable} 'CONSTANT' -> 'const INTEGER'
Logical View:
[000] {File} 'test-dwarf-gcc.o'
[001] {CompileUnit} 'test.cpp'
[004] 4 {TypeAlias} 'INTEGER' -> 'int'
[004] 5 {Variable} 'CONSTANT' -> 'const INTEGER'
输出显示两个对象包含相同的元素。但是 ‘typedef INTEGER’ 位于不同的作用域级别。GCC 生成的对象显示 ‘4’,这是正确的值。
有两种比较方法:逻辑视图和逻辑元素。
逻辑视图¶
它将逻辑视图作为一个整体单元进行比较;对于匹配,每个比较的逻辑元素必须具有相同的父级和子级。
输出以视图形式显示**缺失(-)、新增(+)**元素,通过交换参考和目标对象文件提供更多上下文。
llvm-debuginfo-analyzer --attribute=level
--compare=types
--report=view
--print=symbols,types
test-clang.o test-dwarf-gcc.o
Reference: 'test-clang.o'
Target: 'test-dwarf-gcc.o'
Logical View:
[000] {File} 'test-clang.o'
[001] {CompileUnit} 'test.cpp'
[002] 1 {TypeAlias} 'INTPTR' -> '* const int'
[002] 2 {Function} extern not_inlined 'foo' -> 'int'
[003] {Block}
[004] 5 {Variable} 'CONSTANT' -> 'const INTEGER'
+[004] 4 {TypeAlias} 'INTEGER' -> 'int'
[003] 2 {Parameter} 'ParamBool' -> 'bool'
[003] 2 {Parameter} 'ParamPtr' -> 'INTPTR'
[003] 2 {Parameter} 'ParamUnsigned' -> 'unsigned int'
-[003] 4 {TypeAlias} 'INTEGER' -> 'int'
输出显示合并视图路径(参考和目标)以及缺失和新增的元素。
逻辑元素¶
它比较各个逻辑元素,而不考虑它们的父级是否相同。对于这两种比较方法,相等标准包括名称、源代码位置、类型、词法作用域级别。
llvm-debuginfo-analyzer --attribute=level
--compare=types
--report=list
--print=symbols,types,summary
test-clang.o test-dwarf-gcc.o
Reference: 'test-clang.o'
Target: 'test-dwarf-gcc.o'
(1) Missing Types:
-[003] 4 {TypeAlias} 'INTEGER' -> 'int'
(1) Added Types:
+[004] 4 {TypeAlias} 'INTEGER' -> 'int'
----------------------------------------
Element Expected Missing Added
----------------------------------------
Scopes 4 0 0
Symbols 0 0 0
Types 2 1 1
Lines 0 0 0
----------------------------------------
Total 6 1 1
更改参考和目标顺序
llvm-debuginfo-analyzer --attribute=level
--compare=types
--report=list
--print=symbols,types,summary
test-dwarf-gcc.o test-clang.o
Reference: 'test-dwarf-gcc.o'
Target: 'test-clang.o'
(1) Missing Types:
-[004] 4 {TypeAlias} 'INTEGER' -> 'int'
(1) Added Types:
+[003] 4 {TypeAlias} 'INTEGER' -> 'int'
----------------------------------------
Element Expected Missing Added
----------------------------------------
Scopes 4 0 0
Symbols 0 0 0
Types 2 1 1
Lines 0 0 0
----------------------------------------
Total 6 1 1
由于参考和目标已切换,因此第一个案例中的新增类型现在被列为缺失类型。
退出状态¶
llvm-debuginfo-analyzer 如果输入文件成功解析并打印,则返回 0。否则,返回 1。
限制和已知问题¶
请参阅 Limitations
。