GNU Debugger (GDB)
GNU
调试器 (GDB
) 是一个可移植调试器,可以运行在一些类 UNIX
系统上,并且支持多种编程语言,包括 Ada,C,C++,Objective-C,Free Pascal,Fortran,Go 等。
历史
GDB
是由 Richard Stallman 在 GNU Emacs
稳定之后,于 1986 年写就,并做为他 GNU
系统的一部分。GDB
是一个开源软件,并在 GNU General Public License
证书下发布。它基于 DBX
调试器模型,DBX
来自于伯克利 UNIX
。
自 1990 年到 1993 年,由 John Gilmore 维护。现在它由 GDB Steering Committee
团队维护。
技术细节
特性
GDB
提供对计算机程序执行的跟踪与修改的扩展工具集。用户可以监视并修改程序内部的变量,甚至可以单独调用程序的函数。
GDB
支持目标处理器 (截至 2003) 包括 Alpha
、ARM
、AVR
、H8/300
、Altera Nios/Nios II
、System/370
、X86
、X86-64
、IA-64
、Motorola 68000
、MIPS
、PA-RISC
、PowerPC
、SuperH
、SPARC
、VAX
,以及一些鲜有人知的目标处理器,包括 A29K
、ARC
、ETRAX CRIS
、D10V
、D30V
、FR-30
、FR-V
、Intel i960
、68HC11
、Motorola 88000
、MCORE
、MN10200
、MN10300
、NS32K
、Stormy16
、Z8000
,还有一些更少见的 M32R
、V850
等。
GDB
现在依旧在保持更新。7.0
版本支持对 Python
脚本的支持,7.8
版本支持 GNU Guile
脚本。自 7.0
版本,支持向后调试,允许调试会话向后退回,恢复崩溃程序以查看发生了什么。
远程调试
GDB
通常在调试嵌入式系统时支持远程调试。远程操作是在 GDB
运行在一台设备上,而调试的程序运行在另一个设备上。GDB
可以通过穿行设备或 TCP/IP
与远程设备以某种协议沟通。远程程序可以与 GDB
提供的合适的文件链接,这个文件提供目标侧的通讯协议。另外 gdbserver
可以用来进行远程调试而不需要对它进行修改。
相同的模式被 KGDB
使用来使用 gdb
进行对运行中的 Linux
内核进行源码级调试。使用 KGDB
,内核开发者可以像调试应用程序一样调试内核。可以将断点放在内核源码中,执行代码,观察变量。在那些具有硬件调试寄存器的架构中,可以设置 watchpoint
,在指定的内存地址被执行或被访问时,触发中断。KDBG
需要一个额外的设备,使用串行线缆或者以太网连接到待调试的设备上。
图形用户界面
调试器不包含图形用户界面,默认为命令行接口,虽然它确实有一个文本用户接口。为了便于使用,构建了一下前端,比如 UltraGDB
、Xxgdb
、Data Display Debugger
、Nemiver
、KDbg
、Xcode debugger
、GDBtk/Insight
以及 HP Wildebeest Debugger GUI (WDB GUI)
。一些集成开发环境 (IDE
),比如 Codelite
、Code::Blocks
、Dev-C++
、Geany
、GNAT Programming Studio
、KDevelop
、Qt Creator
、Lazarus
、MonoDevelop
、Eclipse
、NetBeans
以及 Visual Studio
可以添加 GDB
做为它们的组件。
一些调试工具设计配合 GDB
工作,比如内存泄漏检测器。
内部
GDB
使用称作 ptrace
的系统调用 (process trace
的简写) 来观察控制另一个程序的执行情况、确定并修改处理器的内存以及寄存器。下面的 gdb
命令以及对应的 ptrace
调用列出如下:
- (gdb) start:
PTRACE_TRACEME
,令父进程做为一个跟踪者 - (gdb) attach PID:
PTRACE_ATTACH
,连接到一个运行的进程 - (gdb) stop:
kill(child_pid, SIGSTOP)
或PTRACE_INTERRUPT
- (gdb) continue:
PTRACE_CONT
- (gdb) info registers:
PTRACE_GET(FPREGS(ET)
以及PTRACE_SET(FP)REGS(ET)
- (gdb) x:
PTRACE_PEEKTEXT
以及PTRACE_POKETEXT
通过将指定内存地址的指令替换为另一个特殊指令实现断点。执行断点指令会造成 SIGTRAP
。
命令用例
命令 | 描述 |
---|---|
gdb program |
调试 program 程序 (由 shell 启动) |
run -v |
使用指定参数,开始程序的运行 |
bt |
回溯 (防止程序崩溃) |
info registers |
打印所有寄存器信息 |
disas $pc-32, $pc+32 |
反汇编 |
案例
考虑下面的 C
源码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
size_t foo_len(const char *s)
{
return strlen(s);
}
int main(int argc, char *argv[])
{
const char *a = NULL;
printf("size of a = %lu\r\n", foo_len(a));
exit(0);
}
使用 GCC
在 Linux
上编译这段程序,上面的代码必须使用 -g
选项进行编译,以此包含合适的调试信息,这样就能够使用 GDB
进行调试了,假设上面的源码被保存为 example.c
源文件,可以使用下面的命令进行编译:
$ gcc example.c -Og -g -o example
现在二进制可以运行了:
$ ./example
Segmentation fault
上面的程序在运行时产生了段错误的报错,可以使用 GDB
排错。
$ gdb ./example
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2
Copyright (C) 2020 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.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./example...
(gdb) run
Starting program: /home/arv/0_test/example
Program received signal SIGSEGV, Segmentation fault.
0x0000555555555179 in foo_len (s=0x0) at example.c:7
7 return strlen(s);
(gdb) print s
$1 = 0x0
问题在源码第 7 行调用函数 strlen
时出现(因为它的参数 s
是 NULL
)。gdb
运行的实际情况可能与上面的结果不同,取决于 strlen
的实现方式(是否内联),输出可能如下:
$ gdb ./example
GNU gdb (Raspbian 10.1-1.7) 10.1.90.20210103-git
Copyright (C) 2021 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.
Type "show copying" and "show warranty" for details.
This GDB was configured as "arm-linux-gnueabihf".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./example...
(gdb) run
Starting program: /home/arv/test/example
Program received signal SIGSEGV, Segmentation fault.
0xb6fba1dc in strlen () from /usr/lib/arm-linux-gnueabihf/libarmmem-v7l.so
(gdb) bt
#0 0xb6fba1dc in strlen () from /usr/lib/arm-linux-gnueabihf/libarmmem-v7l.so
#1 0x00010470 in foo_len (s=s@entry=0x0) at example.c:7
#2 0x00010480 in main (argc=<optimized out>, argv=<optimized out>) at example.c:14
为了修复这个问题,在函数 main
中的变量 a
必须给到一个合法的字符串,下面是对源码的修复:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
size_t foo_len( const char *s )
{
return strlen(s);
}
int main( int argc, char *argv[] )
{
const char *a = "This is a test string";
printf( "size of a = %lu\n", foo_len(a) );
exit( 0 );
}
重新编译并在 GDB
中运行:
$ gdb ./example
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2
Copyright (C) 2020 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.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./example...
(gdb) run
Starting program: /home/arv/0_test/example
size of a = 21
[Inferior 1 (process 2638) exited normally]
gdb
将 printf
的打印结果打印到屏幕上,并告知用户程序正常退出。