LLVM-DEBUGINFO-ANALYZER(1)

πŸ’– llvm-debuginfo-analyzer(1) β€” LLVM πŸ’–

πŸ“› NAME πŸ“›

llvm-debuginfo-analyzer prints a logical representation of low-level debug information. That’s the whole slay. ✨


Okay bestie, here’s the whole map before we dive in β€” yes, it’s giving very thorough πŸ‘‘

  • SYNOPSIS
  • DESCRIPTION
  • OPTIONS
    • GENERAL
    • ATTRIBUTES
    • PRINT
    • OUTPUT
    • REPORT
    • SELECTION
      • ELEMENTS
      • LINES
      • SCOPES
      • SYMBOLS
      • TYPES
    • COMPARE
    • WARNING
    • INTERNAL
  • EXAMPLES
    • TEST CASE 1 - GENERAL OPTIONS
      • PRINTING MODE
        • BASIC DETAILS
        • SELECT LOGICAL ELEMENTS
      • COMPARISON MODE
        • LOGICAL VIEW
        • LOGICAL ELEMENTS
    • TEST CASE 2 - ASSEMBLER INSTRUCTIONS
      • CodeView - Clang (Windows)
      • CodeView - MSVC (Windows)
      • DWARF - Clang (Linux)
      • DWARF - GCC (Linux)
    • TEST CASE 3 - INCORRECT LEXICAL SCOPE FOR TYPEDEF
      • CodeView - Clang (Windows)
      • CodeView - MSVC (Windows)
      • DWARF - Clang (Linux)
      • DWARF - GCC (Linux)
    • TEST CASE 4 - MISSING NESTED ENUMERATIONS
      • CodeView - Clang (Windows)
      • CodeView - MSVC (Windows)
      • DWARF - Clang (Linux)
      • DWARF - GCC (Linux)
    • TEST CASE 5 - INCORRECT LEXICAL SCOPE FOR VARIABLE
      • CODEVIEW - Clang (Windows)
      • CODEVIEW - MSVC (Windows)
      • DWARF - Clang (Linux)
      • DWARF - GCC (Linux)
    • TEST CASE 6 - FULL LOGICAL VIEW
  • EXIT STATUS
  • SEE ALSO

🌸 SYNOPSIS 🌸

llvm-debuginfo-analyzer [options] [filename ...]

πŸ¦‹ DESCRIPTION πŸ¦‹

Okay so llvm-debuginfo-analyzer parses debug and text sections in binary object files and prints their contents in a logical view. That logical view is a human-readable representation that closely matches the structure of the original user source code. Supported object file formats include ELF, Mach-O, PDB and COFF β€” yes, all of them, bestie. πŸ’…

The logical view abstracts the complexity associated with the different low-level representations of the debugging information that is embedded in the object file. llvm-debuginfo-analyzer produces a canonical view of the debug information regardless of how it is formatted. The same logical view will be seen regardless of object file format, assuming the debug information correctly represents the same original source code. Periodt. ✨

The logical view includes the following logical elements: type, scope, symbol and line, which are the basic software elements used in the C/C++ programming language. Each logical element has a set of attributes, such as types, classes, functions, variables, parameters, etc. The --attribute can be used to specify which attributes to include when printing a logical element. A logical element may have a kind that describes specific types of elements. For instance, a scope could have a kind value of function, class, namespace.

llvm-debuginfo-analyzer defaults to print a pre-defined layout of logical elements and attributes. The command line options can be used to control the printed elements (--print), using a specific layout (--report), matching a given pattern (--select, --select-offsets). Also, the output can be limited to specified logical elements using (--select-lines, --select-scopes, --select-symbols, --select-types).

llvm-debuginfo-analyzer can also compare a set of logical views (--compare), to find differences and identify possible debug information syntax issues (--warning) in any object file. Not me being obsessed with canonical debug info πŸ’€πŸ’–

βš™οΈ OPTIONS βš™οΈ

llvm-debuginfo-analyzer options are separated into several categories, each tailored to a different purpose:

  • GENERAL β€” Standard LLVM options to display help, version, etc.
  • ATTRIBUTES β€” Describe how to include different details when printing an element.
  • PRINT β€” Specify which elements will be included when printing the view.
  • OUTPUT β€” Describe the supported formats when printing the view.
  • REPORT β€” Describe the format layouts for view printing.
  • SELECTION β€” Allows to use specific criteria or conditions to select which elements to print.
  • COMPARE β€” Compare logical views and print missing and/or added elements.
  • WARNING β€” Print the warnings detected during the creation of the view.
  • INTERNAL β€” Internal analysis of the logical view.

