LLVM-DEBUGINFO-ANALYZER(1)

✨ llvm-debuginfo-analyzer ✨

straight out of the LLVM manual collection, section 1 of the manual pages

okay so bestie. you've got a binary. it's got debug info stuffed inside it. you want to actually see it without crying? that's literally this tool's whole job. let's go πŸ’…


πŸŽ€ NAME πŸŽ€

llvm-debuginfo-analyzer β€” prints a logical representation of low-level debug information πŸ¦‹


πŸ“– what's inside (the map, bestie) πŸ—ΊοΈ

  • 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
    • WEBASSEMBLY SUPPORT
      • PRINT BASIC DETAILS
      • SELECT LOGICAL ELEMENTS
      • COMPARISON MODE
        • LOGICAL VIEW
        • LOGICAL ELEMENTS
  • EXIT STATUS
  • LIMITATIONS AND KNOWN ISSUES
  • SEE ALSO

🌈 SYNOPSIS 🌈

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

that's it. that's the whole vibe. <italic>options</italic> and then as many <italic>filename</italic>s as your little heart desires 🫢


πŸ’– DESCRIPTION πŸ’–

llvm-debuginfo-analyzer parses the debug and text sections in binary object files and prints what's inside them as a logical view β€” which is just a fancy way of saying "a human readable glow-up that actually looks like your original source code." the object file formats it fucks with include ELF, Mach-O, WebAssembly, PDB and COFF. all the big ones, slay πŸ’…

the logical view abstracts away all the messy low-level chaos that is embedded debug info in an object file. llvm-debuginfo-analyzer produces a canonical view of the debug information no matter how it's formatted, so like... you get the same logical view regardless of the object file format, assuming the debug information correctly represents the same original source code. periodt.

the logical view is made up of these logical elements: type, scope, symbol and line β€” the basic software building blocks used in C/C++. every logical element has a set of attributes, like types, classes, functions, variables, parameters, etc. you can use --attribute to pick which attributes get included when a logical element is printed. also a logical element can have a kind that describes specific types of elements β€” for instance a scope could have a kind value of function, class, namespace. iykyk.

llvm-debuginfo-analyzer defaults to printing a pre-defined layout of logical elements and attributes. but bestie, the command line options are right there to control it all:

  • control the printed elements with --print
  • use a specific layout with --report
  • match a given pattern with --select, --select-offsets
  • limit the output to specified logical elements with --select-lines, --select-scopes, --select-symbols, --select-types

and!! it can also compare a set of logical views with --compare, to find differences and identify possible debug information syntax issues (--warning) in any object file. iconic behavior honestly πŸ”₯


