跳转至内容

调试/获取追踪信息

来自 ArchWiki

本文档旨在通过提供跟踪和调试信息来帮助调试软件。这些信息随后可用于向(上游)软件开发者或包维护者报告 bug。

简介

通常,可执行文件会被剥离掉人类可读的上下文,以减小文件大小。在没有调试信息可用时获取的跟踪会大大降低其有用性。例如,来自 gdb 会话且调试信息不可用的跟踪可能看起来像这样:

[...]
Backtrace was generated from '/usr/bin/epiphany'

(no debugging symbols found)
Using host libthread_db library "/lib/libthread_db.so.1".
(no debugging symbols found)
[Thread debugging using libthread_db enabled]
[New Thread -1241265952 (LWP 12630)]
(no debugging symbols found)
0xb7f25410 in __kernel_vsyscall ()
#0  0xb7f25410 in __kernel_vsyscall ()
#1  0xb741b45b in ?? () from /lib/libpthread.so.0
[...]

?? 显示了调试信息缺失的位置,以及调用该函数的库或可执行文件的名称。同样,当出现 (no debugging symbols found) 时,您应该查找指明的 filenames。

为了获得对程序开发者有用的正确跟踪,请遵循接下来的章节。大多数官方 Arch 包都提供了单独的调试文件,可以通过 Debuginfod 下载(参见 #获取跟踪)。当可执行文件中最初没有添加增强的调试信息时,就必须 重新构建包 并启用调试符号。

请使用完整的堆栈跟踪来告知开发者您发现的 bug。开发者们会非常感激,这将有助于改进您喜欢的程序。

获取跟踪

实际的回溯(或堆栈跟踪)可以通过 GNU Debugger gdb 获取。根据是启动程序的新实例、附加到现有进程,还是检查之前的崩溃,它有几种用法。

启动程序的新实例

使用可在 $PATH 中找到的可执行程序(或完整路径)启动 gdb

$ gdb application

gdb 会自动尝试下载 官方仓库 中包的调试信息和符号。当 gdb 询问是否要在调试会话中启用 Debuginfod 时,请回答 y

This GDB supports auto-downloading debuginfo from the following URLs:
  <https://debuginfod.archlinux.org>
Enable debuginfod for this session? (y or [n]) y
Debuginfod has been enabled.
To make this setting permanent, add 'set debuginfod enabled on' to .gdbinit.
Downloading separate debug info for /usr/bin/application
Reading symbols from /home/user/.cache/debuginfod_client/fbaee841e2ed2c11ecbbda26f39eeec1da23d6c3/debuginfo...

然后,在 gdb 中,键入 run,后跟您希望程序启动时使用的任何参数。

(gdb) run arguments
提示 或者,您可以使用 gdb --args application arguments... 在启动 gdb 时传递参数,然后在 gdb 中只使用不带参数的 run。例如,要调试用 Python 编写的应用程序,请运行 gdb --args /usr/bin/python /path/to/application

现在执行任何必要的步骤来触发 bug。当应用程序崩溃时,gdb 会自动暂停它并提示输入命令。在冻结或类似问题的情况下,按 Ctrl+c 也会返回到命令提示符。

然后启用日志记录到文件。

(gdb) set logging enabled on
提示 默认文件名是 gdb.txt。可以在 gdb 中使用 set logging file trace.log 指定备用文件名。

最后,将回溯写入当前工作目录中指定的文件。

(gdb) thread apply all backtrace full

附加到现有进程

如果需要调试的程序已在运行,您需要先找到它的进程 ID。可以使用 pidof(1)pgrep(1) 等工具。例如:

$ pidof python3
907171 491909

如果输出没有给出唯一的 ID,您可以尝试更多过滤或查看 ps auxpstree --show-pids 的输出。

由于受限的 ptrace scope,作为普通用户附加默认不起作用。可以临时降低限制,使用 echo 0 > /proc/sys/kernel/yama/ptrace_scope,或者您可以以特权用户运行 gdb,例如使用 sudo

启动 gdb 并将其附加到进程。

$ gdb --pid=PID

gdb 会询问是否在此调试会话中启用 Debuginfod,您应回答 y

请注意,附加到进程会停止该进程,需要显式继续。这取代了 #启动程序的新实例 部分工作流程中的 run 命令。

(gdb) continue

现在执行任何必要的步骤来在附加的进程中触发 bug。然后,按照 #启动程序的新实例 中的方式启用日志记录并获取跟踪。

检查之前的崩溃

要调试已崩溃的应用程序,您需要针对其 core dump 调用 gdb。有关详细信息,请参阅 Core dump#Analyzing a core dump

如果已崩溃程序的调试信息不可用且未获取正确的堆栈跟踪,您可能需要 重新构建包 并再次重现崩溃。

手动获取调试信息

本文或本章节已过时。

原因:Debuginfod 时代,使用 pacman 安装单独的调试包对于官方包不再需要。(在 Talk:Debugging/Getting traces 中讨论)

首先要做的是获取需要 重新构建 或需要 安装调试包 的包的名称。

[...]
Backtrace was generated from '/usr/bin/epiphany'

(no debugging symbols found)
Using host libthread_db library "/lib/libthread_db.so.1".
(no debugging symbols found)
[...]

例如,对于上面跟踪中的摘录,可以使用 pacman 获取相关包的名称:

$ pacman -Qo /lib/libthread_db.so.1
/lib/libthread_db.so.1 is owned by glibc 2.5-8

该包在版本 2.5-8 中名为 glibc。对每个需要调试信息的包重复此步骤。

安装调试包

本文章或章节需要扩充。

原因: 恰当地解释不同的场景。您是安装调试包,还是让 debuginfod 获取它所需的内容?这还提到“一些镜像”但只列出一个。(在 Talk:Debugging/Getting traces#mirrors distributing debug packages 中讨论)
注意 Arch Linux 在 Arch Linux Archive 中不存档调试包。

一些镜像目前在可访问的仓库中分发调试包。这些是由 Arch Linux 管理的赞助镜像,并有权访问调试仓库。

要安装一个包,您可以直接从仓库安装。例如:

# pacman -U https://geo.mirror.pkgbuild.com/core-debug/os/x86_64/zstd-debug-1.5.2-2-x86_64.pkg.tar.zst
警告 来自一个 镜像的调试包与来自另一个镜像的常规包不兼容,如果两个镜像不同步,则会产生不匹配的构建。在这种情况下,请避免混合使用来自不同镜像的包(这会导致 部分升级),而是将所有仓库指向 debug 镜像。

本文档或章节是候选合并对象,目标是 Official repositories

备注: 官方仓库有专门的页面(在 Talk:Debugging/Getting traces 中讨论)

另一个选择是将仓库添加到您的 pacman 配置中。

/etc/pacman.conf
# Testing Repositories

[core-testing-debug]
Include = /etc/pacman.d/mirrorlist

[extra-testing-debug]
Include = /etc/pacman.d/mirrorlist

[multilib-testing-debug]
Include = /etc/pacman.d/mirrorlist

# Stable repositories

[core-debug]
Include = /etc/pacman.d/mirrorlist

[extra-debug]
Include = /etc/pacman.d/mirrorlist

[multilib-debug]
Include = /etc/pacman.d/mirrorlist

将带有调试包的镜像放在 mirrorlist 文件中的第一个位置。

/etc/pacman.d/mirrorlist
Server = https://geo.mirror.pkgbuild.com/$repo/os/$arch
...

重新构建包

如果调试信息未通过 debuginfod 提供(例如,当包来自 AUR 时),则可以从源代码重建。对于 官方仓库 中的包,请参阅 ABS,或对于 AUR 中的包,请参阅 AUR#Acquire build files

要设置所需的 #编译选项,您可以修改 makepkg 配置,如果您只使用 makepkg 进行调试目的。在其他情况下,您应该仅为要重新构建的每个包修改包的 PKGBUILD 文件。

编译选项

从 pacman 4.1 开始,makepkg.conf(5)DEBUG_CFLAGSDEBUG_CXXFLAGS 中具有调试编译标志。要使用它们,请启用 debug makepkg 选项,并禁用 strip

这些设置将强制使用调试符号进行编译,并禁用从可执行文件中剥离它们。

/etc/makepkg.conf
OPTIONS+=(debug !strip)

要将此设置应用于单个包,请修改 PKGBUILD

PKGBUILD
options=(debug !strip)

或者,您可以将调试信息放在一个单独的包中,通过启用 debugstrip,调试符号将被从主包中剥离,并与源代码文件一起(以帮助在调试器中单步执行)放入一个单独的 pkgbase-debug 包中。如果包包含非常大的二进制文件(例如,包含调试符号时超过 1GB),这会很有优势,因为它可能会导致冻结和其他奇怪的不必要行为。

注意 仅安装新编译的调试包是不够的,因为调试器会检查包含调试符号的文件是否与相关的库和可执行文件来自同一个构建。您必须同时安装重新编译的两个包。在 Arch 中,调试符号文件安装在 /usr/lib/debug/ 下,源代码文件安装在 /usr/src/debug 下。有关调试包的更多信息,请参阅 GDB 文档
glibc

某些包(如 glibc)会被剥离。检查 PKGBUILD 中的类似部分:

strip $STRIP_BINARIES usr/bin/{gencat,getconf,getent,iconv,iconvconfig} \
                      usr/bin/{ldconfig,locale,localedef,makedb} \
                      usr/bin/{pcprofiledump,pldd,rpcgen,sln,sprof} \
                      usr/lib/getconf/*

strip $STRIP_STATIC usr/lib/*.a

strip $STRIP_SHARED usr/lib/{libanl,libBrokenLocale,libcidn,libcrypt}-*.so \
                    usr/lib/libnss_{compat,db,dns,files,hesiod,nis,nisplus}-*.so \
                    usr/lib/{libdl,libm,libnsl,libresolv,librt,libutil}-*.so \
                    usr/lib/{libmemusage,libpcprofile,libSegFault}.so \
                    usr/lib/{audit,gconv}/*.so

并在适当的地方删除它们。

Clang

本文或本章节已过时。

原因: 作为参考的包已不再存在于仓库中,我们是否有更新的例子?(在 Talk:Debugging/Getting traces 中讨论)

使用 Clang 作为编译器的包将无法使用 debug 选项进行构建,因为调试标志 -fvar-tracking-assignments 未被处理(例如,之前的 js78 PKGBUILD)。

将以下内容添加到 build() 函数的顶部,以仅删除受影响包的标志:

build() {
  CFLAGS=${CFLAGS/-fvar-tracking-assignments}
  CXXFLAGS=${CXXFLAGS/-fvar-tracking-assignments}
[...]
LTO

使用 链接时优化(LTO) 会在编译和调试时使用更多内存[1][2]。根据应用程序的不同,特别是如果它是像 Firefox 或 Qt 这样的大型应用程序,可能会超出可用内存。如果发生这种情况,请在没有 LTO 的情况下构建应用程序。

官方仓库中的所有包通常都使用 LTO 构建。

构建和安装包

PKGBUILD 的目录中,使用 makepkg 从源代码构建包。这可能需要一些时间。

$ makepkg

然后安装构建好的包。

# pacman -U glibc-2.26-1-x86_64.pkg.tar.gz

参见