Linux 符号和源

本文介绍 WinDbg 如何支持标准 Linux 符号和源。 在 Linux 上支持调试需要 WinDbg 版本 1.2402.24001.0 或更高版本。

DebugInfoD 符号服务器

Window 调试器使用 DebugInfoD 标准自动下载适用于 Linux 的生成项目。 相比之下,DebugInfoD 是 Microsoft 符号服务器和源服务器技术的组合。 它允许基于生成 ID 自动下载三种项目类型(可执行文件 (ELF)、调试信息 (DWARF) 和源代码(代码)。现在,Linux 的各种分发版托管自己的 DebugInfoD 服务器,这些服务器提供一些项目类型。 ELFUTILS https://debuginfod.elfutils.org 中列出了各种 DebugInfoD 服务器。

有关 DebugInfoD 的一般信息,请访问此处:

DebugInfoD* 标记可以指向一个或多个 DebugInfoD 服务器,其中每个服务器 URL 的格式设置为https://domain.com,并用 * 分隔。 将按源路径中列出的顺序搜索服务器,并从第一个匹配的 URL 检索文件。

例如,可以这样设置符号路径。

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

使用 !sym noisy 命令可以显示有关符号加载的信息。 有关详细信息,请参阅 !sym

源路径命令(.srcpath, .lsrcpath(设置源路径))支持通过 DebugInfoD* 标记从 DebugInfoD 服务器检索文件,从而允许检索源代码项目。 例如,可以这样设置源路径。

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

有关详细信息,请参阅源代码扩展访问

DWARF 符号

DWARF 是一种广泛使用的标准化调试数据格式。 DWARF 最初与可执行文件和可链接格式 (ELF) 一起设计的,但是它独立于对象文件格式。 有关详细信息,请参阅 https://en.wikipedia.org/wiki/DWARF;如需了解版本 5 标准,请参阅 DWARF 版本 5

使用对象转储命令可以确定 DWARF 符号版本。 在本示例中,为版本 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 支持

WinDbg 支持 DWARF 和 ELF 的以下用法。

  • Linux 用户模式 - 打开 Linux ELF 核心转储 (-z <core dump>),并使用完整的专用 DWARF 符号执行事后调试和分析。

  • Linux 内核模式 - 打开 Linux 内核 (ELF VMCORE) 转储,并使用完整的专用 DWARF 符号执行事后调试和分析。

  • Linux 内核模式 - 打开 Linux 内核压缩的 KDUMP,并使用完整的专用 DWARF 符号执行事后调试和分析(WinDbg 仅支持 ZLIB 压缩的 KDUMP 文件。不支持 LZO 和 Snappy 压缩的 KDUMP。)

  • 打开 ELF 图像 (-z <ELF image>),并检查内容、反汇编等。

  • 其他方案 - 了解混合 PE/ELF 环境中的 ELF 映像和 DWARF 符号(例如:调试 Windows 上加载的 Open Enclave 组件。有关详细信息,请参阅 Open Enclave 调试。)

WinDbg GDBServer Linux 支持

GNU 调试器 GDBServer 在 Linux 上用于支持 WinDbg 连接。 有关 GDBServer 的详细信息,请参阅 https://en.wikipedia.org/wiki/Gdbserver。 提供查看远程 gdb 调试文档的一站式位置是 - https://sourceware.org/gdb/current/onlinedocs/gdb#Remote-Debugging

有关将 GDBServer 与 WinDbg 配合使用以及代码演练的详细信息,请参阅 Linux 实时远程进程调试。 以下示例使用在适用于 Linux 的 Windows 子系统 (WSL) 下运行的 Ubuntu,但也可以使用其他 Linux 实现。

DWARF 实现

支持 DWARF 符号嵌入到原始映像(调试二进制文件)中,或剥离到单独的 ELF 映像(调试包)中。

为了使 Linux DWARF 堆栈审核成功,必须能够找到加载到 Linux 进程中的任何模块的原始二进制映像。

DWARF 符号/ELF 映像(无论剥离与否)可以通过调试器的 sympath 或符号服务器(通过 GNU 生成 ID 哈希按 .NET Core 编制索引)找到。

可以通过 Linux 样式调试包安装找到 DWARF 符号。 这是由符号路径中名为 .build-id 的目录给出的。 下面是根据 GNU 生成 ID 哈希的第一个字节命名的目录。 在每个这样的目录下都有一个名为 <remaining 18 bytes of GNU Build ID hash>.debug 的文件。

当调试器打开 DWARF 符号时,将执行初始索引步骤,因为格式本身不包括必要的查找表。 对于大型 DWARF 符号集(例如:Linux 内核的专用 DWARF 信息),可能需要 10 - 30 秒。

!addsourcemap 用于从已知 repo / commit 中自动检索源

如果要调试从已知存储库和提交生成的组件,则使用扩展 !addsourcemap 调试器命令可以告知调试器你希望从已知 URL 自动检索源的给定模块和路径。 扩展的用法为:

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

其中:

<module> 是感兴趣的模块的名称。

<local spec> 是该模块中将通过 URL 查找的源的路径。 此路径应以通配符结尾。

<remote spec> 是查找与 <local spec> 匹配的文件的 URL。 此路径应以通配符结尾,通配符将被 <local spec> 中的通配符如何匹配特定的源路径所替换。

若要设置源映射,请使用 lm(列出加载的模块)确认模块是否存在。 然后确定源的远程位置。

此示例将 vmlinux 模块设置为 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

发出 sourcemap 命令后,许多操作将触发源加载。例如,使用 .reload 命令来回切换帧或重新加载。 之后,将自动从 GitHub 中提取源代码。

!sourcemaps

使用 !sourcemaps 命令列出现有的源映射。

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

!removesourcemaps

使用 !removesourcemaps 命令删除现有的源映射。

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

DWARF 符号疑难解答

如果要调试 Linux/Android 转储(或使用 DWARF 符号的其他目标),可能需要查看符号的原始内容,以了解局部变量、类型定义或函数定义不正确的原因。 为此,可以使用调试器具有的一些内置扩展命令来转储 DWARF 符号的原始内容。 此外,可以使用 Readelf 和 dumpdwarf 等 Linux 实用工具显示符号内部信息。

readelf 命令

在 Linux 命令提示符下使用 readelf 命令显示为 Linux 实时远程进程调试中创建的示例 DisplayGreeting 程序创建的调试版本 ID。 在此示例中,将返回 aba822dd158b997b09903d4165f3dbfd37f5e5c1 的内部版本 ID。

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 可以与 grep 一起使用以返回符号版本。

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

dwarfdump

dwarfdump linux 命令根据特定选项的要求输出或检查 DWARF 节。 使用 dwarfdump -h 可查看许多选项。

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

有关在 Ubuntu 上使用 dwarfdump 的详细信息,请参阅 dwarfdump

!diesym

此调试器命令将显示给定表达式(可以是地址、函数名称等)中具有可选指定递归级别的符号的 DIE(或 DIE 子树)。 它为给定地址中包含的符号(通常是函数,但也可能是数据等)定位 DIE,并执行 DIE 的诊断转储,类似于在符号上运行 dwarfdump 或 llvm-dwarfdump,并查找 DIE。

!diesym [options] <expression>

-r# :以递归方式转储 N 个级别。 通常,这是一个,只有 DIE 本身被转储。

<expression> - 用于定位 DIE 的地址由一个表达式给出。 它可以是一个平面十六进制地址 (0x<blah>),也可以是一个其他唯一的函数名。

需要通过数据模型的标准评估进行评估。 使用 dx 命令验证模型表达式。 有关使用 dx 命令的详细信息,请参阅 dx(显示调试器对象模型表达式)

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

显示示例 DisplayGreeting 程序 GetCppConGreeting 函数的 DIE 符号信息。

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)

