调试/获取追踪信息

来自 ArchWiki

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

简介

通常,可执行文件会去除人类可读的上下文以使其更小。在没有调试信息的情况下获取追踪信息会大大降低其有用性。例如,来自 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) 时,您应该查找声明的文件名。

要获得对程序开发人员有用的正确追踪信息,请按照以下章节操作。大多数官方 Arch 软件包都有单独的调试文件,可以使用 Debuginfod 下载(请参阅 #获取追踪信息)。如果最初没有将增强的调试信息添加到可执行文件中,则必须重新构建软件包并启用调试符号。

使用完整的堆栈追踪信息来告知开发人员您发现的错误。这将受到他们的赞赏,并将有助于改进您最喜欢的程序。

获取追踪信息

实际的回溯(或堆栈追踪)可以通过 GNU 调试器 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 时使用 gdb --args 应用程序参数... 传递参数,然后在 gdb 中仅使用不带参数的 run。例如,要调试用 Python 编写的应用程序,请运行 gdb --args /usr/bin/python /path/to/application

现在执行任何必要的操作以引发错误。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 作用域受到限制。可以使用 echo 0 > /proc/sys/kernel/yama/ptrace_scope 临时降低限制,或者您可以以特权用户身份运行 gdb,例如使用 sudo

启动 gdb 并将其附加到进程

$ gdb --pid=PID

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

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

(gdb) continue

现在执行任何必要的操作以在附加的进程中引发错误。然后继续启用日志记录并获取追踪信息,与 #启动程序的新实例 中相同。

检查之前的崩溃

要调试已经崩溃的应用程序,您将需要在其核心转储上调用 gdb。有关详细信息,请参阅 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 中讨论)
注意: 调试包未由 Arch Linux 存档在 Arch Linux 存档上。

一些镜像目前在可访问的软件仓库中分发调试包。这些是由 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 镜像。

本文或章节是与 官方软件仓库 合并的候选对象。

注释: 官方软件仓库有专门的页面(在 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

将带有调试包的镜像放在镜像列表文件的第一个

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

重新构建软件包

如果调试信息未通过 debuginfod 公开(例如,当软件包源自 AUR 时),则可以从源代码重新构建它。有关 官方软件仓库中的软件包,请参阅 ABS,对于 AUR 中的软件包,请参阅 AUR#获取构建文件

要设置所需的#编译选项,如果您仅将 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 软件包中,以帮助单步调试调试器。如果软件包包含非常大的二进制文件(例如,包含调试符号的文件超过 GB),这会很有利,因为它可能会导致冻结和其他奇怪的不良行为发生。

注意: 仅仅安装新编译的调试包是不够的,因为调试器将检查包含调试符号的文件是否与关联的库和可执行文件来自相同的构建。您必须安装这两个重新编译的软件包。在 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 中讨论)

由于调试标志 -fvar-tracking-assignments' 未处理,使用 Clang 作为编译器的软件包将无法使用 debug 选项构建(例如,之前的 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

参见