CGDB 使用方法详解
CGDB可以理解成:
GDB 的终端图形界面版。
它不是另一套调试器,底层仍然是 GDB,只是在终端中增加了源码窗口、断点标记、当前执行行高亮和快捷键。上面通常显示源码,下面是标准 GDB 命令行;因此所有 GDB 命令在 CGDB 里仍然可以使用。CGDB
┌──────────────────────────────────────┐ │ 源码窗口:显示代码、当前执行行、断点 │ ├──────────────────────────────────────┤ │ GDB窗口:(gdb) 输入调试命令和看输出 │ └──────────────────────────────────────┘一、安装 CGDB
Ubuntu 中执行:
sudo apt update sudo apt install cgdb gdb检查:
cgdb --version gdb --versionCGDB 在 Ubuntu 软件仓库中属于基于 curses 的 GDB 界面。Ubuntu软件包
二、程序必须带调试信息
例如你的项目有:
client.c server.c com.c com.h编译客户端:
gcc -Wall -Wextra -std=c11 -g -O0 \ client.c com.c -o client编译服务端:
gcc -Wall -Wextra -std=c11 -g -O0 \ server.c com.c -o server关键参数:
-g 在可执行文件中加入源码、变量、行号等调试信息 -O0 关闭优化,让代码执行顺序更接近源码没有-g,CGDB 可能只能看到汇编,或者无法查看局部变量。优化开启后,还可能出现变量被优化掉、代码跳行等现象。GDB 官方建议编译时生成调试信息;关闭优化通常更容易逐行调试。Sourceware
Makefile :
CFLAGS = -Wall -Wextra -std=c11 -g -O0三、启动 CGDB
调试服务端:
cgdb ./server调试客户端:
cgdb ./client进入后,通常会自动显示包含main()的源文件。
若没有显示想看的文件,在源码窗口按:
o打开源码文件列表,然后选择client.c、server.c或com.c。源码窗口支持文件选择、搜索、跳转和可视化断点。CGDB
四、最重要:CGDB 有不同模式
CGDB 初学最容易混乱的地方就是“我现在在哪个窗口”。
1. 源码模式
启动后一般处于源码模式。
这里可以:
j / ↓ 向下移动 k / ↑ 向上移动 gg 跳到文件开头 G 跳到文件结尾 25G 跳到第25行 /内容 向下搜索 ?内容 向上搜索 n 下一个搜索结果 N 上一个搜索结果 空格 在当前行设置断点 t 设置临时断点 o 打开源码文件列表2. GDB 命令模式
在源码模式按:
i光标会进入下面的 GDB 窗口,现在可以输入:
break main run print i next continue3. 返回源码模式
在 GDB 命令窗口按:
Esc回到上方源码窗口。
因此最核心的切换是:
源码窗口 --按 i--> GDB命令窗口 GDB窗口 --按 Esc--> 源码窗口4. GDB 输出滚动模式
GDB 输出很多时,在 GDB 窗口按PageUp会进入历史输出滚动模式。
滚动时:
PageUp / Ctrl-u 向上 PageDown / Ctrl-d 向下 gg 最前面 G 最后面 q / Enter / i 退出滚动模式这些模式和按键均来自 CGDB 的官方模式设计。CGDB
五、第一次完整调试流程
假设调试:
cgdb ./client第一步:在main()设置断点
在源码窗口移动到:
int main()或者函数内第一条有效语句,然后按:
空格断点行旁边会出现断点标记。
也可以按i进入 GDB 窗口,输入:
break main简写:
b main第二步:启动程序
源码模式按:
F5等价于:
run简写:
r程序运行到断点前停止,源码窗口会高亮当前即将执行的行。
第三步:查看变量
按i,输入:
print i简写:
p i查看指针:
p addr十六进制显示:
p/x addr查看指针指向的字符:
p *addr查看第二个字节:
p addr[1]查看第二个字节地址:
p &addr[1]第四步:单步执行
按Esc回源码窗口,然后使用快捷键:
F8 next:执行下一行,不进入函数 F10 step:执行下一行,遇到函数则进入函数 F7 finish:执行完当前函数,返回调用者 F6 continue:继续运行到下一个断点官方默认快捷键对应:
F5 run F6 continue F7 finish F8 next F10 step六、next 和 step的区别
假设当前行:
int shmid = create_shm(SHM_SIZE);执行:
next会把整个create_shm()当成一行执行完,不进入函数内部。
执行:
step会进入:
int create_shm(size_t size) { ... }常用简写:
n next s step fin finish c continue注意:在源码模式直接按字母s,它不是 GDB 的step命令,而是切换到 GDB 输出滚动模式。因此初学阶段建议使用F10,或者先按i再输入s。
七、程序执行控制命令
| 命令 | 作用 |
|---|---|
run/r | 从头启动程序 |
start | 启动并自动停在main() |
continue/c | 继续到下一个断点 |
next/n | 下一源码行,不进入函数 |
step/s | 下一源码行,进入函数 |
finish | 运行到当前函数返回 |
until | 运行到循环后面或指定行 |
kill | 终止当前被调试程序 |
quit/q | 退出调试器 |
例如:
start会直接启动并停在main()附近,比先设置break main再run更方便。
程序带命令行参数时:
set args hello 123 show args run相当于正常运行:
./client hello 123八、断点详解
1. 按函数名设置
break main break create_shm break attach_shm2. 按行号设置
当前文件第 25 行:
break 25或者 b 25指定文件:
break client.c:25 break com.c:153. 条件断点
例如只在i == 80时停止:
break client.c:30 if i == 80也可以先设置普通断点:
break client.c:30假设编号为 2,再添加条件:
condition 2 i == 804. 查看所有断点
info breakpoints简写:
info bGDB 会给每个断点编号,并显示是否启用、地址、源码位置和条件。Sourceware
5. 禁用和启用
disable 2 enable 2禁用不会删除断点,只是暂时不生效。
6. 删除断点
删除编号 2:
delete 2删除全部:
delete按源码位置删除:
clear client.c:307. 临时断点
tbreak main临时断点命中一次后自动删除。
九、查看变量和表达式
print
p i p shmid p addr p addr[0] p addr[1] p strlen(addr) p sizeof(addr)格式化显示:
p/x i 十六进制 p/d i 十进制 p/c i 字符 p/t i 二进制例如:
p/c 65结果类似:
65 'A'查看变量类型
ptype addr ptype shmid例如:
ptype addr可能显示:
type = char *查看当前函数局部变量
info locals查看当前函数参数
info args自动显示变量
display i display addr以后每次程序停止,GDB 都会自动显示它。
查看自动显示列表:
info display取消:
undisplay 1临时修改变量
set variable i = 90简写通常也可以:
set var i = 90这只修改当前调试进程运行时的变量,不会修改源代码文件。
十、查看内存:x命令
x表示 examine,专门查看内存。
格式:
x/数量格式单位 地址可以理解为:
x / n f u address n:查看多少个单位 f:以什么格式显示 u:每个单位多大常见格式:
x 十六进制 d 有符号十进制 u 无符号十进制 c 字符 s 字符串 i 汇编指令常见单位:
b 1字节 h 2字节 w 4字节 g 8字节GDB 官方将x定义为独立于程序数据类型的内存查看命令。Sourceware
查看共享内存中的字符串
x/s addr查看 32 个字节的十六进制值
x/32bx addr含义:
32:查看32个 b :每个1字节 x :十六进制查看 64 个字符
x/64cb addr查看 10 个整数
假设p是int *:
x/10dw p查看指令
x/10i $pc从当前程序计数器开始查看 10 条机器指令。
十一、特别适合你当前共享内存代码的调试方法
你之前有:
char *addr = attach_shm(shmid); while (i <= 90) { snprintf(addr, SHM_SIZE, "%c", i); addr += 2; }可以在循环内部设置断点,然后查看:
p/x addr p i x/16cb addr如果保存了原始地址:
char *base = addr;可以查看移动了多少字节:
p addr - base例如输出:
$1 = 20说明addr已经向后移动了 20 字节。
查看整个共享内存开头:
x/64cb base查看字符串:
x/s base循环结束前检查:
p/x base p/x addr p addr - base这样可以直接证明:
base 仍是 shmat() 返回的起始地址 addr 已经因为 addr += 2 向后移动最后必须:
detach_shm(base);而不是:
detach_shm(addr);十二、查看函数调用栈
程序崩溃或卡住时,最重要的命令是:
backtrace简写:
bt输出可能类似:
#0 commshm() at com.c:15 #1 create_shm() at com.c:6 #2 main() at server.c:8意思是:
main() └── create_shm() └── commshm() 当前停在这里显示局部变量:
bt full切换栈帧:
frame 0 frame 1 frame 2向调用者移动:
up向被调用函数移动:
down调用栈由一系列栈帧组成,GDB 可以查看每层函数的参数、局部变量和源码位置。Sourceware
十三、调试段错误
程序出现:
Segmentation fault在 CGDB 中重新运行:
run发生段错误后通常会自动停住,然后按顺序执行:
bt frame 0 info locals info args list p 相关指针 x/16bx 相关指针例如:
p addr p/x addr x/16bx addr如果地址是:
0x0通常说明空指针。
还可以检查:
p shmid p addr == (void *)-1调试shmat()时尤其有用。
十四、监视变量什么时候被修改
例如想知道addr什么时候变化:
watch addr程序继续运行:
continue当addr += 2修改addr时,GDB 会停止,并显示旧值和新值。
监视共享内存第一个字节:
watch base[0]监视i:
watch i查看监视点:
info breakpoints删除方式与普通断点相同:
delete 3Watchpoint 是一种在表达式值发生变化时停止程序的特殊断点;某些平台使用硬件监视点,软件监视点则可能明显降低执行速度。Sourceware
对于你的共享内存实验,最好在实际执行写操作的客户端进程中设置监视点;服务端和客户端是两个独立进程,通常应分别调试。
十五、调试客户端和服务端
client和server是两个独立可执行程序,最简单可靠的方法是使用两个 CGDB。
终端一
cgdb ./server设置断点:
break main break server.c:读取数据的行号 run终端二
cgdb ./client设置断点:
break main break client.c:写共享内存的行号 run终端三
继续监视进程:
watch -n 1 \ 'ps -o pid,ppid,pgid,sid,stat,wchan:25,cmd -C client -C server'这样可以同时观察:
服务端停在哪里 客户端停在哪里 共享内存内容如何变化 进程当前是否睡眠或运行十六、附加到已经运行的进程
假设服务端已经运行:
./server另一个终端查询 PID:
pgrep -x server假设结果:
1281513打开 CGDB:
cgdb ./server按i,输入:
attach 1281513附加后,GDB 会先暂停该进程。接下来可以:
bt info locals p addr continue完成后:
detachdetach会解除调试控制,并让进程继续独立运行。GDB 官方支持通过 PID 附加到已运行进程;附加时会先停止该进程,随后可以设置断点、单步和查看变量。Sourceware
这个方法特别适合判断:
服务端到底阻塞在
read()、sleep()、open(),还是别的位置。
附加后执行:
bt就能看到当前调用链。
十七、调试fork()父子进程
如果程序内部调用fork(),想让调试器跟踪子进程:
set follow-fork-mode child想继续跟踪父进程:
set follow-fork-mode parent希望父子进程都保留在 GDB 控制下:
set detach-on-fork off查看所有被调试进程:
info inferiors切换到第二个进程:
inferior 2恢复默认的“分离另一个进程”模式:
set detach-on-fork onGDB 在 Linux 上支持通过follow-fork-mode选择父进程或子进程,并通过detach-on-fork off同时保留多个 fork 产生的进程。Sourceware
十八、调试系统调用
你正在学习管道、共享内存,可以让 GDB 在系统调用发生时停止。
捕获read():
catch syscall read捕获write():
catch syscall write同时捕获:
catch syscall read write捕获 System V 共享内存相关调用:
catch syscall shmget shmat shmdt shmctl查看捕获点:
info breakpoints继续:
continue注意,这可能会非常频繁地停止,因为标准库和动态链接器也可能执行很多系统调用。GDB 的catch syscall可以根据系统调用名称或编号创建捕获点,具体可用名称取决于目标系统和 GDB 的系统调用数据库。Sourceware
十九、调试命名管道中的SIGPIPE
如果写端向已经没有读端的管道写入,程序可能收到SIGPIPE。
让 GDB 收到SIGPIPE时停住:
handle SIGPIPE stop print pass含义:
stop 收到信号时停止 print 显示提示 pass 继续时仍把信号交给程序查看信号配置:
info signals SIGPIPEGDB 可以配置不同信号是停止、打印、传递还是忽略;SIGPIPE在 Unix 系统中是标准信号之一。Sourceware
二十、程序输入输出和 CGDB 命令冲突
假如被调试程序需要:
scanf(...) read(STDIN_FILENO, ...)程序输入和 GDB 命令可能都在同一个终端,看起来比较乱。
对于你的 SSH 环境,建议给被调试程序指定另一个终端。
在另一个终端执行:
tty假设输出:
/dev/pts/5在 CGDB 的 GDB 窗口执行:
set inferior-tty /dev/pts/5然后:
run此时:
CGDB窗口:输入调试命令 /dev/pts/5:显示程序输出、接收程序输入查看当前设置:
show inferior-ttyGDB 官方支持通过set inferior-tty将被调试程序的输入输出定向到指定终端。Sourceware
CGDB 源码模式还提供Ctrl-T打开新的被调试程序终端,不过在纯 SSH 环境中,手动指定/dev/pts/N往往更稳定。CGDB
二十一、窗口快捷键
在源码模式中:
| 快捷键 | 作用 |
|---|---|
Ctrl-W | 横向/纵向切换窗口 |
- | 缩小源码窗口一点 |
= | 放大源码窗口一点 |
_ | 缩小源码窗口约 25% |
+ | 放大源码窗口约 25% |
Ctrl-L | 清屏并重新绘制 |
'. | 跳回当前执行行 |
o | 打开源码文件列表 |
这些是 CGDB 自身快捷键,不是 GDB 命令。CGDB
二十二、配置 CGDB
配置文件默认是:
~/.cgdb/cgdbrc创建:
mkdir -p ~/.cgdb nano ~/.cgdb/cgdbrc推荐配置:
set autosourcereload set color set hlsearch set ignorecase set tabstop=4 set winsplitorientation=horizontal set winsplit=src_big含义:
autosourcereload 源文件修改后自动重新加载 color 开启颜色 hlsearch 高亮搜索结果 ignorecase 搜索时忽略大小写 tabstop=4 Tab显示为4个空格 winsplitorientation 源码和GDB上下排列 winsplit=src_big 源码窗口稍大也可以改成左右布局:
set winsplitorientation=verticalCGDB 默认把用户配置、历史和日志放在~/.cgdb/,启动时会读取其中的cgdbrc;窗口布局、搜索、颜色、Tab 宽度等均可配置。CGDB
临时设置时,在源码模式输入:
:set winsplitorientation=vertical配置文件中则直接写:
set winsplitorientation=vertical二十三、常见问题
1. 看不到源码
重新编译:
gcc -g -O0 main.c -o main cgdb ./main确认启动的是可执行文件,不是源码:
cgdb ./main不是:
cgdb main.c2. 变量显示<optimized out>
关闭优化:
gcc -g -O0 main.c -o main3. 显示No symbol "addr" in current context
可能原因:
当前还没执行到 addr 的作用域 当前栈帧不是定义 addr 的函数 程序没有使用 -g 编译 编译器把变量优化掉了先执行:
bt frame 0 info locals4. 程序正在运行,不能输入命令
按:
Ctrl-C这会中断被调试程序,让控制权回到 GDB。
注意这里终止的是程序当前运行,不一定退出 CGDB。
5. 终端突然“冻住”
可能误按了:
Ctrl-S按:
Ctrl-Q恢复。
然后输入等价 GDB 命令:
run next step continue finish二十四、命令快查
i 进入GDB命令窗口 Esc 回到源码窗口 空格 设置源码断点 start 启动并停在main run / r 从头运行 continue / c 继续运行 next / n 下一行,不进函数 step / s 下一行,进入函数 finish 跳出当前函数 break / b 设置断点 info breakpoints 查看断点 delete 删除断点 print / p 查看变量 info locals 查看局部变量 display 每次停止自动显示变量 watch 变量变化时停止 x/32bx addr 查看32字节十六进制内存 x/s addr 按字符串查看内存 bt 查看调用栈 frame N 切换栈帧 up / down 上下切换栈帧 quit / q 退出