⭐ 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 you 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 β€” the ones used to display usage, version, response files, etc. the classic "who am i and what is this" toolkit πŸ’œ

  • -h, --help β€” show help and usage for this command. (and --help-hidden for more, if you're nosy like me πŸ‘€)
  • --help-list β€” show help and usage for this command without grouping the options into categories (--help-list-hidden for more).
  • --help-hidden β€” display absolutely all available options. no secrets, no cap.
  • --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 reading a.out and returns an error when no input file is found. so like... it tries, bestie 😭
also! if - is used as the input file, llvm-debuginfo-analyzer reads the input from its standard input stream. pipe it in, live your truth 🫢

πŸ¦‹ ATTRIBUTES πŸ¦‹

the following options enable attributes given for the printed elements. the attributes are divided into 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. basically: how much extra lore do you want on each element 🌸

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

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

✨ PRINT ✨

Okay so these next options are all about which elements you actually get to see. The layout you get is decided by --report, and heads up β€” in the tree layout, every element gets its enclosing lexical scopes printed too, even if you didn't explicitly ask for them. Like, the whole family shows up. 🫢

  • --print=<value[,value,...]> β€” where each value is one of the options in the lists below. Pick your faves!

And here's the values for that:

  • =all β€” include all the below attributes. Everything. The full buffet. πŸ’…

The following options print the elements you requested; and if you've got any select conditions going (--select), only the elements that match them get printed. Also, lowkey the elements value is a handy little shortcut that lets you specify instructions, lines, scopes, symbols and types all at once. Iconic behaviour honestly.

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

Now these next ones print out the extra info that got collected while the elements were being created β€” stuff like the scope contributions to the debug information, a summary of elements created / printed / matched (--select), and any warnings that popped up while the view was being created.

  • =sizes β€” Debug Information scopes contributions.
  • =summary β€” summary of elements allocated, selected or printed.
  • =warnings β€” warnings detected.
Heads up bestie: the --print=sizes option is ELF specific. Not universal, just ELF. 🧚

πŸŽ€ OUTPUT πŸŽ€

These options are all about how you control the output that gets generated when the logical elements are printed.

  • --output-file=<path> β€” redirect the output to a file specified by <path>, where - means the standard output stream. Cute and simple.
  • --output-folder=<name> β€” the folder to write a file per compilation unit when --output=split is specified.

And okay, so llvm-debuginfo-analyzer has this whole concept of a split view. When you're redirecting the output from a complex binary format, it gets divided up into individual files, and each one holds the logical view output for a single compilation unit. One file per CU, no cap. πŸ’–

  • --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 is at lexical level one.
  • --output=<value[,value,...]> β€” with value being one of the options in the following list:
    • =all β€” include all the below outputs.
    • =json β€” use JSON as the output format (Not implemented). Yeah. Not yet. πŸ™ˆ
    • =split β€” split the output by Compile Units.
    • =text β€” use a free form text output.
  • --output-sort=<key> β€” the primary key when ordering the elements in the output (default: line).

Sorting by logical element kind requires some familiarity with the element kind selection options (--select-lines, --select-scopes, --select-symbols, --select-types), because those options are literally the ones that describe the different logical element kinds. iykyk ✨

  • =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 is being executed (print, compare, select), several layouts are supported so the elements get displayed in a more suitable way β€” makes the output way easier to understand. Main character formatting energy.

  • --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, without any parent-child relationship. This might be the preferred way to display elements that match specific conditions when comparing logical views, because it makes finding differences so much easier. Slay for diffing. πŸ”₯

The children, parents and view layouts display the elements in a tree format, with the scopes representing their nodes, and types, symbols, lines and other scopes representing the children. The layout shows the lexical scoping relationship between elements, with the binary file being the tree root (level 0) and each compilation unit being a child (level 1). Family tree but make it debug info. 🌈

  • The children layout includes the elements that match any given criteria (--select) or (--compare), plus its children.
  • The parents layout includes the elements that match any given criteria (--select) or (--compare), plus its parents.
  • The combined view layout includes the elements that match any given criteria (--select) or (--compare), its parents and children.

Notes, bestie, write these down:

  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. Always. No exceptions. πŸ’…

πŸ¦‹ SELECTION πŸ¦‹

When printing an element, different data can be included, and it varies (--attribute) β€” from data directly associated with the binary file (offset) all the way to high level details like coverage, lexical scope level, and location. And because the printed output can reach a considerable size (like, huge), there are several selection options that let you enable printing of specific elements. You're welcome.

The pattern matching can ignore the case (--select-nocase) and can be extended to use regular expressions (--select-regex). Fancy.


πŸ‘‘ ELEMENTS πŸ‘‘

The following options allow printing of elements that match the given <pattern>, 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. Vibes only, case doesn't matter.
  • --select-regex β€” treat any <pattern> strings as regular expressions when selecting with the --select option. And if --select-nocase is specified, the regular expression becomes case-insensitive too. ✨

If the <pattern> criteria is too general, you can specify a more selective option to target a particular category of elements: lines (--select-lines), scopes (--select-scopes), symbols (--select-symbols) and types (--select-types).

Just so you know: these options require knowledge of the debug information format (DWARF, CodeView), because the given kind describes a very specific type of element. Like, very specific. Don't come for me if you don't know your DWARF. 😭

⭐ LINES ⭐

The following options allow printing of lines that match the given <kind>. The given criteria describes the debug line state machine registers. Yes, registers. We're in the weeds and it's honestly kinda iconic.

  • --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 πŸ’œ

The following options allow printing of 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 πŸ“

The following options allow printing of 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 🧚

The following options allow printing of types that match the given <kind>.

  • --select-types=<kind[,kind,...]> β€” with kind being one of the options in the following list:
  • =Base β€” base type vibes (integer, boolean, etc).
  • =Const β€” a constant specifier ✨
  • =Enumerator β€” an enumerator.
  • =Import β€” an import declaration.
  • =ImportDeclaration β€” also an import declaration (yes, both exist, don't come for me).
  • =ImportModule β€” an 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 (say that five times fast).
  • =TemplateTypeParam β€” template type parameter.
  • =TemplateValueParam β€” template value parameter.
  • =Typedef β€” type definition.
  • =Unspecified β€” unspecified type.
  • =Volatile β€” volatile specifier.

πŸ¦‹ COMPARE πŸ¦‹

okay so real talk β€” when you're deep in debug info, sometimes just printing the elements is not the move. That's the sitch when what you actually care about is the fallout from different versions of the same toolchain, or what a specific compiler optimization did. πŸ‘€

In those cases you're hunting for which elements got added or removed. And because the debug info format is, respectfully, a whole mess, using a regular diff tool to find those elements is very difficult β€” honestly impossible when you're dealing with different debug formats. 😭

llvm-debuginfo-analyzer supports a logical element comparison, which lets you find semantic differences between logical views β€” the kind produced by different toolchain versions or even different debug information formats. Iconic behaviour, honestly.

When you compare logical views created from different debug formats, the accuracy depends on how closely the debug information represents the user code. For instance: a logical view created from a binary file with DWARF debug information may include more detailed data than a logical view created from a binary file with CodeView debug information. no cap, that's just how it is.

The following options describe the elements to compare.

  • --compare=<value[,value,...]> β€” with value being one of the options in the following list:
    • =all β€” include all the below elements.
    • =lines β€” include lines.
    • =scopes β€” include scopes.
    • =symbols β€” include symbols.
    • =types β€” include types.

So here's the tea: llvm-debuginfo-analyzer takes the first binary file on the command line as the reference and the second one as the target. And to get a more descriptive report, the comparison is done twice β€” the reference and target views get swapped, so you get those missing elements from the target view and those added elements to the reference view. double the slay, double the info. πŸ”₯

See --report options on how to describe the comparison reports. πŸ’–

🌸 WARNING 🌸

When reading the input object files, llvm-debuginfo-analyzer can detect issues in the raw debug information. These may not be considered fatal to the purpose of printing a logical view β€” but they can give you an indication about the quality, and potentially expose issues with the generated debug information. lowkey useful, highkey concerning. 🫢

The following options describe the warnings to be recorded for later printing, if they are requested by --print.

  • --warning=<value[,value,...]> β€” with value being one of the options in the following list:
    • =all β€” include all the below warnings.

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

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

⭐ INTERNAL ⭐

For a better understanding of the logical view, access to more detailed internal information could be needed. That kind of data helps you identify debug information that got processed, or incorrect logical element management. Typically these kind of options are available only in debug builds β€” but bestie, not here:

llvm-debuginfo-analyzer supports these advanced options in both release and debug builds, with the exception of the unique ID, which is generated only in debug builds. πŸ’…

  • --internal=<value[,value,...]> β€” with value being one of the options in the following list:
    • =all β€” include all the below options.

The following options allow you to check the integrity of the logical view; collect the debug tags that are processed or not implemented; ignore the logical element line number (so the logical view comparison is easier when you're using external comparison tools); and print the command line options used to invoke 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.
Note: For ELF format, the collected tags represent the debug tags that are not processed. For PE/COFF format, they represent the tags that are processed. 🧚

πŸŽ€ EXAMPLES πŸŽ€

This section includes some real binary files to show how to use llvm-debuginfo-analyzer to print a logical view and to diagnose possible debug information issues. actual real files, not vibes. πŸ’–

πŸ¦‹ TEST CASE 1 - GENERAL OPTIONS πŸ¦‹

The below example is used to show different output generated by llvm-debuginfo-analyzer. We compiled the example 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 (including regular expressions) to select the kind of logical elements to be included in the output. cute, curated, custom. πŸŽ€

πŸ’œ 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 is present within the debug information. The first column represents the scope level, followed by the associated line number (if any), 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} ('true' branch for the 'if' statement) β€” whereas in the original source code the typedef statement is clearly inside that block. So the 'INTEGER' definition should also be at level [004] inside the block. expose her. πŸ’€

🌈 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 so bestie, in this mode llvm-debuginfo-analyzer compares logical views against each other and spits out a report of the logical elements that are missing or added. This is genuinely a very powerful aid in finding semantic differences in debug info produced by different toolchain versions β€” or even completely different toolchains altogether. Like fr, a compiler producing DWARF can be directly compared against a completely different compiler that produces CodeView. Iconic behaviour, ngl.

Given the previous example, we found that debug info issue above (the one about 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 and get 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, GCC.

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, bestie. Same case when comparing CodeView debug info generated by MSVC and Clang. πŸ’–

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. Very "your whole vibe has to match" of it.

Using the llvm-debuginfo-analyzer comparison functionality, that issue can be seen in a more global context, and that 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. Sneaky but genius:

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. Chef's kiss.


LOGICAL ELEMENTS ✨

This one compares individual logical elements without caring whether their parents are the same. Chill of it. For both comparison methods, the equal criteria includes the name, source code location, type, and lexical scope level.

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 β€” because we love a plot twist:

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 get listed as Missing Types. Literally just swapped hats. 🎩


TEST CASE 2 - ASSEMBLER INSTRUCTIONS πŸ”₯

The example below is used to show the 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) 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. A whole little crossover episode:

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 show the intermixed lines and assembler instructions, which lets you compare the code generated by the different toolchains. It's giving side-by-side glow-up comparison, honestly. 🌈


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

The example below is used to show the 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  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 so focus bestie β€” lines 4 and 7 are the whole plot. They've got 2 typedefs in there, and those two live at different lexical scopes:

           4    typedef int INT;
           7      typedef float FLOAT;

and below are the logical views that llvm-debuginfo-analyzer spits out for 3 different compilers (MSVC, Clang and GCC) emitting different debug info formats (CodeView, DWARF) on different platforms. same source, four different realities. it's giving multiverse πŸ’€

          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}

so here's the tea, and it's a whole thing 😭 β€” looking at those logical views, the Clang compiler emits both typedefs at the same lexical scope (3), and that's just wrong. meanwhile GCC and MSVC emit the correct lexical scope for both typedefs. slay for them, rip for clang.

now, using the llvm-debuginfo-analyzer selection facilities, we can cook up a simple tabular output showing just the logical types that are 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'

it also shows that the CodeView debug information doesn't generate source code line numbers for those logical types. and yeah β€” the logical view is sorted by the types name. iykyk ✨


πŸ’€ TEST CASE 4 - MISSING NESTED ENUMERATIONS πŸ’€

the example below is here to show the different output llvm-debuginfo-analyzer generates. we compiled it 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  }