πŸ› οΈ GENERAL πŸ› οΈ

This section describes the standard help options, used to display the usage, version, response files, etc. Main character energy for command-line basics, fr fr.

  • -h, --help β€” Show help and usage for this command. (--help-hidden for more).
  • --help-list β€” Show help and usage for this command without grouping the options into categories (--help-list-hidden for more).
  • --help-hidden β€” Display all available options.
  • --print-all-options β€” Print all option values after command line parsing.
  • --print-options β€” Print non-default options after command line parsing.
  • --version β€” Display the version of the tool.
  • @<FILE> β€” Read command-line options from <FILE>.

If no input file is specified, llvm-debuginfo-analyzer defaults to read a.out and return an error when no input file is found. πŸ™ˆ

If - is used as the input file, llvm-debuginfo-analyzer reads the input from its standard input stream.

πŸŽ€ ATTRIBUTES πŸŽ€

The following options enable attributes given for the printed elements. The attributes are divided in categories based on the type of data being added, such as: internal offsets in the binary file, location descriptors, register names, user source filenames, additional element transformations, toolchain name, binary file format, etc. It’s giving very detail-oriented, and we love that πŸ’…

  • --attribute=<value[,value,...]> β€” with value being one of the options in the following lists.
    • =all β€” Include all the below attributes.
    • =extended β€” Add low-level attributes.
    • =standard β€” Add standard high-level attributes.

The following attributes describe the most common information for a logical element. They help to identify the lexical scope level; the element visibility across modules (global, local); the toolchain name that produced the binary file.

  • =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.

The following attributes describe files and directory names from the user source code, where the elements are declared or defined; functions with public visibility across modules. These options allow to map the elements to their user code location, for cross references purposes.

  • =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.

The following attributes describe additional logical element source transformations, in order to display built-in types (int, bool, etc.); parameters and arguments used during template instantiation; parent name hierarchy; array dimensions information; compiler generated elements and the underlying types associated with the types aliases.

  • =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.

The following attributes describe the debug location information for a symbol or scope. It includes the symbol percentage coverage and any gaps within the location layout; ranges determining the code sections attached to a function. When descriptors are used, the target processor registers are displayed.

  • =coverage β€” Symbol location coverage.
  • =gaps β€” Missing debug location (gaps).
  • =location β€” Symbol debug location.
  • =range β€” Debug location ranges.
  • =register β€” Processor register names.

The following attributes are associated with low level details, such as: offsets in the binary file; discriminators added to the lines of inlined functions in order to distinguish specific instances; debug lines state machine registers; elements discarded by the compiler (inlining) or by the linker optimizations (dead-stripping); system compile units generated by the MS toolchain in PDBs.

  • =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.

The following attribute described specific information for the PE/COFF file format. It includes MS runtime types.

  • =system β€” Display PDB’s MS system elements.

The above attributes are grouped into standard and extended categories that can be enabled.

The standard group, contains those attributes that add sufficient information to describe a logical element and that can cover the normal situations while dealing with debug information.

  • =base
  • =coverage
  • =directories
  • =discriminator
  • =filename
  • =files
  • =format
  • =level
  • =producer
  • =publics
  • =range
  • =reference
  • =zero

The extended group, contains those attributes that require a more extended knowledge about debug information. They are intended when a lower level of detail is required. Highkey niche knowledge, but iconic. πŸ’œ

  • =argument
  • =discarded
  • =encoded
  • =gaps
  • =generated
  • =global
  • =inserted
  • =linkage
  • =local
  • =location
  • =offset
  • =operation
  • =pathname
  • =qualified
  • =qualifier
  • =register
  • =subrange
  • =system
  • =typename

that's the rest of the value list bestie, no extra blurbs attached to these ones, iykyk πŸ’…


✨ PRINT ✨

these options are all about what you actually print. the layout you get is decided by --report, okay? and listen β€” in the tree layout, every single element gets its enclosing lexical scopes printed too, even the ones you didn't explicitly ask for. generous queen behavior fr fr πŸ‘‘

  • --print=<value[,value,...]> β€” with value being one of the options in the lists below.
    • =all: Include all the below attributes.

