gdb远程调试详解
GDB 远程调试,就是让“调试器”(GDB)和“被调试程序”运行在不同的机器上。它的核心是使用一个轻量级的gdbserver在程序所在的“目标机”上运行,并在另一台“主机”上通过 GDB 客户端发送调试命令,实现远程控制。
GDB远程调试主要有两种模式,它们的核心区别如下:
| 调试模式 | 核心特点 | 适用场景 |
|---|---|---|
target remote(一次性连接) | gdbserver启动程序,GDB连接,程序结束后连接自动断开。 | 非常适合调试从最开始就出现问题的程序,或者当你需要完整地控制程序生命周期时。 |
target extended-remote(扩展远程) | gdbserver启动为多进程模式,GDB连接后可调试多个程序或在程序结束后重新运行。 | 适用于需要进行多次调试、或需要动态决定要启动/附加到哪个进程的复杂场景,是更灵活的模式。 |
📖 核心原理
调试过程中,主机上的 GDB 客户端负责解析符号、管理断点、提供用户界面,本身不运行被调试的程序。目标机上的gdbserver则通过GDB 远程串行协议(RSP),接收和执行来自主机的命令(如设置断点、读/写内存),并将程序执行状态和数据返回给主机,实现远程协作。
🛠️ 环境搭建与核心配置
一个基本的调试流程主要分三步,涉及主机和目标机两端的配合:
1. 准备工作(目标机与主机)
目标机:必须安装
gdbserver。Debian/Ubuntu:
sudo apt-get install gdbserverCentOS/RHEL:
sudo yum install gdbserver
主机:需安装对应目标平台架构的 GDB。
同架构:
sudo apt-get install gdb或sudo yum install gdb交叉架构:需安装
gdb-multiarch-或架构特定的交叉 GDB。
2. 目标机启动gdbserver
有三种常用模式:
调试新程序:启动并调试指定程序。
gdbserver [127.0.0.1]:<端口> <程序名> [程序参数]。例如:gdbserver :2345 ./hello_world。附加到运行中的进程:调试已运行的进程,不重启。
gdbserver --attach [127.0.0.1]:<端口> <PID>。例如:gdbserver --attach :2345 1234。多进程模式:允许主机后续选择启动或附加进程。
gdbserver --multi [127.0.0.1]:<端口>。例如:gdbserver --multi :2345。
3. 主机端连接 GDB
在主机上执行:
# 1. 启动主机端 GDB,加载带调试符号的程序副本gdb ./hello_world# 2. 在 GDB 命令界面,连接到远程目标 (端口需与上一步设置的端口一致)(gdb) target remote <目标机IP>:<端口># 模式匹配说明:# - 若目标机使用普通模式启动,使用 target remote# - 若目标机使用 --multi 模式启动,则应使用 target extended-remote连接成功后,即可使用各种 GDB 命令进行调试。
进阶配置:Secure Shell (SSH) 端口转发
当目标机位于内网,或希望加密调试通道时,可使用 SSH 端口转发:
在主机上建立 SSH 隧道,将本地端口转发到目标机的调试端口:
ssh -L 本地端口:localhost:目标机端口 用户@目标机IP例如,将本地的
2345端口转发到目标机(192.168.1.100)的2345端口:ssh -L 2345:localhost:2345 user@192.168.1.100保持 SSH 会话,然后在主机 GDB中连接到本地的转发端口:
(gdb) target remote localhost:2345
SSH 端口转发不仅解决了网络访问问题,也加密了数据传输,是推荐的调试方式。
🛠️ 进阶功能:多线程与多进程调试
对于多线程和多进程程序,推荐使用extended-remote模式,它提供了更强大的支持。
多进程调试
首先,在目标机启动gdbserver:gdbserver --multi :1234然后,在宿主机GDB中连接:
(gdb) target extended-remote <target_ip>:1234连接成功后,你可以使用
set remote exec-file或file命令指定要调试的程序,并使用run命令启动它。extended-remote模式还支持调试由该程序创建的子进程。多线程调试
在GDB中调试多线程程序时,可以使用以下常用命令:info threads:查看所有线程。thread <id>:切换到指定线程。break <location> thread <id>:为特定线程设置断点。set scheduler-locking on:当断点命中时,只让当前线程运行,其他线程暂停,这对于精准调试非常有用。
🧪 调试场景:VSCode 示例
以玲珑包demo程序在在容器中的调试为例:
首先给 vscode 安装 C/C++ 插件,因为 vscode 是运行在宿主机上的,所以也需要通过 gdbserver 来为如意玲珑容器中的应用提供调试。先使用ll-builder run --exec /bin/bash进入容器,然后执行gdbserver 127.0.0.1:12345 /opt/apps/org.deepin.demo/bin/demo,gdbserver 会使用 tcp 协议监听 12345 端口并等待 gdb 连接。然后在宿主机上, vscode 配置好 launch.json 文件即可。具体配置如下:
{"version":"0.2.0","configurations": [ {"name":"(gdb) linglong","type":"cppdbg","request":"launch","program":"${workspaceFolder}/linglong/output/binary/files/bin/demo","args": [],"stopAtEntry":true,"cwd":"${workspaceFolder}","MIMode":"gdb","miDebuggerServerAddress":"127.0.0.1:12345","setupCommands": [ {"text":"set substitute-path /project ${workspaceFolder}"}, {"text":"set debug-file-directory ${workspaceFolder}/linglong/output/develop/files/lib/debug"} ] } ] }配置好后,点击“运行” -> “启动调试”,VSCode 就会自动连接远程gdbserver-。
部分配置需要按照项目实际情况更改:
"program": "${workspaceFolder}/linglong/output/binary/files/bin/demo",
这是传递给 gdb 的二进制文件,
demo需要更改为项目实际的二进制文件名"stopAtEntry": true
这是要求 gdb 在 main 函数自动停止,如果不需要可以设置为 false
"miDebuggerServerAddress": "127.0.0.1:12345"
这是 gdb 连接的远程地址,在启动 gdbserver 时如果端口不是默认的 12345,需要修改为实际端口。
"text": "set substitute-path /project ${workspaceFolder}"
这是设置源码路径的替换,
${workspaceFolder}vscode 会自动替换成当前工作目录,如果需要更改为实际路径可以修改。"text": "set debug-file-directory ${workspaceFolder}/linglong/output/develop/files/lib/debug"
这里是设置调试文件的目录,如果调试符号没有保存到
develop模块,需要修改为实际位置。
🧪 调试场景:qtcreator 示例
Qt Create 也集成了对 gdb 的支持,启动 Qt Create 后打开菜单栏中的调试->开始调试->连接到正在运行的调试服务器,在弹出的对话框中填入(以玲珑包demo程序在容器中的调试为例):
服务器端口:`12345` 本地执行档案:`/tmp/org.deepin.demo/linglong/output/binary/files/bin/demo` 工作目录:`/tmp/org.deepin.demo` Init Commands: `setsubstitute-path /project /tmp/org.deepin.demo` 调试信息:`/tmp/org.deepin.demo/linglong/output/develop/files/lib/debug`大致配置如下图所示:
配置完成后,即可正常使用QtCreator来进行调试了。
扩展:调试内核、固件与模拟器
除了调试应用,GDB 远程调试也广泛用于系统级开发:
调试内核与 Bootloader:QEMU 这类模拟器内置了 GDB 服务。启动时加上
-s和-S参数,主机 GDB 连接即可调试内核或裸机程序-。调试 MCU 固件:通过 OpenOCD 等调试代理连接调试器和目标芯片。OpenOCD 作为“翻译官”,将 GDB 命令转为硬件调试信号-。
调试 Android 应用:
gdbserver也可用于调试 Android 原生代码(NDK)。需注意 Android 动态链接器可能导致的符号加载问题-。
🔧 核心调试命令(速查表)
| 命令 | 说明 | 示例/备注 |
|---|---|---|
target remote <host>:<port> | 连接命令 | 连接远程目标。与--multi模式连接用target extended-remote。 |
file <executable> | 加载符号 | 可在连接前或连接后加载主机上带符号的程序副本。 |
break main/b main | 设置断点 | break main.c:15表示在第15行设置断点。 |
continue/c | 继续执行 | 暂停后恢复运行。 |
step/s | 单步执行 | 遇到函数会步入内部。 |
next/n | 单步跳过 | 遇到函数视为一步,不进入内部。 |
print var/p var | 打印变量 | print var = 100可修改变量值。 |
info threads | 查看线程 | 查看进程中所有线程的信息-。 |
thread <id> | 切换线程 | 切换到指定ID的线程进行调试-。 |
quit/q | 退出调试 | 退出 GDB。 |
💡 高级调试技巧
set sysroot <路径>:设置目标系统的根目录,用于加载共享库和可执行文件。如果调试二进制文件和库在主机和目标机的路径不同,可以通过sysroot命令组合路径-。set solib-search-path <路径1>:<路径2>:当目标环境缺少调试符号时使用。指定在主机上查找库文件的路径,确保 GDB 能加载正确的符号。info sharedlibrary:查看已加载的共享库信息,并确认它们的调试符号是否已从主机正确加载。
❓ 常见问题与排查技巧
连接失败:检查网络连通性(
ping),确认gdbserver的端口在目标机上可被外部访问(如netstat -tuln | grep <端口>查看监听状态),并检查防火墙设置。断点失效:确认连接成功,并检查设置的断点位于已加载的代码中。如果在共享库中断点失效,参考前面的路径映射技巧-。
“No such file or directory”:通常是主机端 GDB 找不到源文件或带调试符号的程序副本。使用
directory命令添加源文件搜索路径。调试增强:开启远程协议日志有助于排查通信问题:
(gdb)setdebug remote 1 (gdb) target remote ... (gdb)setdebug remote 0# 调试完成后关闭日志日志会打印详细的收发数据包,对调试非常有用。
断点或单步行为异常,如
step直接变成全速运行
检查程序编译选项,务必使用-O0 -g重新编译。GDB不响应
run或continue命令
检查连接是否已建立,并确认GDB连接的是gdbserver而非本地程序,因为在远程模式中应使用continue而非run。调试不同架构时符号无法加载
确保使用了支持多架构的gdb-multiarch客户端,并正确加载了为目标架构编译的带调试信息的程序。目标程序缺少符号信息
检查目标程序是否在编译时加入了-g选项。
gdb远程调试时,配置set substitute-path用法详解
1.set substitute-path命令概述
set substitute-path是 GDB 中用于路径映射/替换的命令,主要用于解决源代码路径在不同环境中不一致的问题。例如,当你在机器 A 上编译程序,然后在机器 B 上使用 GDB 调试,或者当源代码被移动到新的位置时,该命令可以告诉 GDB 如何将编译时记录的旧路径映射到新路径,从而正确加载源代码。
2. 命令语法与子命令
set substitute-path命令系列包含以下三个子命令:
| 命令 | 说明 |
|---|---|
set substitute-path from to | 设置一条路径替换规则,将路径中的from字符串替换为to,规则添加在列表末尾 |
unset substitute-path [path] | 删除指定path的替换规则,若不指定则删除所有规则 |
show substitute-path [path] | 显示当前所有替换规则,若指定path则显示会对其生效的规则 |
3. 工作原理与匹配规则
GDB 执行路径替换时遵循以下核心规则:
前缀匹配,字符串替换:GDB 在源文件名(目录部分)的开头进行简单的字符串匹配和替换,而非正则匹配
必须匹配到目录分隔符:
from部分的末尾必须匹配一个目录分隔符(如/或\\),才会应用替换。例如,规则/usr/source→/mnt/cross会对/usr/source/foo.c生效,但对/usr/sourceware/foo.c无效仅作用于绝对路径前缀:替换仅作用于路径开头部分,
/root/usr/source/baz.c不会被该规则匹配总是优先尝试:GDB 会始终先尝试使用替换后的路径查找源代码
3.1 使用dir还是set substitute-path?
这里需要区分两个命令:
| 场景 | 命令 | 说明 |
|---|---|---|
源代码路径是相对路径(如./src/main.c) | directory/dir | GDB 会用dir指定的目录与相对路径拼接 |
源代码路径是绝对路径(如/home/user/project/src/main.c) | set substitute-path | 将路径前缀替换为本地实际路径 |
💡小技巧:如果不确定调试信息中存储的是绝对路径还是相对路径,可以使用
readelf -p .debug_str 你的可执行文件来查看。
4. 实战场景与示例
场景一:源代码目录整体迁移
编译时源代码位于/home/user/old_project/src,后来移动到/home/user/new_project/src:
(gdb)setsubstitute-path /home/user/old_project/src /home/user/new_project/src (gdb) show substitute-path List of allsourcepath substitution rules: `/home/user/old_project/src' -> `/home/user/new_project/src'.验证是否生效:
(gdb) info sources场景二:跨机器远程调试 / Docker 容器内编译
在 Docker 容器内编译的程序,其调试信息中可能包含容器内的绝对路径(如/data/riotbuild/riotbase)。在宿主机上进行 GDB 远程调试时,需要将这些路径映射到宿主机上的实际位置:
(gdb)setsubstitute-path /data/riotbuild/riotbase /home/user/local/riotbase场景三:替换动态库路径
当调试一个由他人编译的动态库时,其调试信息中记录的是别人的编译路径。可以通过替换规则让 GDB 正确找到动态库:
(gdb) info sharedlibrary (gdb)setsubstitute-path /path/to/otheruser/library /path/to/your/library (gdb)breakmy_function场景四:Windows 路径转义
在 Windows 环境下使用 GDB(如 MinGW 或 Cygwin),路径中的反斜杠需要双重转义:
# 正确写法(gdb)setsubstitute-path C:\\Users\\user\\Desktop\\project ./src# 错误写法(不会生效)(gdb)setsubstitute-path C:\Users\user\Desktop\project ./src场景五:仅替换路径前缀(部分匹配)
如果只想替换路径的前缀部分(而不是完整路径),且原路径以/root开头,可以用:
# 将 /root/test/src 映射到 /home/test/src (gdb) set substitute-path /root /home这样,/root/test/src/main.c会被映射为/home/test/src/main.c,而/root/other/src/x.c会映射为/home/other/src/x.c。
场景六:树形结构的路径映射
当整个项目树被整体移动时,一条set substitute-path规则就能覆盖所有子目录。相比directory命令需要逐个添加子目录,set substitute-path更加高效。
5. 查看与替换当前规则
查看所有规则:
(gdb) show substitute-path- 查看特定路径会被如何替换
(gdb) show substitute-path /original/path/to/file.c- 删除特定规则:
(gdb) unset substitute-path /old/path- 删除所有规则:
(gdb) unset substitute-path6. 常见问题与注意事项
6.1 替换规则不生效怎么办?
检查路径末尾是否包含文件名。规则应该基于目录级别进行设置,而不是精确到文件名:
# 错误(包含文件名) set substitute-path /old/src/main.c /new/src/main.c # 正确(基于目录) set substitute-path /old/src /new/src6.2 规则始终从路径开头进行替换
GDB 的替换是前缀匹配,不是子串替换。例如,规则/src→/new_src可以匹配/src/project/file.c,但不能匹配/home/user/src/file.c。
6.3 与dir命令的区别与配合
| 命令 | 应用场景 | 特点 |
|---|---|---|
dir | 相对路径 | 添加搜索目录,需要逐个添加 |
set substitute-path | 绝对路径前缀 | 一条规则覆盖整个目录树 |
set sysroot | 远程调试动态库 | 指定远程目标系统根目录 |
如果源代码使用相对路径存储,应使用dir命令;如果是绝对路径,则使用set substitute-path。
6.4 远程调试时的路径映射
在进行 GDB 远程调试时(如使用target remote连接 gdbserver),主机端的 GDB 需要正确映射目标机上的源代码路径。通常需要结合set sysroot和set substitute-path一起使用。
6.5 验证调试信息中的原始路径
使用以下命令查看可执行文件或动态库中记录的调试路径:
readelf -p .debug_str your_program strings your_program | grep -E "^/.*/"7. 总结
set substitute-path是 GDB 中处理源代码路径不匹配问题的核心命令,特别适用于以下场景:
项目源代码在编译后被整体移动
跨机器调试(远程调试、Docker 容器)
调试他人编译的动态库或可执行文件
交叉编译环境调试
使用时注意:规则基于前缀匹配,替换从路径开头进行,且from部分需要匹配到目录分隔符。在 Windows 环境下需要双重转义反斜杠。