this one illustrates a scope issue found in the Clang compiler: PR46466 (Bugs LLVM) / PR45811 (GitHub LLVM) 🫢

and 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'
          [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 from those logical views up there, the DWARF debug information generated by the Clang compiler does not include any references to the enumerators RED and BLUE. Meanwhile the DWARF generated by GCC, plus the CodeView generated by Clang and MSVC? They do include such references. slay. πŸ’…

Using the llvm-debuginfo-analyzer selection facilities, we can produce a logical view showing just the logical types that are Enumerator and its parents. And the logical view is sorted by the types name. iconic.

          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 llvm-debuginfo-analyzer selection facilities again, we can 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. bestie, look at those little tables 🫢

          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. not me crying over missing debug info 😭


πŸ§ͺ TEST CASE 5 - INCORRECT LEXICAL SCOPE FOR VARIABLE πŸ§ͺ

The example below is used to show the 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). okay bestie, compiler chaos incoming πŸ’€

          // 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, the header file above defines forceinline. very demure, very mindful ✨

          #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 test above is used to illustrate a variable issue found in the Clang compiler: PR43860 (Bugs LLVM) / PR43205 (GitHub). yikes, compiler bug lore πŸ›

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. multi-platform mess, let's go 🌈

          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'

okay so looking at those logical views up there πŸ’… the tea is spilled: the CodeView debug info that Clang spits out puts the variables Var_1 and Var_2 at the same lexical scope (scope 4) inside the function InlineFunction (the page spells it InlineFuction, live with it 🫢). Meanwhile the DWARF from GCC/Clang and the CodeView from MSVC both put those variables at the correct lexical scope β€” 3 and 4 respectively. no cap. πŸ™ˆ