the next batch prints the actual elements you asked for; and if you've got any select conditions going (--select), only the elements that match them get printed. the elements value is the lazy-girl shortcut β€” it grabs instructions, lines, scopes, symbols and types all at once πŸŽ€

  • =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).

these next ones print the meta info β€” the stuff collected while the elements were being created: how much each scope contributed to the debug info, the summary of what got created/printed/matched (--select), and any warnings that popped up while building the view πŸ‘€

  • =sizes: Debug Information scopes contributions.
  • =summary: Summary of elements allocated, selected or printed.
  • =warnings: Warnings detected.
note: the --print=sizes option is ELF specific. so like, don't come for it if you're not on ELF πŸ’€

🌸 OUTPUT 🌸

these ones control how the output looks once we're actually printing the logical elements. aesthetic choices, basically πŸŽ€

  • --output-file=<path> β€” redirect the output into a file at <path>, where - means the standard output stream.

okay so llvm-debuginfo-analyzer has this whole concept of a split view. when you redirect the output from a complex binary format, it gets divided into individual files β€” each one holding the logical view output for a single compilation unit. one file per CU, very organized, very demure ✨

  • --output-folder=<name> β€” the folder to write a file per compilation unit into, when --output=split is specified.
  • --output-level=<level> β€” only print elements up to the given lexical level value. the input file sits at lexical level zero, and a compilation unit sits at lexical level one. so like, level 0 is the ground floor, level 1 is the first floor, etc 🏒
  • --output=<value[,value,...]> β€” with value being one of the options in the following lists.
    • =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.
heads up bestie: =json is literally (Not implemented). don't @ me, I don't make the rules 😭
  • --output-sort=<key> β€” the primary key when ordering the elements in the output (default: line). and listen, sorting by logical element kind requires you to be familiar with the element kind selection options (--select-lines, --select-scopes, --select-symbols, --select-types), because those options are the ones that describe the different logical element kinds. like, read those first, then come back 🫢
    • =kind: Sort by element kind.
    • =line: Sort by element line number.
    • =name: Sort by element name.
    • =offset: Sort by element offset.

πŸ¦‹ REPORT πŸ¦‹

depending on what task you're running (print, compare, select), there are several layouts supported so the elements display in a more suitable way and the output is actually easy to read. it's giving "we care about your reading experience" πŸ’–

  • --report=<value[,value,...]> β€” with value being one of the options in the following list.
    • =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.

the list layout presents the logical elements in a tabular form with zero parent-child relationship. this might genuinely be the preferred way to display elements that match specific conditions when you're comparing logical views β€” it makes differences way easier to spot, no cap πŸ”

the children, parents and view layouts display elements in a tree format, where the scopes are the nodes, and types, symbols, lines and other scopes are the children. the layout shows the lexical scoping relationship between elements β€” the binary file is the tree root (level 0) and each compilation unit is a child (level 1) 🌳

the children layout includes the elements that match any given criteria (--select) or (--compare) and their children.

the parents layout includes the elements that match any given criteria (--select) or (--compare) and their parents.

the combined view layout includes the elements that match any given criteria (--select) or (--compare), plus their parents and children. the full main-character package, honestly πŸ‘‘

Notes:

  1. When a selection criteria (--select) is specified with no report option, the list layout is selected.
  2. The comparison mode always uses the view layout.

πŸ’œ SELECTION πŸ’œ

when printing an element, different data can be included β€” and it varies (--attribute) all the way from data directly tied to the binary file (offset) up to high level details like coverage, lexical scope level, location. and because the printed output can get considerably big, there are several selection options that let you print only specific elements. targeted, curated, slay ✨

the pattern matching can ignore case (--select-nocase) and can be extended to use regular expressions (--select-regex). fancy πŸ’…


⭐ ELEMENTS ⭐

