代码调试篇(4):如何调试 glibc 源码——准备篇

Author: stormQ

Created: Monday, 08. February 2021 12:34PM

Last Modified: Wednesday, 10. February 2021 08:09AM



摘要

本文描述了调试 glibc 源码需要做的准备工作,内容包括:如何安装带调试信息的 glibc 共享库、如何获取与系统配套的 glibc 源码、如何在 gdb 中使用 glibc 源码,并通过一个简单的例子展示了这一过程。


调试 glibc 源码前需要做的准备

step 1: 安装 libc-dbg

$ sudo apt-get install libc-dbg

注:libc-dbg中包含了带调试信息的ld-X.X.solibc-X.X.solibm-X.X.so等 glibc 共享库。

在笔者的机器上,安装libc-dbg后,这些带调试信息的共享库位于/usr/lib/debug/lib/x86_64-linux-gnu目录下,如下:

$ ls /usr/lib/debug/lib/x86_64-linux-gnu
ld-2.31.so               libc-2.31.so   libmemusage.so   libnss_compat-2.31.so  libnss_hesiod-2.31.so   libpcprofile.so    libSegFault.so
libanl-2.31.so           libdl-2.31.so  libmvec-2.31.so  libnss_dns-2.31.so     libnss_nis-2.31.so      libresolv-2.31.so  libthread_db-1.0.so
libBrokenLocale-2.31.so  libm-2.31.so   libnsl-2.31.so   libnss_files-2.31.so   libnss_nisplus-2.31.so  librt-2.31.so      libutil-2.31.so

我们可以通过readelf命令来验证这些共享库确实是带有调试信息的。比如:

$ readelf -S /usr/lib/debug/lib/x86_64-linux-gnu/libc-2.31.so | grep debug
  [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

如果出现上面输出结果中以.debug开头的 sections,那么说明是带调试信息的。

step 2: 安装配套的 glibc 源码

注意: 如果系统自带的 glibc 库,比如lib.c.so的版本是 2.31。那么,直接从 GNU 官网上下载的 glibc 2.31 版本的源码可能并不匹配。这里用“可能”一词意味着,如果 ubuntu 没有修改你要调试的源码,那么是匹配的;否则,就是不匹配的。

1) 安装配套的 glibc 源码

方式 1(推荐):

$ sudo apt-get install glibc-source

方式 2:

从 ubuntu packages 网站上手动下载系统对应的 glibc 源码,比如:glibc-source_2.31-0ubuntu9_all.deb 的下载页面。下载完成后,执行如下命令进行安装(示例):

$ sudo dpkg -i glibc-source_2.31-0ubuntu9_all.deb

2) 解压配套的 glibc 源码

本文是以“方式 1”安装的。在笔者的机器上,安装后的 glibc 源码位于/usr/src/glibc/目录,如下:

$ ls /usr/src/glibc/
debian  glibc-2.31.tar.xz

安装完成后,需要解压才能使用。假设要将其解压到/home目录下,那么执行如下命令:

$ sudo tar xvf /usr/src/glibc/glibc-2.31.tar.xz -C /home

注:tar命令中的-C <path>选项表示解压到<path>目录下。

3) 如何判断 glibc 源码是否配套

如果用 gdb 调试 glibc 源码时,出现warning: Source file is more recent than executable.。那么,通常意味着 glibc 源码是不配套的。此时,我们必须在安装配套的 glibc 源码后,才可以进行源码调试。


一个简单的调试 glibc 源码的程序

step 1: 准备示例程序,并生成可执行目标文件

示例程序,hello.cpp:

#include <stdio.h>

int main()
{
  printf("hello, world. I’m %d\n"123);
  return 0;
}

注意: 这里不用使用语句printf("hello, world.\n");,因为该语句实际调用的函数是puts,而不是printf

生成可执行目标文件——hello:

$ g++ -o hello hello.cpp -g

step 2: 调试 glibc 源码——以调试 printf 函数为例

1) 用 gdb 启动可执行目标文件——hello

$ gdb -q ./hello
Reading symbols from ./hello...

2) 带调试信息共享库所在目录(optional)

查看 gdb 是否会自动搜索并加载相应的调试信息:

(gdb) show debug-file-directory 
The directory where separate debug symbols are searched for is "/usr/lib/debug".

从上面的输出结果可以看出,gdb 默认情况下会搜索/usr/lib/debug目录,加载 glibc 共享库的符号表等调试信息。而该目录正是我们上文中带调试信息的 glibc 共享库的安装目录。因此,我们不需要自己指定该目录了。

如果需要手动指定带调试信息共享库所在目录,比如/usr/lib/debug/,命令如下:

(gdb) set debug-file-directory /usr/lib/debug/

3) 指定 glibc 源码所在的目录

(gdb) directory /home/glibc-2.31/stdio-common/
Source directories searched: /home/glibc-2.31/stdio-common:$cdir:$cwd

注意: 由于printf函数的底层实现函数位于printf.c源文件中,而printf.c位于目录glibc-2.31/stdio-common目录下。所以,要调试printf函数的实现,需要指定该目录。

4) 运行可执行目标文件

(gdb) start
Temporary breakpoint 1 at 0x1149file hello.cpp, line 4.
Starting program: /home/xxq/Desktop/hello 

Temporary breakpoint 1main () at hello.cpp:4
4    {

5) 设置断点,并继续执行

printf底层实现函数的名称为__printf。所以,我们在__printf函数开始处设置断点。断点设置后,继续执行,就进入了该函数。

(gdb) b __printf
Breakpoint 2 at 0x7ffff7e24e10: file printf.c, line 28.
(gdb) c
Continuing.

Breakpoint 2, __printf (format=0x555555556004 "hello, world. I’m %d\n") at printf.c:28
28    {

6) 查看__printf函数的源码

(gdb) l
23    
24    /* Write formatted output to stdout from the format string FORMAT.  */
25    /* VARARGS1 */
26    int
27    __printf (const char *format, ...)
28    {
29      va_list arg;
30      int done;
31    
32      va_start (argformat);

此时,我们成功地在调试 glibc 源码了。


References


下一篇:代码调试篇(5):如何调试动态链接器

上一篇:代码调试篇(3):利用 gdb 自动化测试复杂条件逻辑——以 behaviac 行为树为例

首页