llvm-symbolizer - 将地址转换为源代码位置

概要

llvm-symbolizer [选项] [地址…]

描述

llvm-symbolizer 从命令行读取输入名称和地址,并将相应的源代码位置打印到标准输出。它还可以通过 --filter-markup 来解析包含 符号化标记 的日志。

如果在命令行上未指定地址,则从标准输入读取地址。如果在命令行上未指定输入名称,但指定了地址,则第一个地址值将被视为输入名称。如果输入值无法识别,则报告找不到源信息。

输入名称可以与地址一起在标准输入上或作为命令行上的位置参数指定。默认情况下,输入名称被解释为对象文件路径。但是,在名称前加上 BUILDID: 表示它是一个十六进制构建 ID,而不是路径。这将查找相应的调试二进制文件。为了保持一致性,在名称前加上 FILE: 明确表示它是一个对象文件路径(默认值)。

位置参数或标准输入值可以以 “DATA” 或 “CODE” 开头,以指示应将地址符号化为数据或可执行代码。如果两者都未指定,则假定为 “CODE”。DATA 被符号化为地址和符号大小,而不是行号。

llvm-symbolizer 在解析命令行选项后,从环境变量 LLVM_SYMBOLIZER_OPTS 中解析选项。LLVM_SYMBOLIZER_OPTS 主要用于在 llvm-symbolizer 被另一个程序或运行时调用时补充命令行选项。

示例

以下所有示例都使用以下两个源文件作为输入。它们混合使用了 C 风格和 C++ 风格的链接,以说明这些名称的打印方式有何不同(请参阅 --demangle)。

// test.h
extern "C" inline int foz() {
  return 1234;
}
// test.cpp
#include "test.h"
int bar=42;

int foo() {
  return bar;
}

int baz() {
  volatile int k = 42;
  return foz() + k;
}

int main() {
  return foo() + baz();
}

这些文件按如下方式构建

$ clang -g test.cpp -o test.elf
$ clang -g -O2 test.cpp -o inlined.elf

示例 1 - 命令行上的地址和对象

$ llvm-symbolizer --obj=test.elf 0x4004d0 0x400490
foz
/tmp/test.h:1:0

baz()
/tmp/test.cpp:11:0

示例 2 - 标准输入上的地址

$ cat addr.txt
0x4004a0
0x400490
0x4004d0
$ llvm-symbolizer --obj=test.elf < addr.txt
main
/tmp/test.cpp:15:0

baz()
/tmp/test.cpp:11:0

foz
/tmp/./test.h:1:0

示例 3 - 与地址一起指定的对象

$ llvm-symbolizer "test.elf 0x400490" "FILE:inlined.elf 0x400480"
baz()
/tmp/test.cpp:11:0

foo()
/tmp/test.cpp:8:10

$ cat addr2.txt
FILE:test.elf 0x4004a0
inlined.elf 0x400480

$ llvm-symbolizer < addr2.txt
main
/tmp/test.cpp:15:0

foo()
/tmp/test.cpp:8:10

示例 4 - BUILDID 和 FILE 前缀

$ llvm-symbolizer "FILE:test.elf 0x400490" "DATA BUILDID:123456789abcdef 0x601028"
baz()
/tmp/test.cpp:11:0

bar
6295592 4

$ cat addr3.txt
FILE:test.elf 0x400490
DATA BUILDID:123456789abcdef 0x601028

$ llvm-symbolizer < addr3.txt
baz()
/tmp/test.cpp:11:0

bar
6295592 4

示例 5 - CODE 和 DATA 前缀

$ llvm-symbolizer --obj=test.elf "CODE 0x400490" "DATA 0x601028"
baz()
/tmp/test.cpp:11:0

bar
6295592 4

$ cat addr4.txt
CODE test.elf 0x4004a0
DATA inlined.elf 0x601028

$ llvm-symbolizer < addr4.txt
main
/tmp/test.cpp:15:0

bar
6295592 4

示例 6 - 路径样式选项

此示例使用与上述相同的源文件,但源文件的完整路径为 /tmp/foo/test.cpp,并按如下方式编译。第一个用例显示默认的绝对路径,第二个用例显示 –basenames,第三个用例显示 –relativenames。

$ pwd
/tmp
$ clang -g foo/test.cpp -o test.elf
$ llvm-symbolizer --obj=test.elf 0x4004a0
main
/tmp/foo/test.cpp:15:0
$ llvm-symbolizer --obj=test.elf 0x4004a0 --basenames
main
test.cpp:15:0
$ llvm-symbolizer --obj=test.elf 0x4004a0 --relativenames
main
foo/test.cpp:15:0

示例 7 - 作为符号名称的地址

$ llvm-symbolizer --obj=test.elf main
main
/tmp/test.cpp:14:0
$ llvm-symbolizer --obj=test.elf "CODE foz"
foz
/tmp/test.h:1:0