these options let you print elements that match a given <pattern>, an offset <value>, or an element <condition>.

  • --select=<pattern> β€” print all elements whose name or line number matches the given <pattern>.
  • --select-offsets=<value[,value,...]> β€” print all elements whose offset matches the given values. see the --attribute option.
  • --select-elements=<condition[,condition,...]> β€” print all elements that satisfy the given <condition>, with condition being one of the options in the following list.
    • =discarded: Discarded elements by the linker.
    • =global: Element referenced across Compile Units.
    • =optimized: Optimized inlined abstract references.
  • --select-nocase β€” pattern matching is case-insensitive when using --select.
  • --select-regex β€” treat any <pattern> strings as regular expressions when selecting with the --select option. if --select-nocase is specified, the regular expression becomes case-insensitive too. two for one πŸŽ€

if the <pattern> criteria is too general, you can bring in a more selective option to target a particular category of elements: lines (--select-lines), scopes (--select-scopes), symbols (--select-symbols) and types (--select-types). these options require knowledge of the debug information format (DWARF, CodeView, COFF), because the given kind describes a very specific type of element. like, know your format before you come in swinging, bestie 🫢


🧚 LINES 🧚

these options let you print lines that match the given <kind>. the given criteria describes the debug line state machine registers.

  • --select-lines=<kind[,kind,...]> β€” with kind being one of the options in the following list.
    • =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.

🌈 SCOPES 🌈

these options let you print scopes that match the given <kind>.

  • --select-scopes=<kind[,kind,...]> β€” with kind being one of the options in the following list.
    • =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.

πŸ“ SYMBOLS πŸ“

these options let you print symbols that match the given <kind>.

  • --select-symbols=<kind[,kind,...]> β€” with kind being one of the options in the following list.
    • =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.

πŸ’€ TYPES πŸ’€

these options let you print types that match the given <kind>.

  • --select-types=<kind[,kind,...]> β€” with kind being one of the options in the following list.
  • =Base β€” base type (integer, boolean, etc) 🧱
  • =Const β€” the const specifier πŸ”’
  • =Enumerator β€” an enumerator
  • =Import β€” an import declaration
  • =ImportDeclaration β€” an import declaration (yes, again β€” both spellings are real, don't @ me) πŸ“¦
  • =ImportModule β€” an import module
  • =Pointer β€” pointer type πŸ‘‰
  • =PointerMember β€” pointer to member function
  • =Reference β€” reference type
  • =Restrict β€” the restrict specifier
  • =RvalueReference β€” r-value reference
  • =Subrange β€” array subrange
  • =TemplateParam β€” template parameter
  • =TemplateTemplateParam β€” template template parameter (say that five times fast 😭)
  • =TemplateTypeParam β€” template type parameter
  • =TemplateValueParam β€” template value parameter
  • =Typedef β€” type definition
  • =Unspecified β€” unspecified type
  • =Volatile β€” the volatile specifier ⚑

COMPARE πŸ’…

okay so here's the tea β˜• β€” sometimes printing the elements just isn't it. Like, the situation where you're not staring at the elements themselves, but you wanna know the damage: what changed when you bumped your toolchain version, or what a specific compiler optimization did to your precious debug info. That's the vibe.

For those cases, you're looking to see which elements got added or removed. And because the debug info format is, uh, complicated (derogatory), using a regular diff tool to find those elements is genuinely brutal β€” and straight-up impossible when you're dealing with different debug formats. πŸ’€

llvm-debuginfo-analyzer supports a logical element comparison, so you can find semantic differences between logical views β€” even ones produced by different toolchain versions, or, bestie, different debug information formats entirely. iconic behavior honestly ✨

Heads up 🫢 when you compare logical views created from different debug formats, the accuracy depends on how close the debug information actually represents the user code. Like, a logical view from a binary with DWARF debug info may include more detailed data than a logical view from a binary with CodeView/COFF debug info. Not a bug, just how it is.

The following options describe the elements to compare:

  • --compare=<value[,value,...]> β€” where value is one of the options in the list below πŸ‘‡
    • =all β€” include all the below elements.
    • =lines β€” include lines.
    • =scopes β€” include scopes.
    • =symbols β€” include symbols.
    • =types β€” include types.

llvm-debuginfo-analyzer takes the first binary file on the command line as the reference and the second one as the target. And because we want a report that actually says something, the comparison is done twice β€” the reference and target views get swapped, so you get both the elements missing from the target view and the elements added to the reference view. double the data, double the slay πŸŽ€

See the --report options for how to describe the comparison reports.

WARNING ⚠️

When it's reading the input object files, llvm-debuginfo-analyzer can detect issues in the raw debug information. Now, these might not be fatal to the whole "print a logical view" mission, but they do give you an indication about the quality of what you're looking at β€” and potentially expose issues with the generated debug information. it's giving diagnostic bestie energy πŸ”

The following options describe the warnings to be recorded for later printing β€” if they're requested by --print, that is:

  • --warning=<value[,value,...]> β€” where value is one of the options in the list below πŸ‘‡
    • =all β€” include all the below warnings.

The next options collect additional information during the creation of the logical view, so we can include invalid coverage values and locations for symbols; invalid code ranges; and lines that are zero. 🧐

  • =coverages β€” invalid symbol coverages values.
  • =lines β€” debug lines that are zero.
  • =locations β€” invalid symbol locations.
  • =ranges β€” invalid code ranges.

INTERNAL 🧚

Sometimes you need to really understand the logical view, and for that you might need access to more detailed internal information. Like, that kind of data helps you identify debug information that got processed, or incorrect logical element management. Typically these kinds of options are only available in debug builds β€” but not here, we're generous πŸ’œ

llvm-debuginfo-analyzer supports these advanced options in both release and debug builds β€” with one exception: the unique ID, which is generated only in debug builds. πŸ™ˆ

  • --internal=<value[,value,...]> β€” where value is one of the options in the list below πŸ‘‡
    • =all β€” include all the below options.

The following options let you check the integrity of the logical view; collect the debug tags that are processed or not implemented; ignore the logical element line number (so comparing the logical view with external comparison tools is easier); and print the command line options used to invoke llvm-debuginfo-analyzer. very "here's everything, no secrets" of them 🌸

  • =id β€” print unique element ID.
  • =cmdline β€” print command line.
  • =integrity β€” check elements integrity.
  • =none β€” ignore element line number.
  • =tag β€” debug information tags.
Note: for the ELF format, the collected tags represent the debug tags that are not processed. For the PE/COFF format, they represent the tags that are processed. Subtle but important, bestie πŸ‘‘

EXAMPLES 🌈

This section includes some real binary files to show you how to use llvm-debuginfo-analyzer to print a logical view β€” and to diagnose possible debug information issues. Let's get into it 🎬

TEST CASE 1 - GENERAL OPTIONS πŸ§ͺ

The example below shows the different output generated by llvm-debuginfo-analyzer. We compiled it for an X86 ELF target with Clang (-O0 -g):

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  }

