GDB之(3)加载指定动态库文件
Author:Once Day Date:2024年2月26日
漫漫长路,才刚刚开始…
全系列文章请查看专栏: Linux实践记录_Once-Day的博客-CSDN博客
推荐参考文档:
- gdb 查找动态库方法_info sharedlibrary-CSDN博客
- GDB: The GNU Project Debugger (sourceware.org)
- GDB Documentation (sourceware.org)
文章目录
- GDB之(3)加载指定动态库文件
- 1. 概述
- 1.1 跨架构调试库文件
- 1.2 指定库文件目录
- 2. 查看库文件加载情况
- 2.1 查看加载的共享库信息
- 2.2 查看进程虚拟地址映射
- 3. 显示库源码信息
- 3.1 查看对应函数地址源码信息
- 3.2 设置源代码目录路径
1. 概述
GDB(GNU Debugger)是GNU开源组织发布的一个强大的Unix/Linux程序调试工具。在软件开发的调试阶段,GDB提供了程序员在程序暂停执行的情况下,查看和修改程序内部的功能。这对于跟踪错误和异常行为,在复杂的系统中尤为重要。
当程序员面对的问题是需要调试的程序依赖特定版本的共享库文件时,GDB加载指定库文件的能力就变得尤为重要。通常,系统可能有多个版本的库文件,或者开发者可能希望测试程序在使用不同版本库文件时的行为。在这种情况下,GDB就需要能够加载和使用指定的库文件。
1.1 跨架构调试库文件
一个为 x86 架构编译的 GDB 通常不能直接用来调试 ARM 架构的二进制文件和它们的 core dump。原因是 GDB 需要理解目标程序的架构(例如指令集和寄存器),而 x86 版本的 GDB 通常只理解 x86 架构。
然而,可以使用一个“交叉编译”的 GDB 版本来实现这个目标。交叉编译的 GDB 是为主机(Host)架构(例如 x86)编译的,但是被配置为理解目标架构(例如 ARM)。可以从许多各种各样的源获取交叉编译的 GDB,包括 Linux 发行版的软件源,或者交叉编译工具链的提供者(例如 Linaro 对于 ARM)。
例如,如果正在使用 Ubuntu,可以通过以下命令安装交叉编译的 GDB:
ubuntu->~:$ sudo apt-get install gdb-multiarch
ubuntu->~:$ gdb-multiarch -v
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
gdb-multiarch
是一个特殊的 gdb 版本,它支持多种架构,包括 x86 和 ARM。安装了 gdb-multiarch
后,应该就可以使用它来调试 ARM 架构的二进制文件和它们的 core dump 了。
如果不能获取一个预编译的交叉编译的 GDB,也可以从源代码自己编译一个。这需要更多的工作,但是提供了更多的灵活性。可以在 GDB 的官方文档中找到更多关于如何配置和编译 GDB 的信息。
1.2 指定库文件目录
要在GDB中加载指定的库文件,可以使用set solib-search-path
命令或者set sysroot
命令。这两个命令都告诉GDB在哪里查找共享库。例如,如果想要GDB只在/my/special/libs
目录中搜索共享库,可以在GDB的命令行中输入:
(gdb) set solib-search-path /my/special/libs
如果库文件不在标准路径下,或者您正在交叉编译环境中工作,set sysroot
命令也非常有用,它指定了系统根目录的位置,GDB会在这个位置基础上追加标准库路径来搜索库文件:
(gdb) set sysroot /my/special/sysroot
此外,GDB还支持set environment
命令,可以用来设置环境变量,这在某些情况下也可以影响库文件的加载:
(gdb) set environment LD_LIBRARY_PATH /my/special/libs
在调试过程中,如果GDB提示某个库文件找不到,或者您想知道GDB实际加载了哪些库,可以使用info sharedlibrary
命令查看已加载的共享库信息。
有趣的是,使用GDB加载指定库文件的功能可以让程序员在不更改系统全局设置的情况下,测试程序在不同的环境下的行为。这就像给程序穿上了“隐形斗篷”,允许它在一个临时改变的环境中运行,而不影响系统中其它程序的运行。
在使用GDB加载特定库文件时,程序员需要注意确保库文件的版本与程序兼容,否则可能会遇到诸如符号不匹配等问题。
这个过程有点像是在一个复杂的机器中更换零件,更换的零件必须要与机器其他部分相匹配,否则机器可能无法正常工作。
2. 查看库文件加载情况
2.1 查看加载的共享库信息
GDB提供了一系列命令,其中info sharedlibrary
或简写的info shared
命令就专门用于查看库文件加载情况。
当启动GDB并且运行了程序之后,输入info sharedlibrary
命令,GDB会列出所有已加载的共享库文件,包括库文件的名字、内存中加载的地址以及是否已被加载等信息。这对于检查程序是否已经正确地加载了所有需要的动态链接库文件(Dynamic Link Libraries, DLLs),或者是否有些库文件未能正确加载是非常有用的。如果程序崩溃,可以查看是不是某个库文件没有加载或者加载了错误的版本。
除此之外,GDB还允许在特定的库文件加载或卸载时设置断点。通过break
命令结合dlclose
或dlopen
(这两者是Linux系统中用于动态加载或卸载库的函数)可以在库文件加载或卸载时暂停程序的执行,进而可以进一步调查库文件的加载细节。
使用GDB查看库文件加载情况的时候,可能会遇到一些特殊情况。例如,有时候因为权限问题或路径设置错误,库文件可能加载失败。这时,GDB的输出会帮助你快速定位问题。
有些程序员在调试过程中发现程序意外地加载了错误版本的库文件,而这通常是因为环境变量如LD_LIBRARY_PATH
的设置不当。这样的小插曲提醒了程序员,环境配置是开发工作中不可忽视的一部分。
请注意,如果程序尚未开始执行(例如,如果刚刚启动 gdb
并加载了程序,但是还没有使用 run
命令开始执行),那么 info sharedlibrary
命令可能不会显示任何信息,因为此时还没有任何库被加载。在这种情况下,需要先让你的程序开始执行,然后再使用 info sharedlibrary
命令。
如下所示:
(gdb) info sharedlibrary
From To Syms Read Shared Object Library
0x00007ffff7fc5090 0x00007ffff7fee335 Yes /lib64/ld-linux-x86-64.so.2
0x00007ffff7db1700 0x00007ffff7f43abd Yes /lib/x86_64-linux-gnu/libc.so.6
2.2 查看进程虚拟地址映射
在程序开发和调试过程中,理解进程的虚拟地址空间结构是非常重要的。GDB(GNU Debugger)提供了这样的能力,让开发者能够深入了解程序的内存布局。要查看一个进程的虚拟地址映射,可以使用GDB中的 info proc mappings
命令,这个命令展示了进程的虚拟内存映射,包括每个内存段的起始地址、结束地址、大小以及相关权限等信息。
在使用GDB时,首先需要启动GDB并附加到一个正在运行的进程或启动一个新的进程。如果想要调试一个正在运行的进程,可以使用 attach process_id
命令将GDB附加到该进程,其中 process_id
是进程的ID号。如果是启动一个新的进程,可以直接使用 file
命令加载程序,然后使用 run
命令运行它。
一旦进程在GDB的控制之下,就可以输入 info proc mappings
命令来查看进程的虚拟内存布局。这个命令的输出通常会显示多个内存区域,每个区域都有其特定的用途,如堆(heap)、栈(stack)、以及用于程序代码和数据的区域。每个区域都会显示相应的地址范围和权限,权限通常包括读(r)、写(w)和执行(x),如下所示:
ubuntu->cs-test:$ sudo gdb-multiarch --args ./my-pidof bash
.............
(gdb) Quit
(gdb) break main
Breakpoint 1 at 0x17e8
(gdb) r
Starting program: /home/ubuntu/tdata/cs-test/my-pidof bash
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".Breakpoint 1, 0x00005555555557e8 in main ()
(gdb) info proc mappings
process 3546635
Mapped address spaces:Start Addr End Addr Size Offset Perms objfile0x555555554000 0x555555555000 0x1000 0x0 r--p /home/ubuntu/tdata/cs-test/my-pidof0x555555555000 0x555555556000 0x1000 0x1000 r-xp /home/ubuntu/tdata/cs-test/my-pidof0x555555556000 0x555555557000 0x1000 0x2000 r--p /home/ubuntu/tdata/cs-test/my-pidof0x555555557000 0x555555558000 0x1000 0x2000 r--p /home/ubuntu/tdata/cs-test/my-pidof0x555555558000 0x555555559000 0x1000 0x3000 rw-p /home/ubuntu/tdata/cs-test/my-pidof0x7ffff7d86000 0x7ffff7d89000 0x3000 0x0 rw-p 0x7ffff7d89000 0x7ffff7db1000 0x28000 0x0 r--p /usr/lib/x86_64-linux-gnu/libc.so.60x7ffff7db1000 0x7ffff7f46000 0x195000 0x28000 r-xp /usr/lib/x86_64-linux-gnu/libc.so.60x7ffff7f46000 0x7ffff7f9e000 0x58000 0x1bd000 r--p /usr/lib/x86_64-linux-gnu/libc.so.60x7ffff7f9e000 0x7ffff7fa2000 0x4000 0x214000 r--p /usr/lib/x86_64-linux-gnu/libc.so.60x7ffff7fa2000 0x7ffff7fa4000 0x2000 0x218000 rw-p /usr/lib/x86_64-linux-gnu/libc.so.6
......
例如,堆区域通常用于动态内存分配,栈区域用于函数调用时的局部变量和返回地址,而代码区域则包括了程序的机器代码。通过这样的映射信息,开发者能够理解各种数据在内存中的分布,并有助于诊断内存溢出、访问违规等常见的内存问题。
值得注意的是,info proc mappings
命令在不同的操作系统和GDB版本中可能会有所不同。在某些系统中,可能需要使用 maintenance info sections
命令来获取类似的内存映射信息。
此外,有时候开发者可能会遇到权限问题,这种情况下可能需要以超级用户(root)权限运行GDB。
GDB提供的虚拟地址映射查看功能是一个强大的特性,它能够帮助程序员更好地了解和调试程序的内存使用情况。
3. 显示库源码信息
3.1 查看对应函数地址源码信息
在程序调试过程中,查看程序的源码是理解程序行为和定位问题的关键。GDB(GNU Debugger)提供了丰富的命令来显示程序的源码信息,使得调试工作更加直观和高效。要在GDB中显示程序的源码,可以使用一系列相关命令,下面是几个最常用的:
-
list
(或简写为l
)命令:这是查看源码最基本的GDB命令。当在GDB中键入list
命令时,默认情况下,GDB会显示当前断点位置的周围几行代码。也可以指定特定的行号或函数名来查看特定部分的代码,例如list 10
将显示从第10行开始的源代码,而list main
则会显示main
函数周围的代码。 -
list filename:linenumber
命令:如果想查看特定文件中特定行号的源码,可以使用这个命令。例如,list main.c:15
会显示main.c
文件中第15行的源代码。 -
show listsize
命令:这个命令可以用来显示或设置list
命令一次性显示的源码行数。可以通过set listsize number
命令来改变这个值,其中number
是想要一次显示的行数。 -
list *address
命令:有时可能只知道程序中某个特定地址,而想查看该地址对应的源码。使用这个命令,GDB会显示包含那个地址的源码行。
为了能够使用这些命令,程序需要包含调试信息(通常是在编译时使用 -g
选项)。没有调试信息的二进制文件会限制GDB显示源码的能力。
此外,GDB还提供了命令来控制源码的显示方式。例如,set listsize
命令可以调整每次显示的行数,而 set pagination on
或 set pagination off
命令则可以开启或关闭分页显示,这样当输出内容过多时,可以逐页查看而不是一次性滚动过去。
使用GDB查看源码的时候,可能会遇到源码不可用的情况,这通常是由于没有编译带有调试信息的程序或源码路径不正确所致。GDB会尝试在当前目录或编译时指定的目录中查找源文件。如果源文件不在这些位置,可以使用 dir
命令来添加额外的搜索路径。
下面是一个操作示例(程序编译时要带上-g
选项):
(gdb) break watchdog_get_usage
Breakpoint 1 at 0x31e8: file block-analyze.c, line 655.
(gdb) r
Starting program: /home/ubuntu/tdata/cs-test/block-analyze.out 2000
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".Breakpoint 1, watchdog_get_usage (interval_ms=2000) at block-analyze.c:655
warning: Source file is more recent than executable.
655 */
(gdb) list
650
651 /**
652 * 获取指定CPU核的使用率, 通过读取/proc/stat文件数据实现.
653 * 对于/proc/stat文件的具体解释, 可参考Linux kernel代码: fs/proc/stat.c.
654 * 不同版本的Linux内核, /proc/stat文件的格式可能会有所不同(有问题请分析源码).
655 */
656 bool watchdog_get_usage(uint32_t interval_ms)
657 {
658 int32_t total_cpu, count;
659 struct watchdog_cpu cpu_info[WATCHDOG_CPU_DATA_NUM];
(gdb) p watchdog_cpu
watchdog_cpu watchdog_cpu_deal_data watchdog_cpu_dump_data watchdog_cpu_get_usage
(gdb) p watchdog_cpu_dump_data
$1 = {void (struct watchdog_cpu *)} 0x5555555555fe <watchdog_cpu_dump_data>
(gdb) list *0x5555555555fe
0x5555555555fe is in watchdog_cpu_dump_data (block-analyze.c:113).
108 #define WATCHDOG_CPU_IDLE(cpu) ((cpu)->idle + (cpu)->iowait)
109 #define WATCHDOG_CPU_USAGE(cpu) (WATCHDOG_CPU_TOTAL(cpu) - WATCHDOG_CPU_IDLE(cpu))
110
111 /* 格式化打印watchdog cpu中所有字段的数据 */
112 static void watchdog_cpu_dump_data(struct watchdog_cpu *cpu_info)
113 {
114 WDG_info(
115 "CPU user: %lu, nice: %lu, system: %lu, idle: %lu, iowait: %lu, irq: %lu, softirq: %lu, "
116 "steal: %lu, guest: %lu, guest_nice: %lu.\n",
117 cpu_info->user, cpu_info->nice, cpu_info->system, cpu_info->idle, cpu_info->iowait,
3.2 设置源代码目录路径
在使用GDB(GNU Debugger)进行程序调试时,有时源代码不在GDB默认的搜索路径中。为了使GDB能够找到源代码,可以使用 directory
命令来设置源代码的目录路径。这个命令告诉GDB在哪些路径下查找源文件,当源代码和可执行文件不在同一个目录下时,非常有用,或者是源代码在编译后被移动到了一个新的位置。使用 directory
命令的基本语法如下:
directory 路径
这里的“路径”指的是源代码所在的目录,可以是相对路径或绝对路径。如果有多个目录需要添加,可以用冒号(在UNIX或Linux系统中)或分号(在Windows系统中)将它们分隔开。例如:
directory /path/to/source:/another/path/to/source
当添加新的搜索路径后,GDB会在这些目录中搜索源文件。如果想要查看当前的搜索路径列表,只需要输入 directory
命令而不加任何参数。GDB将显示出当前的搜索路径。如果想清除所有已设置的源代码路径,可以使用以下命令:
directory
然后GDB会询问是否要清除所有的路径设置。确认后,GDB将只在默认的目录中搜索源文件,通常是可执行文件所在的目录。
如果源代码路径中包含了许多不再需要的目录,gdb(GNU调试器)有时可能会因为找到错误版本的源代码而导致混淆。可以按照以下步骤来纠正这种情况:
- 使用不带参数的
directory
命令来将源代码路径重置为其默认值。 - 使用带有适当参数的
directory
命令来重新添加希望包含在源代码路径中的目录,可以一次性添加所有需要的目录。
下面是一个示例演示:
(gdb) directory ../Python-c/
Source directories searched: /home/ubuntu/tdata/cs-test/../Python-c:$cdir:$cwd
(gdb) directory ../c-source/
Source directories searched: /home/ubuntu/tdata/cs-test/../c-source:/home/ubuntu/tdata/cs-test/../Python-c:$cdir:$cwd
(gdb) directory
Reinitialize source path to empty? (y or n) y
Source directories searched: $cdir:$cwd
(gdb) show directories
Source directories searched: $cdir:$cwd
Once Day
也信美人终作土,不堪幽梦太匆匆......
如果这篇文章为您带来了帮助或启发,不妨点个赞👍和关注,再加上一个小小的收藏⭐!
(。◕‿◕。)感谢您的阅读与支持~~~