示例 8 - 对于没有行号对应关系的地址(与零行关联的地址),--skip-line-zero 输出

// test.c
int foo = 0;
int x = 1234;
int main() {
  if (x)
    return foo;
  else
    return x;
}

这些文件按如下方式构建

$ clang -g -O2 -S test.c -o test.s
$ llvm-mc -filetype=obj -triple=x86_64-unknown-linux  test.s -o test.o
$ llvm-symbolizer --obj=test.o --skip-line-zero 0xa
main
/tmp/test.c:5:7 (approximate)

选项

--adjust-vma <offset>

在执行查找时,将指定的偏移量添加到对象文件地址。这可以用于执行查找,就像对象被偏移量重定位一样。

--skip-line-zero

如果地址没有关联的行号,则使用行表中当前序列中的最后一行号。此类行在输出中标记为 “approximate”,因为它们可能具有误导性。

--basenames, -s

仅打印文件名,不带任何目录,而不是绝对路径。

--build-id

使用给定的构建 ID(指定为十六进制字符串)查找对象。与 --obj 互斥。

--color [=<always|auto|never>]

指定是否在 --filter-markup 模式下使用颜色。默认为 auto,它检测标准输出是否支持颜色。单独指定 --color 等同于 --color=always

--debug-file-directory <path>

提供一个包含 .build-id 子目录的目录路径,以搜索已剥离二进制文件的调试信息。将按给定的顺序搜索此参数的多个实例。

--debuginfod, --no-debuginfod

是否尝试 debuginfod 查找调试二进制文件。除非另有说明,否则仅当 libcurl 已编译(LLVM_ENABLE_CURL)并且环境变量 DEBUGINFOD_URLS 提供了至少一个服务器 URL 时,debuginfod 才启用。

--demangle, -C

打印 demangled 函数名称,如果名称是 mangled 的(例如,mangled 名称 _Z3bazv 变为 baz(),而非 mangled 名称 foz 则按原样打印)。默认为 true。

--dwp <path>

对于任何具有拆分 DWARF 调试数据的 CU,使用 <path> 中指定的 DWP 文件。

--fallback-debug-path <path>

当单独的文件包含调试数据,并且由 GNU 调试链接部分引用时,如果无法相对于对象找到调试数据,则使用指定的路径作为查找调试数据的基础。

--filter-markup

从标准输入读取,将包含的 符号化标记 转换为人类可读的形式,并将结果打印到标准输出。以下标记元素尚不支持

  • {{{hexdict}}}

  • {{{dumpfile}}}

{{{bt}}} 回溯元素使用以下语法报告帧

#<number>[.<inline>] <address> <function> <file>:<line>:<col> (<module>+<relative address>)

<inline> 为内联到与 <number> 对应的调用者中的调用提供帧号。内联调用号从 1 开始,并从被调用者增加到调用者。

<address> 是函数调用指令内的地址。该地址可能不是指令的开始。<relative address> 是加载到该地址的 <module> 中的相应虚拟偏移量。

--functions [=<none|short|linkage>], -f

指定函数名称的打印方式(省略函数名称、打印短函数名称或分别打印完整链接名称)。默认为 linkage

--help, -h

显示此命令的帮助和用法。

--inlining, --inlines, -i

如果源代码位置在内联函数中,则打印所有内联帧。这是默认设置。

--no-inlines

不打印内联帧。

--no-demangle

不打印 demangled 函数名称。

--obj <path>, --exe, -e

要符号化的对象文件路径。如果指定 -,则直接从标准输入流读取对象。与 --build-id 互斥。

--output-style <LLVM|GNU|JSON>

指定首选的输出样式。默认为 LLVM。当输出样式设置为 GNU 时,该工具遵循 GNU 的 addr2line 的样式。GNU 样式与 LLVM 样式的区别在于

  • 不打印源代码位置的列号。

  • 在地址报告后不添加空行。

  • 当不显示内联帧时,不将内联函数的名称替换为最顶层调用者的名称。

  • 当地址的调试数据鉴别符为非零时,打印该鉴别符。生成鉴别符的一种方法是使用 clang 的 -fdebug-info-for-profiling 进行编译。

JSON 样式以 JSON 格式提供机器可读的输出。如果地址是通过 stdin 提供的

输出 JSON 将是一系列单独的对象。否则,所有结果都将包含在单个数组中。

$ llvm-symbolizer --obj=inlined.elf 0x4004be 0x400486 -p
baz() at /tmp/test.cpp:11:18
 (inlined by) main at /tmp/test.cpp:15:0

foo() at /tmp/test.cpp:6:3

$ llvm-symbolizer --output-style=LLVM --obj=inlined.elf 0x4004be 0x400486 -p --no-inlines
main at /tmp/test.cpp:11:18

foo() at /tmp/test.cpp:6:3

