如何在 Linux 上使用 gdbserver 进行远程调试

最后更新: 14/01/2026
作者: 艾萨克
  • gdbserver 作为 GDB 的远程代理,通过 TCP 或串口控制另一台机器上的进程。
  • 要进行远程调试,关键是要使用以下方式编译: 符号使用合适的 gdb 并正确配置符号和字体路径。
  • gdbserver 提供单进程模式和多进程模式,还可以与 WinDbg 和 QEMU 集成,用于内核调试。
  • --debug、sysroot 和值大小限制等选项有助于诊断问题并稳定会话。

使用 gdbserver 进行远程调试

如果你用 C 或 C++ 编程 Linux 而且你从来没接触过 gdbserver你错过了远程调试进程最实用的工具之一,无论是在服务器、嵌入式系统,还是虚拟机或WSL中。gdbserver并非什么“神奇”的工具,也并非专家专属,它只是一个与gdb通信并控制目标进程执行的小程序。

核心思想非常简单。在运行要调试的二进制文件的机器上( 目标)你启动 gdbserver;在你的工作电脑上( 主持人你可以启动支持 gdb 协议的 gdb 或 WinDbg。两者都通过 TCP 或串口连接,连接后你可以设置断点、检查变量、查看堆栈,或者像在自己的机器上运行程序一样,逐步跟踪程序的执行过程。

gdbserver是什么?什么时候适合使用它?

什么是 gdbserver

gdbserver 是 GNU gdb 的远程调试“代理”它的功能非常具体:它在被分析程序运行的机器上运行,控制该进程(或多个进程),并通过远程连接与位于另一台机器(或同一台机器)上的 gdb 客户端通信。

在日常使用中,gdbserver 通常用于两种典型场景。调试运行于嵌入式环境(路由器、精简版 Linux 开发板、设备)中的软件 IoT等等)以及在远程 Linux 机器上调试进程,在这些机器上,拥有包含所有库和符号的“胖”gdb 并不方便,或者根本不可能。

在实际应用中,gdbserver 处理诸如以下任务: 读取和写入进程寄存器和内存,控制程序执行(继续、暂停、单步执行),管理断点,并使用 GDB 远程协议将所有这些数据发送到 gdb。这种理念与 OpenOCD 等工具非常相似,后者充当 gdb 和程序之间的桥梁。 硬件 外部的,区别在于 gdbserver 运行在与二进制文件运行相同的系统上。

如果你来自这样的环境 Windows 了解这一点也很有意思。 像 WinDbg 这样的调试器可以与 Linux 上的 gdbserver 通信,因此您可以使用 WinDbg 通过 gdb 协议提供的远程调试支持来调试 Linux 上的用户进程,该协议是 Microsoft 在最新版本中集成的。

使用 gdb 和 gdbserver 进行调试的基本要求

使用 gdbserver 的要求

在开始远程调试之前,您需要了解主机/目标之间的关系。。 该 目标 这是运行待调试程序和执行 gdbserver 的机器; 主持人 你将在这台机器上运行 gdb(或 WinDbg),并将源代码以及(最好是)调试符号放在这台机器上。

最基本的起点是用符号编译二进制文件。在 GCC 或 g++ 中,这是通过标志实现的。 -g通常建议同时禁用优化功能(例如使用……) -O0这样,调试器就能更准确地显示变量、宏和代码结构。对于特定的宏,您可以使用更高的调试级别,例如: -g3.

主机端需要兼容版本的 gdb 使用目标架构。要调试 MIPS、ARM 或其他架构的嵌入式系统,必须使用相应交叉工具链的 gdb(例如)。 arm-none-eabi-gdb o gdb-multiarch)如有必要,配置架构和字节序 comandosset arch y set endian.