使用 -r2 选项可显示附加级别的 DIE 符号信息。

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 将在 DWARF 调试部分中以可选指定的递归级别显示给定偏移量表达式处的任何 DIE 的 DIE(或 DIE 子树)。

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

-r# :以递归方式转储 N 个级别。

-t: :如果 DIE 位于 .debug_types 的类型单元中,而不是 .debug_info 中的编译单元,则必须指定 -t 开关。

提供一个 -m <module base expression>,提供要查询的任何模块的基址。

<offset expression> 是 DIE 偏移量的大小。

在 Linux 提示符下,将 dwarfdump 与 -r 一起使用,输出 DWARF 文件的 .debug_aranges 部分,以查找 DIE 偏移量。

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

记下 0x0000000c 的 DW_AT_ranges 值。 在调试器中,使用该偏移值和 DisplayGreeting 的模块名称来显示 DIE 符号信息。

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

!dieancestry 命令的行为类似于 !die,只是它沿着 DIE 树向上移动到包含编译单元或类型单元,而不是沿着树向下移动。

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

-r# :以递归方式转储 N 个级别。

提供一个 -m <module base expression>,提供要查询的任何模块的基址。

<offset expression> 是 DIE 偏移量的大小。

示例:

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       

请注意,可以单击链接(例如,到父节点或同级节点的链接),以便进一步遍历 DWARF 符号树。

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'

...
   

未显示所有输出。

!dwunwind

!dwunwind 有点类似于 PE 映像的 .fnent(显示函数数据)。 它显示表达式给定的地址的 DWARF 展开规则。 它也类似于 readelf --unwind 命令,后者在可用时显示展开信息。

!dwunwind <expression>

此示例显示 DisplayGreeting 程序中 GetCppConGreeting 函数的展开规则。

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

这将显示指令指针寄存器的展开堆栈。

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

下面是程序计数器示例。

   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

在给定的递归级别上为给定模块转储 DIE 树,类似于在符号上运行 dwarfdump 或 llvm-dwarfdump,并查找 DIE。

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

-r#:指定递归级别

-t:转储 .debug_types 而不是 .debug_info

包含 DIE 的模块的模块基必须由 -m <expression> 选项给出。

<offset expression> 是 DIE 偏移量的大小。

示例:

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  

使用 -r2 选项可以在 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'

...

未显示所有输出。 请注意,可以单击链接(例如,到同级节点的链接),以便进一步遍历 DWARF 符号树。

!dielocal

为名为“name”的局部变量定位 DIE,并执行 DIE 的诊断转储,类似于在符号上运行 dwarfdump 或 llvm-dwarfdump,并查找DIE。

!dielocal [options] <name>

-r# :以递归方式转储 N 个级别。 通常,这是一个,只有 DIE 本身被转储。

<name>:名为“name”的局部变量。

示例:

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) 

另请参阅

源代码扩展访问

ELFUTILS debuginfod

DWARF 版本 5

使用符号

!sym