Linux symbols and sources

This article describes how WinDbg supports standard Linux symbols and sources. Support for debugging on Linux requires WinDbg version 1.2402.24001.0 or above.

DebugInfoD symbol servers

The Window debugger uses the DebugInfoD standard for automatic download of build artifacts for Linux. Comparably, DebugInfoD is a combination of Microsoft's symbol server and source server technologies. It permits automatically downloading of three artifact types (executables (ELF), debug info (DWARF), and source (code) based on the build-id. Various distributions of Linux now host their own DebugInfoD servers which provide some of the artifact types. The various DebugInfoD servers are listed at ELFUTILS https://debuginfod.elfutils.org.

General information on DebugInfoD is available here:

The DebugInfoD* tag can point to one or more DebugInfoD servers with each server URL formatted as https://domain.com and separated by *. The servers will be searched in the same order as listed in the source path and the files will be retrieved from the first matching URL.

For example you can set the symbol path like this.

.sympath+ DebugInfoD*https://debuginfod.elfutils.org

Use the !sym noisy command to display information about symbol loading. For more information, see !sym.

The source path command (.srcpath, .lsrcpath (Set Source Path)) supports file retrieval from DebugInfoD servers through the DebugInfoD* tag, which allows for the retrieval of source code artifacts. For example, you can set the source path, like this.

.srcpath+ DebugInfoD*https://debuginfod.elfutils.org

For more information, see Source Code Extended Access.

DWARF symbols

DWARF is a widely used, standardized debugging data format. DWARF was originally designed along with Executable and Linkable Format (ELF), although it is independent of object file formats. For more information, see https://en.wikipedia.org/wiki/DWARF and for the version 5 standard, see DWARF Version 5.

Use the object dump command to determine the DWARF symbol version. In this example, version 5.

bob@BOB:/mnt/c/Users/BOB$ objdump -g DisplayGreeting | grep -A 2 'Compilation Unit @'
  Compilation Unit @ offset 0x0:
   Length:        0x285c (32-bit)
   Version:       5

WinDbg DWARF Support

WinDbg supports the following uses of DWARF and ELF.

  • Linux User Mode - Opening Linux ELF Core Dumps (-z <core dump>) and doing post-mortem debugging and analysis with full private DWARF symbols.

  • Linux Kernel Mode - Opening Linux Kernel (ELF VMCORE) Dumps and doing post-mortem debugging and analysis with full private DWARF symbols.

  • Linux Kernel Mode - Opening Linux Kernel compressed KDUMPs and doing post-mortem debugging and analysis with full private DWARF symbols (WinDbg only supports ZLIB compressed KDUMP files. LZO and Snappy compressed KDUMPs are unsupported.)

  • Opening ELF Images (-z <ELF image>) and examining contents, disassembly, etc.

  • Other Scenarios - Understanding ELF images and DWARF symbols in mixed PE/ELF environments (e.g.: debugging Open Enclave components loaded on Windows. For more information, see Open Enclave debugging.)

WinDbg GDBServer Linux support

The GNU Debugger, GDBServer is used on Linux to support the WinDbg connection. For more information about GDBServer, see https://en.wikipedia.org/wiki/Gdbserver. One place to view the documentation for remote gdb debugging is here - https://sourceware.org/gdb/current/onlinedocs/gdb#Remote-Debugging

For more information, on using GDBServer with WinDbg and a code walkthrough, see Linux live remote process debugging. The examples here use Ubuntu running under the Windows Subsystem for Linux (WSL), but other Linux implementations can also be used.

DWARF Implementation

DWARF symbols are supported embedded in the original image (a debug binary) or stripped into a separate ELF image (a debug package).

In order for the Linux DWARF stack walk to succeed, the original binary image for any module loaded into the Linux process must be able to be found.

DWARF symbols/ELF images (stripped or not) can be found via the debugger's sympath or the symbol server (indexed as per .NET Core via the GNU Build ID hash).