关于连接方式,gdbserver 支持两种主要类型。串行链路(在嵌入式硬件中非常常见,通过 UART)和 TCP/IP 协议,当目标设备位于同一网络或可通过网络访问的 Linux 机器时,TCP/IP 协议最为方便。在这两种情况下,命令都通过 gdb 执行。 target remote 连接到 gdbserver 公开的端点。

启动 gdbserver 的方式:单进程模式和多进程模式

gdbserver 执行模式

gdbserver 可以通过两种主要方式工作。 当我们谈到用户模式调试时:可以直接与单个进程关联,或者作为“进程服务器”允许列出和附加到不同的系统进程。

单进程模式 启动 gdbserver 时,需要指定主机、端口以及要运行的程序。在 Linux 桌面机器上,一个简单的例子是这样的:

命令: gdbserver localhost:3333 foo

通过该命令,gdbserver 启动二进制文件。 foo 他一直监听着3333端口。在远程 gdb 连接之前,程序会保持停止状态;当 gdb 连接时,程序才会停止运行。 target remote localhost:3333进程开始由调试器控制。

在多进程模式(进程服务器)下,可以使用该选项。 --multi在这种情况下,gdbserver 不会直接启动任何程序,而只是监听传入的连接,并允许客户端(gdb 或 WinDbg)管理要创建或附加到哪个进程:

  谷歌推出 Gemini Code Assist:免费的人工智能编程助手

命令: gdbserver --multi localhost:1234

在 Linux 上使用 WinDbg 时,这种多模式尤其有趣。因为可以直接从 WinDbg 列出远程系统上的进程,查看进程 ID (PID)、用户和命令行,并附加到您感兴趣的进程,这与使用进程服务器的方式类似。 dbgsrv.exe 在Windows上。

使用 gdbserver 和 gdb 进行远程调试的逐步指南

让我们用一个非常典型的例子来具体说明。:使用 gdbserver 在同一台机器(主机和目标匹配)上调试一个简单的应用程序,以模拟远程场景。

首先,你需要编写并编译一个小程序。例如,一个简单的循环,用于打印计数器:

命令: gcc -g foo.c -o foo

关键在于旗帜。 -g这会将必要的调试信息添加到二进制文件中,以便 gdb 可以显示代码行、变量名、类型等。在“真正的”交叉编译环境中,您可以使用交叉编译工具链进行编译,然后将二进制文件及其依赖项复制到目标位置。

下一步是在目标系统上启动 gdbserver。如果主机和目标是同一台机器,则:

命令: gdbserver localhost:3333 foo

你会看到类似这样的消息 “进程 foo 已创建;进程 ID = XXXX;正在监听端口 3333”。这表明 gdbserver 已创建该进程,并正在等待 gdb 连接。如果您的系统需要更高的权限(例如,附加到系统进程),则可能需要使用以下命令运行该命令。 sudo但是,在授予许可时保持谨慎总是明智的。 送至脱硫装置。

在主机上,启动 gdb 并指定本地可执行文件。 (与目标系统上运行的程序相同,或者带有符号的完全相同的副本):

命令: gdb foo

进入 gdb 后,您可以使用以下命令建立远程连接::

命令: target remote localhost:3333

此时,gdb 从本地二进制文件中加载符号。它与 gdbserver 同步,并接管 gdbserver 下实际运行的进程。之后的流程与通常流程相同:执行诸如此类的命令 break 设定突破点, continue, step, next, print 为了检查变量, backtrace 查看电池等情况。

使用 gdbserver 连接到正在运行的进程

你并不总是想从头开始启动程序。通常,你对加入一个已经运行的流程感兴趣(例如,一个 httpd路由器(系统守护进程或生产服务)。

典型的做法是使用该选项 --attach 来自 gdbserver传递监听端口和目标进程的 PID。例如,在已复制了针对其架构编译的 gdbserver 的路由器上,您可以这样做:

命令: gdbserver localhost:3333 --attach <pid_de_httpd>

在主机端,您将使用支持路由器架构的 gdb 版本。, 例如 gdb-multiarch预先配置架构和字节序:

命令: set arch mips
set endian big

然后指定包含符号的本地文件。 远程二进制文件(例如) file httpd)并且,如有必要,您可以使用以下命令告诉 gdb 二进制文件在目标上的实际运行位置: set remote exec-file /usr/bin/httpd最后,和以前一样,你连接到:

命令: target remote 192.168.0.1:3333

一旦连接您可以为特定函数设置断点(例如)。 break checkFirmware),继续执行,让程序的正常流程(例如,从 Web 界面上传固件)触发断点。

在 Linux 上使用 WinDbg 和 gdbserver

近年来,微软在 WinDbg 中添加了对调试 Linux 进程的支持。 使用 gdbserver 作为后端。此功能适用于您在 Windows 系统下工作,但代码运行在 Linux(包括 WSL)上的场景。

使用 gdbserver 通过 WinDbg 调试特定的 Linux 进程流程大致如下:首先,使用类似这样的命令在 Linux 机器上找到目标进程。 ps -A (例如一个 python3 然后,在目标系统上启动 gdbserver(前提是目标系统正在运行):

命令: gdbserver localhost:1234 python3

如果环境需要,您可能需要使用 sudo gdbserver ...采取与以往相同的安全预防措施。当 gdbserver 显示“正在监听端口 1234”后,在 WinDbg 中转到“文件 / 连接到远程调试器”,并指定以下类型的连接字符串:

命令: gdb:server=localhost,port=1234

WinDbg 使用一个小型 gdb 协议“驱动程序”与 gdbserver 通信。 而且,一旦连接建立,它就会停留在那个点。 引导 进程。从那里,您可以使用其堆栈窗口、模块、内存、断点以及诸如以下的命令 k 查看电池或 lm 列出模块(请注意,某些命令需要 PE 格式而不是 ELF 格式,因此在某些情况下可能会显示奇怪的数据)。

gdbserver 和 WinDbg 进程服务器

除了单进程情况外,WinDbg 还可以连接到充当进程服务器的 gdbserver。 使其工作方式更类似于处理远程 Windows 进程的方式。在此模式下,gdbserver 将以如下方式启动: --multi 且不包含相关流程:

  在 iPhone 上激活低电量模式的最佳方法

命令: sudo gdbserver --multi localhost:1234

在 WinDbg 中,选择“文件 / 连接到进程服务器” 您可以重复使用连接字符串 gdb:server=localhost,port=1234连接激活后,您可以列出可用的 Linux 进程,并附加到您想要的进程,甚至可以启动一个新进程。

需要注意一个细微之处。WinDbg 会根据 gdbserver 连接时是否已附加到某个进程来区分“进程服务器”和“单个目标”。如果您在 gdbserver 附加到某个进程后关闭了 WinDbg,然后尝试重新连接,则 WinDbg 可能不会将其识别为进程服务器,您可能需要重启 gdbserver。

结束进程服务器会话通常情况下,只需在运行 gdbserver 的控制台中按下 CTRL+D 并停止 WinDbg 的调试即可。在某些极端情况下,如果出现同步问题,则可能需要完全关闭调试器并从头开始重新启动 gdbserver。

远程调试中的符号和源代码管理

远程调试便捷的关键之一在于符号和字体部分的完美解析。如果没有符号,浏览堆栈或在特定函数上设置断点就会变得非常痛苦。

在经典的 gdb + gdbserver 场景中,最好在主机上保留一份带有符号的可执行文件副本。 (未剥离符号表)和源代码树。gdb 不要求远程二进制文件包含符号表;只要你用 gdb 加载的本地文件包含符号表即可。 file 在偏移级别上匹配远程可执行文件。

在 WinDbg 和 Linux 调试领域,也出现了像 DebugInfoD 这样的服务。它通过 HTTP 公开符号和字体。WinDbg 可以使用特殊类型的路径。 DebugInfoD*https://debuginfod.elfutils.org 两个人都在 .sympath 如在 .srcpath 按需下载 DWARF 符号和 Linux ELF 二进制源代码。

以 WSL 为例,用户代码位于 C:\Users\Bob\你可以通过 WinDbg 来判断:

命令: .sympath C:\Users\Bob\
.srcpath C:\Users\Bob\

如果您还想对系统二进制文件使用 DebugInfoD,:

命令: .sympath+ DebugInfoD*https://debuginfod.elfutils.org
.srcpath+ DebugInfoD*https://debuginfod.elfutils.org

在这种配置下,当您检查堆栈或进入 libc 函数时,WinDbg 可能会尝试下载相应的 DWARF 符号,如果服务器也公开了代码,则会非常详细地显示源代码,尽管 Windows 工具链内部对 ELF 和 DWARF 的处理方式与对 PE 和 PDB 的处理方式不同,它并不“原生”地处理 ELF 和 DWARF。

实际示例:使用 gdbserver 和 WinDbg 调试 C++ 程序

一个示例性的例子是一个小型 C++ 应用程序,它可以向屏幕上输出问候语。,在 WSL 中编译并带有调试符号。想象一下一个程序,它预留了一个 std::array<wchar_t, 50> 并将较长的消息复制到其中,导致文本被截断,并出现字符缺失。 ???? 最后

用类似这样的工具编译之后:

命令: g++ DisplayGreeting.cpp -g -o DisplayGreeting

你针对该二进制文件启动 gdbserver:

命令: gdbserver localhost:1234 DisplayGreeting

在 WinDbg 中,您可以使用字符串进行连接。 gdb:server=localhost,port=1234 会话建立并配置好符号和字体路径后,您就可以设置断点了。 DisplayGreeting!main你可以用 dx greeting 检查本地数组并查看其大小(50 个位置),然后在内存选项卡或变量视图中直观地检查问候语是如何被截断的。

这个例子的妙处在于它表明,即使 WinDbg 不完全支持所有 ELF/DWARF 格式,也能取得不错的效果。使用 gdbserver 作为远程后端,您可以相当轻松地遍历堆栈、检查类型、按函数名称设置断点以及浏览 C++ 代码。

使用 qemu 和 gdb 调试 Linux 内核

gdbserver 不仅可以在用户模式下使用;在内核模式下也有非常强大的应用场景。尤其是在将 QEMU 与调试支持结合使用时。虽然这里“gdbserver”的角色是由 QEMU 自身的选项实现的,但方法是一样的:一端运行待调试的系统并打开一个 gdb 端口;另一端可以是 gdb 或支持远程协议的调试器。

要调试内核,需要使用特定的调试选项进行编译。:启用调试信息的生成(CONFIG_DEBUG_INFO),GDB 内核脚本(CONFIG_GDB_SCRIPTS)以及内核自身的调试模式(CONFIG_DEBUG_KERNEL此外,禁用链接期间删除符号的选项也很重要,例如“链接期间剥离汇编器生成的符号”。

编译后您将得到一个二进制文件 vmlinux “未剥离”你将使用 gdb 中的那个。你还需要一个基本的 initramfs,你可以使用类似这样的命令生成它:

命令: mkinitramfs -o ramdisk.img

然后使用调试参数启动 QEMU一个典型的例子包括选项 -gdb tcp::1234 打开一个与 gdb 兼容的远程端点,并且 -S 这样虚拟机就会从一开始就处于暂停状态。你还可以指定内核。 -kernel vmlinux中, -initrd ramdisk.img记忆与 -m 512 通常情况下,你会将控制台重定向到 ttyS0 从头到尾管理一切 终端.

  如何在 Windows 11 上将 Edge 更新到最新版本:完整分步指南

QEMU 被扣留等待 gdb从主机启动 gdb,指向 vmlinux 你与……联系 target remote localhost:1234从那里你可以设置早期断点,例如 hb start_kernel并通过诸如此类的命令来控制执行。 c (继续)按 CTRL+C 再次暂停。

gdb 和 gdbserver 的最新变化和细微差别

在像 Red Hat Enterprise Linux 8 这样的现代发行版中,gdb 和 gdbserver 有一些值得注意的变化。尤其是如果您是从以前的版本升级而来,或者有分析调试器输出的脚本。

一方面,gdbserver 现在使用 shell 启动“底层”进程。与 gdb 类似,这允许在命令行中进行变量扩展和替换。如果出于任何原因需要禁用此功能,RHEL 8 中提供了相应的设置,可以恢复到之前的模式。

有些内容已被删除或更改。:为使用以下方式编译的 Java 程序提供调试支持 gcjHP-UX XDB 兼容模式,例如: set remotebaud (已替换为 set serial baud)或与某些旧格式的兼容性 stabs此外,线程编号不再是全局的,而是按“较低”线程编号,并且显示为 inferior_num.thread_num以及一些新的便利变量,例如 $_gthread 指代全局标识符。

另一个相关的新功能是调整 max-value-size这限制了 gdb 可以分配多少内存来显示值的内容。默认值为 64 KiB,因此尝试打印大型数组或庞大的结构体可能会导致“值过大”的警告,而不是显示所有可用内存。

此外,gdb 处理方式也进行了调整。 sysroot默认值现在是 target:这意味着对于远程进程,它会首先尝试在目标系统上查找库和符号。如果您希望它优先使用本地符号,则应该运行 set sysroot 在出发前,先选择你感兴趣的路线。 target remote.

关于命令历史记录,当前使用的环境变量是 GDBHISTSIZE 而不是 HISTSIZE这样,您可以微调在调试会话中输入的命令的保留时间,而不会干扰使用行读取库的其他应用程序的行为。

使用 gdbserver 的工作流程技巧和故障排除

为了拥有舒适的工作流程,有一些模式往往非常有效。 在为嵌入式系统或远程服务器开发程序时,第一步是尽可能地自动化符号编译和二进制文件部署到目标平台。这样,您始终可以知道正在运行的是哪个版本的可执行文件,并且主机上随时可以获取符号文件副本。

在崩溃核心较多的环境中,学习如何在批处理模式下使用 gdb 是值得的。带有如下旗帜 --batch, --ex y -x 自动对一系列核心启动命令,并从脚本中处理它们的回溯信息(例如在 Python 这样可以快速筛选出重复出现的问题,按堆栈跟踪对故障进行分组等等。

当远程连接出现问题时,该选项 --debug gdbserver 是你最好的朋友例如,如果您使用以下命令启动进程服务器:

命令: gdbserver --debug --multi localhost:1234

gdbserver 控制台将显示详细的运行跟踪信息。 在远程协议层面,这包括传入的数据包、格式错误、断开连接问题等。当 gdb 服务器突然断开连接、进程在设置断点后立即崩溃,或者调试 GUI 发送 gdbserver 无法理解的内容时,这非常有用。

在诸如 TP-Link 路由器之类的环境中,您可以将 gdbserver 附加到关键进程,例如 httpd某些断点很容易导致竞态条件或看门狗机制触发进程终止,尤其是在调试器中进程“卡住”过久的情况下。在这种情况下,可能需要调整阻塞的信号、控制的线程,并在必要时修改系统配置(例如超时时间、硬件看门狗机制),以允许更长时间的调试会话。

熟练使用 gdbserver 需要结合多个部分。使用合适的符号进行编译,为架构选择正确的 gdb,配置符号和源文件路径,理解 gdbserver 的两种主要模式(单进程和多进程),并且不要害怕从该模式下拉取代码。 --debug 当连接出现异常时,有了这些基础,从你的电脑调试运行在远程 Linux 系统、路由器或带有自定义内核的虚拟机上的应用程序就变得非常轻松,而且极其有用。