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)