GDB 99 - Debugging glibc with Source Code
# 前置知识
;
# 前言
本文假定读者使用的 glibc 是 libc-2.31
,这是我的系统所用的 glibc 版本。
本文中的方法保证可以在 Ubuntu 18.04 和 Ubuntu 20.04 上成功运行,在其它 Linux 发行版或低版本的 Ubuntu 上可能会遇到其它问题。
# 获取调试信息
首先我们需要获取 glibc 的调试信息。这很简单,只需要执行如下命令即可:
sudo apt install libc6-dbg
libc6-dbg
包含一组带有调试信息的 glibc 共享库文件,包括 ld-X.X.so
, libc-X.X.so
, libm-X.X.so
等等,其中 X.X
为 glibc 的版本号。
安装后,这些带有调试信息的共享库文件将被放置在 /usr/lib/debug/lib/x86_64-linux-gnu
目录下。我们可以使用 readelf
命令来验证这些 .so
文件是否确实带有调试信息。例如:
readelf -S /usr/lib/debug/lib/x86_64-linux-gnu/libc-2.31.so | grep debug
如果一切正常,我们可以看到一些以 .debug
开头的 section ,例如:
[68] .debug_aranges PROGBITS 0000000000000000 00001ee0
[69] .debug_info PROGBITS 0000000000000000 00015910
[70] .debug_abbrev PROGBITS 0000000000000000 00951dd6
[71] .debug_line PROGBITS 0000000000000000 00a5d1bc
[72] .debug_str PROGBITS 0000000000000000 00c6f061
[73] .debug_loc PROGBITS 0000000000000000 00c9bf98
[74] .debug_ranges PROGBITS 0000000000000000 00fcbc6e
# 获取源代码文件
只有调试信息是不够的,我们还必须要有 glibc 的源代码信息。然而许多 Linux 系统都不会预装 glibc 的源代码文件,这也是初次调试 glibc 源代码的人最容易被误导的地方:网上的许多技术博客(包括一些 stackoverflow 上的回答)都没有提及这件事,只是让提问者去安装 libc6-dbg
,然而仅仅这样是不够的。
在安装 libc6-dbg
后,如果你仍然无法在 gdb 上看到 glibc 的源码,说明你的系统没有预装(这是多数的情况)。有三种方法可以获取 glibc 的源代码文件。
方法 1 :通过 apt install
获取
执行如下命令:
sudo apt install glibc-source
安装后,在 /usr/src
目录下会多出一个 glibc/
文件夹。我们并不需要 glibc/debian/
中的内容,只需要 glibc/glibc-2.31.tar.xz
。
假设我们希望将源代码信息放置在 /usr/src/glibc-2.31/
目录下,则可以执行如下命令:
cd /usr/src
sudo mv glibc/glibc-2.31.tar.xz .
sudo tar -xvf glibc-2.31.tar.xz
# sudo rm -rf glibc # if you dont need other data
方法 2 :通过 apt source
获取
执行如下命令:
cd /usr/src
sudo apt source libc6-dev
安装后,源代码信息会被放置在 /usr/src/glibc-2.31/
目录下。读者也可以选择其它喜欢的位置。
如果报告如下错误,说明你没有为 Ubuntu Software 打开 deb-src 的源:
E: You must put some 'deb-src' URIs in your sources.list
可以执行如下命令,然后在弹出框中(或是直接从 GUI 界面搜索打开 Ubuntu 的 Software & Updates 界面,然后)进入 Ubuntu Software 菜单,将 Source code 一项勾选上。
software-properties-gtk
这会在 /etc/apt/sources.list
文件中添加一些 deb-src
的源,之后你应当能够成功执行 apt source
命令。
方法 3 :通过 wget
获取
执行如下命令:
cd /usr/src
sudo wget -c http://ftp.gnu.org/gnu/glibc/glibc-2.31.tar.xz
sudo tar -xvf glibc-2.31.tar.xz
安装后,源代码信息会被放置在 /usr/src/glibc-2.31/
目录下。读者也可以选择其它喜欢的位置。
# 配置源码搜索路径
此时我们已经有了 glibc 的调试信息和源码信息,事实上已经可以调试 glibc 的源代码了。
# 原始方法
例如我们希望调试 glibc 的 malloc
函数,可以先在 malloc
符号处设置断点,等到程序执行到该处时,将 gdb 的源码搜索路径切换到之前安装好的 glibc 源码文件夹:
b malloc
r
dir /usr/src/glibc-2.31/malloc
也可以直接在执行时指定:
gdb -d /usr/src/glibc-2.31/malloc
然而这种原始的方法有诸多不便:如果我们希望在用户程序和 glibc 之间反复切换(例如调用了多次 malloc
),那么我们就不得不反复切换 gdb 的源码搜索路径,甚至是调试 glibc 中不同的文件也需要切换(例如 raise
函数位于 glibc-2.31/signal
目录下)。
这显然是不合理的:我们在调试自己的工程程序时,也不存在切换个文件夹 gdb 就找不到源代码这种事情。随后我们会介绍该问题的成因以及解决方案。
# 使用路径替换
我们可以随意写一个会调用库函数的简单程序(例如 printf
),然后在 printf
符号处设置断点,等到程序执行到该处时,查看 gdb 当前的源码搜索路径:
b printf
r
info source
以我系统上的输出结果为例:
Reading symbols from ./a.out...
(gdb) b printf
Breakpoint 1 at 0x1050
(gdb) r
Starting program: /home/jm233333/Projects/a.out
Breakpoint 1, __printf (format=0x555555556004 "hello") at printf.c:28
28 printf.c: No such file or directory.
(gdb) info source
Current source file is printf.c
Compilation directory is /build/glibc-eX1tMB/glibc-2.31/stdio-common
Source language is c.
我们看到 gdb 对 glibc printf
的源码搜索路径是 /build/glibc-eX1tMB/glibc-2.31/
,该路径在不同的系统中可能不同。事实上,这正是系统中的 glibc 库的编译位置。有两种方法可以修复此问题。
简单粗暴的解决方案是将 glibc 的源码移动到 /build/glibc-eX1tMB/glibc-2.31
,但这显然不是一个漂亮的解决方案。
我们可以利用 gdb 的 substitute-path
功能,这将批量替换所有匹配的源码搜索路径,详见 gdb 官方文档:
set substitute-path /build/glibc-eX1tMB/glibc-2.31 /usr/src/glibc-2.31
可以将该命令添加到 .gdbinit
文件中,这样我们就能方便快捷地调试 glibc 的源码了。
# 其它
在配置完成后,如果用在调试 glibc 时有报告如下警告,则说明你的系统实际使用的 glibc 和你准备的调试信息、源码信息之间存在不兼容(其中二者的版本不同):
warning: Source file is more recent than executable
这种情况通常出现在低版本的 Linux 系统中,这意味着你需要重新配置版本配套的 glibc 。
# 参考资料
Stack Overflow - GDB complaining about missing raise.c (opens new window)
Ethanol's Blog - Basic Debug Glibc Source Code (opens new window)
- 01
- Reading Papers - Kernel Concurrency06-01
- 02
- Linux Kernel - Source Code Overview05-01
- 03
- Linux Kernel - Per-CPU Storage05-01