PRINTING MODE πŸ–¨οΈ

In this mode, llvm-debuginfo-analyzer prints the logical view β€” or portions of it β€” based on criteria patterns (regular expressions included!) to select the kind of logical elements to be included in the output.

BASIC DETAILS πŸŽ€

The following command prints basic details for all the logical elements, sorted by the debug information internal offset; it includes its lexical level and debug info format.

llvm-debuginfo-analyzer --attribute=level,format
                        --output-sort=offset
                        --print=scopes,symbols,types,lines,instructions
                        test-dwarf-clang.o

or

llvm-debuginfo-analyzer --attribute=level,format
                        --output-sort=offset
                        --print=elements
                        test-dwarf-clang.o

Each row represents an element that's present within the debug information. The first column is the scope level, then the associated line number (if there is one), and finally the description of the element. πŸ’–

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'

On closer inspection πŸ‘€ we can see what could be a potential debug issue:

[003]                 {Block}
[003]     4           {TypeAlias} 'INTEGER' -> 'int'

The INTEGER definition is at level [003] β€” the same lexical scope as the anonymous {Block} (the true branch for the if statement). But in the original source code, that typedef statement is clearly inside that block, so the INTEGER definition should also be at level [004], inside the block. caught in 4k πŸ“Έ

SELECT LOGICAL ELEMENTS πŸ¦‹

The following prints all instructions, symbols and types that contain inte or movl in their names or types, using a tab layout and given the number of matches.

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

COMPARISON MODE πŸ’…

Okay bestie, comparison mode is where llvm-debuginfo-analyzer really shows off. It takes logical views and produces a report with the logical elements that are missing or added. This is a very powerful aid in finding semantic differences in the debug information produced by different toolchain versions β€” or even completely different toolchains altogether. For example, a compiler producing DWARF can be directly compared against a completely different compiler that produces CodeView. Iconic. ✨