$ llvm-symbolizer --output-style=GNU --obj=inlined.elf 0x4004be 0x400486 -p --no-inlines
baz() at /tmp/test.cpp:11
foo() at /tmp/test.cpp:6

$ clang -g -fdebug-info-for-profiling test.cpp -o profiling.elf
$ llvm-symbolizer --output-style=GNU --obj=profiling.elf 0x401167 -p --no-inlines
main at /tmp/test.cpp:15 (discriminator 2)

$ llvm-symbolizer --output-style=JSON --obj=inlined.elf 0x4004be 0x400486 -p
[
  {
    "Address": "0x4004be",
    "ModuleName": "inlined.elf",
    "Symbol": [
      {
        "Column": 18,
        "Discriminator": 0,
        "FileName": "/tmp/test.cpp",
        "FunctionName": "baz()",
        "Line": 11,
        "StartAddress": "0x4004be",
        "StartFileName": "/tmp/test.cpp",
        "StartLine": 9
      },
      {
        "Column": 0,
        "Discriminator": 0,
        "FileName": "/tmp/test.cpp",
        "FunctionName": "main",
        "Line": 15,
        "StartAddress": "0x4004be",
        "StartFileName": "/tmp/test.cpp",
        "StartLine": 14
      }
    ]
  },
  {
    "Address": "0x400486",
    "ModuleName": "inlined.elf",
    "Symbol": [
      {
        "Column": 3,
        "Discriminator": 0,
        "FileName": "/tmp/test.cpp",
        "FunctionName": "foo()",
        "Line": 6,
        "StartAddress": "0x400486",
        "StartFileName": "/tmp/test.cpp",
        "StartLine": 5
      }
    ]
  }
]
--pretty-print, -p

打印人类可读的输出。如果指定了 --inlining,则封闭范围以 (inlined by) 为前缀。对于 JSON 输出,此选项将导致 JSON 被缩进并拆分到新行。否则,JSON 输出将以紧凑形式打印。

$ llvm-symbolizer --obj=inlined.elf 0x4004be --inlining --pretty-print
baz() at /tmp/test.cpp:11:18
 (inlined by) main at /tmp/test.cpp:15:0
--print-address, --addresses, -a

在源代码位置之前打印地址。默认为 false。

$ llvm-symbolizer --obj=inlined.elf --print-address 0x4004be
0x4004be
baz()
/tmp/test.cpp:11:18
main
/tmp/test.cpp:15:0

$ llvm-symbolizer --obj=inlined.elf 0x4004be --pretty-print --print-address
0x4004be: baz() at /tmp/test.cpp:11:18
 (inlined by) main at /tmp/test.cpp:15:0
--print-source-context-lines <N>

为每个符号化的地址打印 N 行源代码上下文。

$ llvm-symbolizer --obj=test.elf 0x400490 --print-source-context-lines=3
baz()
/tmp/test.cpp:11:0
10  :   volatile int k = 42;
11 >:   return foz() + k;
12  : }
--relativenames

打印相对于编译目录的文件路径,而不是绝对路径。如果编译器的命令行包含完整路径,则这将与默认值相同。

--verbose

打印详细的地址、行和列信息。

$ llvm-symbolizer --obj=inlined.elf --verbose 0x4004be
baz()
  Filename: /tmp/test.cpp
  Function start filename: /tmp/test.cpp
  Function start line: 9
  Function start address: 0x4004b6
  Line: 11
  Column: 18
main
  Filename: /tmp/test.cpp
  Function start filename: /tmp/test.cpp
  Function start line: 14
  Function start address: 0x4004b0
  Line: 15
  Column: 18
--version, -v

打印该工具的版本信息。

@<FILE>

从响应文件 <FILE> 读取命令行选项。

WINDOWS/PDB 特定选项

--dia

使用 Windows DIA SDK 进行符号化。如果找不到 DIA SDK,llvm-symbolizer 将回退到本机实现。

MACH-O 特定选项

--default-arch <arch>

如果二进制文件包含多个架构的对象文件(例如,它是 Mach-O 通用二进制文件),则符号化给定架构的对象文件。您还可以通过在输入中写入 binary_name:arch_name 来指定架构(请参阅下面的示例)。如果未通过任何一种方式指定架构,则地址将不会被符号化。默认为空字符串。

$ cat addr.txt
/tmp/mach_universal_binary:i386 0x1f84
/tmp/mach_universal_binary:x86_64 0x100000f24

$ llvm-symbolizer < addr.txt
_main
/tmp/source_i386.cc:8

_main
/tmp/source_x86_64.cc:8
--dsym-hint <path/to/file.dSYM>

如果二进制文件的调试信息不在默认位置,请在此选项提供的 .dSYM 路径中查找调试信息。此标志可以多次使用。

退出状态

llvm-symbolizer 返回 0。其他退出代码表示内部程序错误。

参见

llvm-addr2line(1)