调试/获取追踪信息
本文档旨在通过提供跟踪和调试信息来帮助调试软件。这些信息随后可用于向(上游)软件开发者或软件包维护者提交 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) 时,您应该查找所列出的文件名。
为了获得对程序开发者有用的正确跟踪,请按照后续章节进行操作。大多数官方 Arch 包都提供了单独的调试文件,可以使用 Debuginfod 下载(参见 #获取跟踪)。当增强的调试信息最初未添加到可执行文件中时,则必须通过 重新构建包 来启用调试符号。
使用完整的堆栈跟踪,以便在发现 Bug 时提前告知开发者。这将深受他们的赞赏,并有助于改进您喜欢的程序。
获取跟踪
实际的后向跟踪(或堆栈跟踪)可以通过 gdb,即 GNU 调试器来获得。它可以根据是启动程序的新实例、附加到现有进程还是检查先前的崩溃,以多种方式使用。
启动程序的新实例
使用可在 $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 aux 或 pstree --show-pids 的输出。
由于受限的 ptrace scope,普通用户附加默认不起作用。可以通过 echo 0 > /proc/sys/kernel/yama/ptrace_scope 临时降低限制,或者可以使用 sudo 以特权用户身份运行 gdb。
启动 gdb 并将其附加到进程。
$ gdb --pid=PID
gdb 会询问是否在此调试会话中启用 Debuginfod,您应该回答 y。
请注意,附加到进程会使其暂停,并且需要显式地继续执行。这取代了 #启动程序的新实例 部分工作流程中的 run 命令。
(gdb) continue
现在执行任何必要的步骤来在附加的进程中触发 Bug。然后,像在 #启动程序的新实例 中一样,继续启用日志记录并获取跟踪。
检查先前的崩溃
要调试已崩溃的应用程序,您需要对它的 core dump 调用 gdb。有关详细信息,请参阅 Core dump#Analyzing a core dump。
如果崩溃程序的调试信息不可用且未获得正确的后向跟踪,您可能需要 重新构建包 并再次重现崩溃。
手动获取调试信息
首先要做的是获取需要 重新构建 或 安装调试包 的软件包的名称。
[...] 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。对每个需要调试信息的软件包重复此步骤。
安装调试包
一些镜像当前在可访问的仓库中分发调试包。这些是由 Arch Linux 控制的赞助镜像,并有权访问调试仓库。
- https://geo.mirror.pkgbuild.com (GeoDNS 镜像)
要安装一个包,您可以直接从仓库安装。例如:
# pacman -U https://geo.mirror.pkgbuild.com/core-debug/os/x86_64/zstd-debug-1.5.2-2-x86_64.pkg.tar.zst
debug 镜像。另一种选择是将仓库添加到您的 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_CFLAGS 和 DEBUG_CXXFLAGS 中具有调试编译标志。要使用它们,请启用 debug makepkg 选项,并禁用 strip。
这些设置将强制使用调试符号进行编译,并禁用从可执行文件中剥离它们。
/etc/makepkg.conf
OPTIONS+=(debug !strip)
要将此设置应用于单个包,请修改 PKGBUILD。
PKGBUILD
options=(debug !strip)
或者,您可以将调试信息放入一个单独的包中,方法是同时启用 debug 和 strip。这样,调试符号将从主包中剥离,并与源代码文件一起(以帮助在调试器中进行单步调试)放入一个单独的 pkgbase-debug 包中。如果包包含非常大的二进制文件(例如,包含调试符号后超过 1GB),这会很有优势,因为这可能会导致冻结和其他奇怪、不受欢迎的行为。
/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
使用 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