Given the previous example, we found the above debug information issue β€” the one related to the previous invalid scope location for the 'typedef int INTEGER' β€” by comparing against another compiler. 🫢

Using GCC to generate test-dwarf-gcc.o, we can apply a selection pattern with the printing mode to obtain the following logical view output.

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'

The output shows that both objects contain the same elements. But the 'typedef INTEGER' is located at a different scope level. The GCC generated object shows '4', which is the correct value. Slay. πŸ’–

Note that there is no requirement that GCC must produce identical or similar DWARF to Clang to allow the comparison. We’re only comparing the semantics. The same case when comparing CodeView debug information generated by MSVC and Clang. No cap.

There are 2 comparison methods: logical view and logical elements. πŸŽ€

LOGICAL VIEW πŸ¦‹

It compares the logical view as a whole unit; for a match, each compared logical element must have the same parents and children. Periodt.

Using the llvm-debuginfo-analyzer comparison functionality, that issue can be seen in a more global context, which can include the logical view. πŸ’…

The output shows in view form the missing (-) and added (+) elements, giving more context by swapping the reference and target object files.

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'

The output shows the merging view path (reference and target) with the missing and added elements. Ate. ✨

LOGICAL ELEMENTS 🌸

It compares individual logical elements without considering if their parents are the same. For both comparison methods, the equal criteria includes the name, source code location, type, and lexical scope level. iykyk

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

Changing the Reference and Target order:

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

As the Reference and Target are switched, the Added Types from the first case now are listed as Missing Types. Main character energy. πŸ‘‘

TEST CASE 2 - ASSEMBLER INSTRUCTIONS πŸ”₯

The below example is used to show different output generated by llvm-debuginfo-analyzer. We compiled the example for X86 Codeview and ELF targets with recent versions of Clang, GCC and MSVC (-O0 -g) for Windows and Linux. πŸ’»

  1  extern int printf(const char * format, ... );
  2
  3  int main()
  4  {
  5    printf("Hello, World\n");
  6    return 0;
  7  }

These are the logical views that llvm-debuginfo-analyzer generates for 3 different compilers (MSVC, Clang and GCC), emitting different debug information formats (CodeView, DWARF) on Windows and Linux. Vibe check passed. 🌈

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}

The logical views shows the intermixed lines and assembler instructions, allowing to compare the code generated by the different toolchains. Obsessed. πŸ’›

TEST CASE 3 - INCORRECT LEXICAL SCOPE FOR TYPEDEF πŸ’€

The below example is used to show different output generated by llvm-debuginfo-analyzer. We compiled the example for X86 Codeview and ELF targets with recent versions of Clang, GCC and MSVC (-O0 -g). 🧚

           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  }

okay bestie, this little test is literally here to show a scope issue found in the Clang compiler: PR44884 (Bugs LLVM) / PR44229 (GitHub LLVM). πŸ’…

Lines 4 and 7 contain 2 typedefs, defined at different lexical scopes. No cap:

   4    typedef int INT;
   7      typedef float FLOAT;

These are the logical views that llvm-debuginfo-analyzer generates for 3 different compilers (MSVC, Clang and GCC), emitting different debug information formats (CodeView, DWARF) on different platforms. The way it's invoked is giving:

          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}

From the previous logical views, we can see that the Clang compiler emits both typedefs at the same lexical scope (3), which is wrong. GCC and MSVC emit correct lexical scope for both typedefs. 🫢

Using the llvm-debuginfo-analyzer selection facilities, we can produce a simple tabular output showing just the logical types that are Typedef. The command is:

          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'

It also shows, that the CodeView debug information does not generate source code line numbers for the those logical types. The logical view is sorted by the types name. πŸ’…

πŸ§ͺ TEST CASE 4 - MISSING NESTED ENUMERATIONS πŸ§ͺ

The below example is used to show different output generated by llvm-debuginfo-analyzer. We compiled the example for an X86 Codeview and ELF targets with recent versions of Clang, GCC and MSVC (-O0 -g).

           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  }

The above test is used to illustrate a scope issue found in the Clang compiler: PR46466 (Bugs LLVM) / PR45811 (GitHub LLVM).