DWARF symbols can be found via a Linux style debug package install. Such is given by a directory named .build-id in the symbol path. Under this are directories named according to the first byte of the GNU Build ID hash. Under each such directory is a file named <remaining 18 bytes of GNU Build ID hash>.debug.

When the debugger opens DWARF symbols, it performs an initial indexing step as the format itself does not include necessary lookup tables. For large sets of DWARF symbols (e.g.: private DWARF information for the Linux kernel), this may take 10 - 30 seconds.

!addsourcemap for automatic source retrieval from known repo / commit

If you are debugging components built from a known repo and commit, there is an extension, !addsourcemap, debugger command allows you to tell the debugger that for a given module and path, you would like to automatically retrieve sources from a known URL. The usage of the extension is:

!addsourcemap <module> <local spec> <remote spec>

Where:

<module> is the name of the module of interest.

<local spec> is the path of sources within that module which will be looked up via a URL. This path should end in a wildcard.

<remote spec> is the URL at which files that match <local spec> will be looked up. This path should end in a wildcard which will be replaced with how the wildcard in <local spec> matches a particular source path.

To set the sourcemap, confirm that the module is present using lm (List Loaded Modules). Then determine the remote location of the source.

This example sets the vmlinux module to a specific build available on GitHub.

0:000> !addsourcemap vmlinux /build/linux/* https://raw.githubusercontent.com/torvalds/linux/6e61dde82e8bfe65e8ebbe43da45e615bc529236/
Source map /build/linux/* -> https://raw.githubusercontent.com/torvalds/linux/6e61dde82e8bfe65e8ebbe43da45e615bc529236/ successfully added

After the sourcemap command has been issued, a number of things will trigger a source load, for example switching frames back and forth or reloading, using the .reload command. After that, and an automatic source pull from GitHub will happen.

!sourcemaps

Use the !sourcemaps command to list existing source maps.

0:000> !sourcemaps
Source maps for vmlinux.6:
    /build/linux/* -> https://raw.githubusercontent.com/torvalds/linux/6e61dde82e8bfe65e8ebbe43da45e615bc529236/

!removesourcemaps

Use the !removesourcemaps command to remove an existing source map.

0:000> !removesourcemaps vmlinux /build/linux/* https://raw.githubusercontent.com/torvalds/linux/6e61dde82e8bfe65e8ebbe43da45e615bc529236/
1 source maps successfully removed

Troubleshooting DWARF symbols

If you are debugging Linux/Android dumps (or other targets that use DWARF symbols), you may want to look at the raw contents of symbols to understand why local variables, type definitions, or function definitions are incorrect. To do this, the debugger has some built-in extension commands to dump the raw contents of DWARF symbols. In addition use Linux utilities such as readelf and dumpdwarf to display symbol internals information.

readelf command

Use the readelf command at the Linux command prompt to display the debug build ID that was created for the sample DisplayGreeting program that was created in Linux live remote process debugging. In this example, a build ID of aba822dd158b997b09903d4165f3dbfd37f5e5c1 is returned.

Bob@BOB6:/mnt/c/Users/Bob$ readelf -n DisplayGreeting

Displaying notes found in: .note.gnu.property
  Owner                Data size        Description
  GNU                  0x00000020       NT_GNU_PROPERTY_TYPE_0
      Properties: x86 feature: IBT, SHSTK
        x86 ISA needed: x86-64-baseline

Displaying notes found in: .note.gnu.build-id
  Owner                Data size        Description
  GNU                  0x00000014       NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: aba822dd158b997b09903d4165f3dbfd37f5e5c1

Displaying notes found in: .note.ABI-tag
  Owner                Data size        Description
  GNU                  0x00000010       NT_GNU_ABI_TAG (ABI version tag)
    OS: Linux, ABI: 3.2.0

Readelf can be used with grep to return the symbol version.

 readelf --debug-dump=info DisplayGreeting | grep -A 2 'Compilation Unit @'
  Compilation Unit @ offset 0x0:
   Length:        0x285c (32-bit)
   Version:       5

dwarfdump

The dwarfdump linux command prints or checks DWARF sections as requested by specific options. Use dwarfdump -h to view the many options.

bob@BOB6:/mnt/c/Users/BOB$ dwarfdump -h

For more information about using dwarfdump on Ubuntu, see dwarfdump .

!diesym

This debugger command will display the DIE (or DIE subtree) for whatever symbol is at the given expression (can be an address, a function name, etc.) with an optionally specified recursion level. It locates the DIE for the symbol (typically function but could be data, etc...) contained at a given address and performs a diagnostic dump of the DIE similar to running dwarfdump or llvm-dwarfdump on the symbols and finding the DIE.

!diesym [options] <expression>

-r# : dump N levels recursively. Normally, this is one and only the DIE itself is dumped.

<expression> - The address to locate the DIE is given by an expression. It may be a flat hex address (0x<blah>) or it may be an otherwise unique function name.

It needs to be evaluatable by the data model's standard evaluation. Use the dx command to validate that the model expression. For more information about using the dx command, see dx (Display Debugger Object Model Expression).

0:000> dx DisplayGreeting!GetCppConGreeting
DisplayGreeting!GetCppConGreeting                 : DisplayGreeting!GetCppConGreeting+0x0 [Type: GetCppConGreeting]

Display DIE symbol information for the sample DisplayGreeting program, GetCppConGreeting function.

0:000> !diesym DisplayGreeting!GetCppConGreeting
0x2816: DW_TAG_subprogram [^^^]
    DW_AT_external          (true)
    DW_AT_name              'GetCppConGreeting'
    DW_AT_decl_file         1 ('/mnt/c/Users/BOB/DisplayGreeting.cpp')
    DW_AT_decl_line         0x7
    DW_AT_decl_column       0x6
    DW_AT_linkage_name      '_Z17GetCppConGreetingPwm'
    DW_AT_low_pc            0x11E9
    DW_AT_high_pc           +0x3c (== 0x1225)
    DW_AT_frame_base        DW_OP_call_frame_cfa 
    DW_AT_call_all_tail_calls   (true)

Use the -r2 option to display an additional level of DIE symbol information.

0:000> !diesym -r2 DisplayGreeting!GetCppConGreeting
0x2816: DW_TAG_subprogram [^^^]
    DW_AT_external          (true)
    DW_AT_name              'GetCppConGreeting'
    DW_AT_decl_file         1 ('/mnt/c/Users/BOB/DisplayGreeting.cpp')
    DW_AT_decl_line         0x7
    DW_AT_decl_column       0x6
    DW_AT_linkage_name      '_Z17GetCppConGreetingPwm'
    DW_AT_low_pc            0x11E9
    DW_AT_high_pc           +0x3c (== 0x1225)
    DW_AT_frame_base        DW_OP_call_frame_cfa 
    DW_AT_call_all_tail_calls   (true)

    0x2834: DW_TAG_formal_parameter [^^^]
        DW_AT_name              'buffer'
        DW_AT_decl_file         1 ('/mnt/c/Users/BOB/DisplayGreeting.cpp')
        DW_AT_decl_line         0x7
        DW_AT_decl_column       0x21
        DW_AT_type              (CU + 0x12f7 == 0x12f7)
        DW_AT_location          DW_OP_fbreg(-40) 

!die

!die will display the DIE (or DIE subtree) for whatever DIE is at the given offset expression within the DWARF debug section with an optionally specified recursion level.

!die [-r#] [-t] -m <module base expression> <offset expression>

-r# : dump N levels recursively.

-t : If the DIE is within a type unit in .debug_types instead of a compilation unit within .debug_info, you must specify the -t switch.

Supply an -m <module base expression> that gives the base address of whatever module you are querying about.

The <offset expression> is the size of the DIE offset.

At the Linux prompt use dwarfdump with the -r to print the .debug_aranges section of the DWARF file to locate the DIE offset.

bob@BOB6:/mnt/c/Users/BOB$ dwarfdump -r DisplayGreeting

.debug_aranges

COMPILE_UNIT<header overall offset = 0x00000000>:
< 0><0x0000000c>  DW_TAG_compile_unit
                    DW_AT_producer              GNU C++17 11.4.0 -mtune=generic -march=x86-64 -g -fasynchronous-unwind-tables -fstack-protector-strong -fstack-clash-protection -fcf-protection
                    DW_AT_language              DW_LANG_C_plus_plus_14
                    DW_AT_name                  DisplayGreeting.cpp
                    DW_AT_comp_dir              /mnt/c/Users/BOB
                    DW_AT_ranges                0x0000000c

      Offset of rnglists entries: 0x0000000c
      [ 0] start,end             0x000011e9 0x0000134a
      [ 1] start,end             0x0000134a 0x00001368
      [ 2] start,end             0x00001368 0x0000137b
      [ 3] start,end             0x0000137b 0x0000138d
      [ 4] end of list
                    DW_AT_low_pc                0x00000000
                    DW_AT_stmt_list             0x00000000


arange starts at 0x000011e9, length of 0x00000161, cu_die_offset = 0x0000000c
arange starts at 0x0000134a, length of 0x0000001e, cu_die_offset = 0x0000000c
arange starts at 0x00001368, length of 0x00000013, cu_die_offset = 0x0000000c
arange starts at 0x0000137b, length of 0x00000012, cu_die_offset = 0x0000000c

Note the DW_AT_ranges value of 0x0000000c. In the debugger use that offset value and the module name of DisplayGreeting to display DIE symbol information.

0:000> !die -m DisplayGreeting 0x0000000c
0xc: DW_TAG_compile_unit [^^^]
    DW_AT_producer          'GNU C++17 11.4.0 -mtune=generic -march=x86-64 -g -fasynchronous-unwind-tables -fstack-protector-strong -fstack-clash-protection -fcf-protection'
    DW_AT_language          0x21
    DW_AT_name              
    DW_AT_comp_dir          
    DW_AT_ranges            
        [0x11e9 - 0x134a)
        [0x134a - 0x1368)
        [0x1368 - 0x137b)
        [0x137b - 0x138d)
    DW_AT_low_pc            0x0
    DW_AT_stmt_list        

!dieancestry

The !dieancestry command behaves similar to !die except that it walks up the DIE tree towards the containing compilation or type unit, instead of down the tree.

!dieancestry [-r#] [-t] -m <module base expression> <offset expression>

-r# : dump N levels recursively.

Supply an -m <module base expression> that gives the base address of whatever module you are querying about.

The <offset expression> is the size of the DIE offset.

Example:

0:000> !dieancestry -m DisplayGreeting 0x0000000c
0xc: DW_TAG_compile_unit [^^^]
    DW_AT_producer          'GNU C++17 11.4.0 -mtune=generic -march=x86-64 -g -fasynchronous-unwind-tables -fstack-protector-strong -fstack-clash-protection -fcf-protection'
    DW_AT_language          0x21
    DW_AT_name              
    DW_AT_comp_dir          
    DW_AT_ranges            
        [0x11e9 - 0x134a)
        [0x134a - 0x1368)
        [0x1368 - 0x137b)
        [0x137b - 0x138d)
    DW_AT_low_pc            0x0
    DW_AT_stmt_list       

Note that links, for example to parents or siblings, are clickable to allow for further traversal of the DWARF symbol tree.

0:000> !die -r2 -m 0x555555554000 0xc
0xc: DW_TAG_compile_unit [^^^]
    DW_AT_producer          'GNU C++17 11.4.0 -mtune=generic -march=x86-64 -g -fasynchronous-unwind-tables -fstack-protector-strong -fstack-clash-protection -fcf-protection'
    DW_AT_language          0x21
    DW_AT_name              
    DW_AT_comp_dir          
    DW_AT_ranges            
        [0x11e9 - 0x134a)
        [0x134a - 0x1368)
        [0x1368 - 0x137b)
        [0x137b - 0x138d)
    DW_AT_low_pc            0x0
    DW_AT_stmt_list         

    0x2a: DW_TAG_namespace [^^^]
        DW_AT_name              'std'
        DW_AT_decl_file         9 ('/usr/include/c++/11/bits/exception_ptr.h')
        DW_AT_decl_line         0x116
        DW_AT_decl_column       0xb
        DW_AT_sibling           (CU + 0xf01 == 0xf01)

    0xf01: DW_TAG_base_type [^^^]
        DW_AT_byte_size         0x1
        DW_AT_encoding          DW_ATE_boolean (2)
        DW_AT_name              'bool'

    0xf08: DW_TAG_base_type [^^^]
        DW_AT_byte_size         0x8
        DW_AT_encoding          DW_ATE_unsigned (7)
        DW_AT_name              'long unsigned int'

...
   

Not all output is shown.

!dwunwind

!dwunwind is somewhat similar to .fnent (Display function data) for PE images. It displays the DWARF unwind rules for an address given by the expression. It is also similar to the readelf --unwind command, that displays unwind information, when it is available.

!dwunwind <expression>

This example displays the unwind rules for the GetCppConGreeting function in the DisplayGreeting program.

0:000> !dwunwind DisplayGreeting!GetCppConGreeting
DW_FRAME_SAME_VAL: 0('rax'), 1('rdx'), 2('rcx'), 3('rbx'), 4('rsi'), 5('rdi'), 6('rbp'), 7('rsp'), 8('r8'), 9('r9'), 10('r10'), 11('r11'), 12('r12'), 13('r13'), 14('r14'), 15('r15')
0('CFA'): DW_EXPR_OFFSET 7('rsp') + 8
16('<Return Address>'): DW_EXPR_OFFSET 12290('CFA') + -8

This displays the unwind stack for the instruction pointer register.

0:000> !dwunwind @rip
DW_FRAME_SAME_VAL: 0('rax'), 1('rdx'), 2('rcx'), 4('rsi'), 5('rdi'), 7('rsp'), 8('r8'), 9('r9'), 10('r10'), 11('r11'), 14('r14'), 15('r15')
0('CFA'): DW_EXPR_OFFSET 7('rsp') + 208
3('rbx'): DW_EXPR_OFFSET 12290('CFA') + -40
6('rbp'): DW_EXPR_OFFSET 12290('CFA') + -32
12('r12'): DW_EXPR_OFFSET 12290('CFA') + -24
13('r13'): DW_EXPR_OFFSET 12290('CFA') + -16
16('<Return Address>'): DW_EXPR_OFFSET 12290('CFA') + -8

Here is a program counter example.

   0:000> !dwunwind @pc
   DW_FRAME_SAME_VAL: 0('x0'), 1('x1'), 2('x2'), 3('x3'), 4('x4'), 5('x5'), 6('x6'), 7('x7'), 8('x8'), 9('x9'), 10('x10'), 11('x11'), 12('x12'), 13('x13'), 14('x14'), 15('x15'), 16('x16'), 17('x17'), 18('x18'), 31('sp'), 32('pc')
   0('CFA'): DW_EXPR_OFFSET 31('sp') + 208
   19('x19'): DW_EXPR_OFFSET 1436('CFA') + -192
   20('x20'): DW_EXPR_OFFSET 1436('CFA') + -184
   21('x21'): DW_EXPR_OFFSET 1436('CFA') + -176
   22('x22'): DW_EXPR_OFFSET 1436('CFA') + -168
   23('x23'): DW_EXPR_OFFSET 1436('CFA') + -160
   24('x24'): DW_EXPR_OFFSET 1436('CFA') + -152
   25('x25'): DW_EXPR_OFFSET 1436('CFA') + -144
   26('x26'): DW_EXPR_OFFSET 1436('CFA') + -136
   27('x27'): DW_EXPR_OFFSET 1436('CFA') + -128
   28('x28'): DW_EXPR_OFFSET 1436('CFA') + -120
   29('fp'): DW_EXPR_OFFSET 1436('CFA') + -208
   30('lr'): DW_EXPR_OFFSET 1436('CFA') + -200

!dietree

Dumps the DIE tree for a given module at a given recursion level similar to running dwarfdump or llvm-dwarfdump on the symbols and finding the DIE.

!dietree [OPTIONS] -m <module base> <offset expression>

-r# : Specify recursion level

-t : Dump .debug_types and not .debug_info

The module base for the module containing the DIE must be given by the -m <expression> option.

The <offset expression> is the size of the DIE offset.

Example:

0:000> !dietree -m DisplayGreeting 0x0000000c
0xc: DW_TAG_compile_unit [^^^]
    DW_AT_producer          'GNU C++17 11.4.0 -mtune=generic -march=x86-64 -g -fasynchronous-unwind-tables -fstack-protector-strong -fstack-clash-protection -fcf-protection'
    DW_AT_language          0x21
    DW_AT_name              
    DW_AT_comp_dir          
    DW_AT_ranges            
        [0x11e9 - 0x134a)
        [0x134a - 0x1368)
        [0x1368 - 0x137b)
        [0x137b - 0x138d)
    DW_AT_low_pc            0x0
    DW_AT_stmt_list  

Use the -r2 option to display additional values in the dietree.

0:000> !dietree -r2 -m DisplayGreeting 0x0000000c
0xc: DW_TAG_compile_unit [^^^]
    DW_AT_producer          'GNU C++17 11.4.0 -mtune=generic -march=x86-64 -g -fasynchronous-unwind-tables -fstack-protector-strong -fstack-clash-protection -fcf-protection'
    DW_AT_language          0x21
    DW_AT_name              
    DW_AT_comp_dir          
    DW_AT_ranges            
        [0x11e9 - 0x134a)
        [0x134a - 0x1368)
        [0x1368 - 0x137b)
        [0x137b - 0x138d)
    DW_AT_low_pc            0x0
    DW_AT_stmt_list         

    0x2a: DW_TAG_namespace [^^^]
        DW_AT_name              'std'
        DW_AT_decl_file         9 ('/usr/include/c++/11/bits/exception_ptr.h')
        DW_AT_decl_line         0x116
        DW_AT_decl_column       0xb
        DW_AT_sibling           (CU + 0xf01 == 0xf01)

    0xf01: DW_TAG_base_type [^^^]
        DW_AT_byte_size         0x1
        DW_AT_encoding          DW_ATE_boolean (2)
        DW_AT_name              'bool'

    0xf08: DW_TAG_base_type [^^^]
        DW_AT_byte_size         0x8
        DW_AT_encoding          DW_ATE_unsigned (7)
        DW_AT_name              'long unsigned int'

    0xf0f: DW_TAG_base_type [^^^]
        DW_AT_byte_size         0x1
        DW_AT_encoding          DW_ATE_unsigned_char (8)
        DW_AT_name              'unsigned char'

...

Not all output is shown. Note that links, for example to siblings, are clickable to allow for further traversal of the DWARF symbol tree.

!dielocal

Locates the DIE for the local variable named "name" and performs a diagnostic dump of the DIE similar to running dwarfdump or llvm-dwarfdump on the symbols and finding the DIE.

!dielocal [options] <name>

-r# : dump N levels recursively. Normally, this is one and only the DIE itself is dumped.

<name> : local variable named "name".

Example:

0:000> !dielocal greeting
0x2806: DW_TAG_variable [^^^]
    DW_AT_name              'greeting'
    DW_AT_decl_file         1 ('/mnt/c/Users/BOB/DisplayGreeting.cpp')
    DW_AT_decl_line         0xf
    DW_AT_decl_column       0x1d
    DW_AT_type              (CU + 0xb18 == 0xb18)
    DW_AT_location          DW_OP_fbreg(-240) 

See also

Source Code Extended Access

ELFUTILS debuginfod

DWARF Version 5

Using Symbols

!sym