And because llvm-debuginfo-analyzer lets you slice and dice, we can pull a cute little tabular output that shows only the logical elements whose name contains the Var pattern. The logical view is sorted by the variable's name:

          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'

Also β€” and this is the part that's giving "the compiler forgot something" 😭 β€” it shows that the CodeView debug info 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 πŸ§ͺ

bestie, for the advanced girlies πŸ‘‘ llvm-debuginfo-analyzer can show you the low level info too β€” offsets inside the debug information section, debug location operands, linkage names, all of it. it's giving x-ray vision ✨

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

the Scope Sizes table is literally the "who's eating all the bytes" moment πŸ’€ it shows how many bytes each scope contributes to the debug information, which is perfect for spotting unexpected size changes in the DWARF sections when you bump between versions of the same toolchain. slay for CI regression hunting fr fr.

          [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 of a logical scope β€” in this case the function foo lives within lines 2 and 9. πŸŽ€

and the {Coverage} plus {Location} attributes describe the debug location and coverage of logical symbols. heads up bestie 🫢: for optimized code, the coverage value goes down and that affects how debuggable your program is. not me crying over an optimized binary 😭


🌐 WEBASSEMBLY SUPPORT 🌐

the example below exists purely to show off the WebAssembly output that llvm-debuginfo-analyzer generates. we compiled it for a WebAssembly 32-bit target with Clang (-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  }

this next command prints the basic details for all the logical elements, sorted by the debug information's internal offset β€” and it throws in the lexical level and the debug info format too, because of course it does πŸ’…βœ¨

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

or

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

Every row is one element living inside the debug info, bestie. First column = the scope level, then the line number that goes with it (if there even is one), and last but definitely not least, the description of the element itself. Read it like a family tree, iykyk 🌳

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'

🌸 SELECT LOGICAL ELEMENTS 🌸

OK so picture this: you wanna print every instruction, symbol and type that has block or .store chillin' in their name or type. You use a tab layout, and the summary tells you how many matches you got. Cute, right? ✨

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

🌈 COMPARISON MODE 🌈

So basically, from the example above, we spotted that debug-info issue (yep, the one about the invalid scope location for typedef int INTEGER β€” the audacity πŸ’€) by comparing against a whole other compiler. Iconic detective behaviour tbh πŸ•΅οΈβ€β™€οΈ

So we used GCC to generate test-dwarf-gcc.o, then applied a selection pattern together with the printing mode, and the logical view output came out like this:

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'

So the output shows both objects have the exact same elements. But plot twist: the typedef INTEGER is sitting at a different scope level. The GCC-generated object shows 4, and that's the correct value. Slay GCC, honestly πŸ‘‘

There are 2 comparison methods: logical view and logical elements. Let's get into it πŸ’…

πŸ¦‹ LOGICAL VIEW πŸ¦‹

This one compares the logical view as one whole unit β€” so for a match, every single compared logical element has to have the same parents and the same children. No wiggle room, periodt.

The output shows the missing (-) and added (+) elements in view form, and it gives you more context by swapping the reference and target object files. So thoughtful of it 🫢

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'

The output shows you the merging view path (reference and target) with the missing and added elements all up in there. Chef's kiss πŸ‘¨β€πŸ³πŸ’‹

πŸŽ€ LOGICAL ELEMENTS πŸŽ€

This one compares individual logical elements without caring whether their parents are the same. Independent queen energy πŸ‘‘ For both comparison methods, the equal criteria includes the name, source code location, type, and lexical scope level.

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

And now let's flip the Reference and Target order, just to see what happens πŸ‘€

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

See how that works? Because the Reference and Target got switched, the Added Types from the first case are now listed as Missing Types. Literally the same info, just mirrored πŸ”„ No cap.

πŸ’… EXIT STATUS πŸ’…

okay so when you're done running stuff, this is how you know if it went off without a hitch:

  • 0 β€” slay! ✨ The input files were parsed and printed successfully. Everything worked, no cap.
  • 1 β€” 😭 anything else. If it didn't parse and print all pretty like, you get a 1. That's it, that's the whole vibe check.

🫢 LIMITATIONS AND KNOWN ISSUES 🫢

ngl bestie, this one's short β€” it just says:

See Limitations.

That's literally it. The page points you at the Limitations section and dips. iykyk 🧚


🌈 SEE ALSO 🌈

  • llvm-dwarfdump β€” your other bestie in the LLVM tool fam πŸ’œ

πŸ‘‘ AUTHOR πŸ‘‘

Maintained by the LLVM Team (https://llvm.org/). iconic behavior fr fr πŸŽ€


2003-2025, LLVM Project β€” yeah they've been at it a minute πŸ’€βœ¨


okay that's the whole page bestie, you made it to the end πŸŽ‰ this gem is LLVM-DEBUGINFO-ANALYZER(1), from the LLVM source (version 19, duh πŸ’…), and it's dated 2025-01-17 β€” so yeah, fresh-ish, go read the whole thing again if you skipped the top πŸ™ˆ mwah, ily, go analyze some debug info πŸ¦‹πŸŒΈ

πŸ“¦ where this page comes from