These are the logical views that llvm-debuginfo-analyzer generates for 3 different compilers (MSVC, Clang and GCC), emitting different debug information formats (CodeView, DWARF) on different platforms.

          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'
          [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'

okay so looking at those logical views from earlier, the DWARF debug info that the Clang compiler made has literally no references to the enumerators RED and BLUE. The DWARF generated by GCC, plus the CodeView generated by Clang and MSVC? Those do include the references. no cap, that's the vibe check.

Using the selection facilities in llvm-debuginfo-analyzer, we can make a logical view that shows just the logical types that are Enumerator and its parents. It's sorted by the types name, bestie. very cute.

          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'

Using the selection facilities in llvm-debuginfo-analyzer, we can also produce a simple tabular output that includes a summary for the logical types that are Enumerator. The logical view is sorted by the types name, periodt.

          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

From the values printed under the Found column, we can see that no Types were found in the DWARF debug information generated by Clang. πŸ’€


πŸ’€ TEST CASE 5 - INCORRECT LEXICAL SCOPE FOR VARIABLE πŸ’€

okay so this next example is literally here to show the different output that llvm-debuginfo-analyzer generates. We compiled the example for an X86 Codeview target and an ELF target with recent versions of Clang, GCC and MSVC (-O0 -g). slay.

          // 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

Because the test depends on inline compiler options, that header file defines forceinline. iykyk.

          #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  }

The above test is used to illustrate a variable issue found in the Clang compiler: PR43860 (Bugs LLVM) / PR43205 (GitHub). not me being obsessed with compiler bugs, but here we are. 🫢

These are the logical views that llvm-debuginfo-analyzer generates for 3 different compilers (MSVC, Clang and GCC), emitting different debug information formats (CodeView, DWARF) on different platforms.

          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'

So basically, looking at those logical views above πŸ‘€ β€” the CodeView debug info that Clang spat out is showing Var_1 and Var_2 sitting at the same lexical scope (4) inside the function InlineFuction. Meanwhile the DWARF from GCC/Clang and the CodeView from MSVC both put those variables at the correct lexical scope: 3 and 4 respectively. Like, bestie, that's the whole bug right there 😭

Okay so using the selection facilities in llvm-debuginfo-analyzer, we can pull a cute little tabular output that shows just the logical elements with var in their name. The logical view is sorted by the variables name. Slay.

     llvm-debuginfo-analyzer --attribute=level,format
                             --output-sort=name
                             --select-regex --select-nocase --select=Var
                             --report=list
                             --print=symbols
                             pr-43860-*.o

And here's what comes out, one file at a time πŸ’…

     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'

It also shows that the CodeView debug information does not generate source code line numbers for those logical symbols. The logical view is sorted by the types name. 🫢


πŸ§ͺ TEST CASE 6 - FULL LOGICAL VIEW

Okay so for the advanced users out there β€” the ones with main character energy β€” llvm-debuginfo-analyzer can also display low level information, like offsets within the debug information section, debug location operands, linkage names, etc. It's giving full lore drop ✨

     llvm-debuginfo-analyzer --attribute=all
                             --print=all
                             test-dwarf-clang.o

And the big reveal:

     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'

And then, the receipts πŸ“Š

     -----------------------------
     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%)

The Scope Sizes table shows the contribution in bytes to the debug information by each scope, which can be used to determine unexpected size changes in the DWARF sections between different versions of the same toolchain. Basically it's a glow-up tracker for your 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]

The {Range} attribute describes the line ranges for a logical scope. In this case, the function foo is within the lines 2 and 9. 🎯

The {Coverage} and {Location} attributes describe the debug location and coverage for logical symbols. For optimized code, the coverage value decreases and it affects the program debuggability. So like β€” optimized builds, am I right? 😭 Less debug info, more mystery.


πŸšͺ EXIT STATUS

llvm-debuginfo-analyzer returns 0 if the input files were parsed and printed successfully. Otherwise, it returns 1. Simple as that, no cap πŸ’―

πŸ‘€ SEE ALSO

  • llvm-dwarfdump

✍️ AUTHOR

Maintained by the LLVM Team (https://llvm.org/). They're the ones keeping this whole thing iconic πŸ‘‘

2003-2023, LLVM Project


Okay bestie, that's the whole page! πŸŽ€ This one came from the LLVM-DEBUGINFO-ANALYZER(1) manual, source 17, dated 2023-12-01 β€” go forth and debug your debug info, you absolute legend πŸ’–βœ¨πŸ¦‹

πŸ“¦ where this page comes from