☰
【Linux工具】gdb
2026/10/29 10:49:51 网站建设 项目流程

文章目录

  • 一、调试程序
    • 1、gdb 启动
      • (1) 使用名称拉起进程
      • (2) 附加到已运行进程
      • (3) 先进gdb再attach运行中的进程
    • 2、设置断点
      • (1)按行号设断点
      • (2) 按函数名设断点
      • (3) 地址断点
      • (4) 条件断点
      • (5) 临时断点(命中一次后自动删除)
      • (6) 正则表达式断点
      • B、断点查看与管理
        • 启用 / 禁用
        • 删除断点
      • C、观察点(Watchpoint)
      • D、捕获点(Catchpoint)
      • E、断点命令(命中后自动执行)
      • 保存与恢复断点
    • 七、实战技巧
      • 1. 循环中跳过前 N 次
      • 2. 条件断点 vs ignore
      • 3. 断点命中后打印再继续
      • 4. 在共享库未加载时设断点
      • 5. 多线程断点
    • 八、常用快捷键速查
    • 九、注意事项
    • 2、SIGPIPE 管道破裂信号
    • (1) gdb 调试过程中为什么打印 SIGPIPE
      • 1. 直接原因:程序收到了 `SIGPIPE` 信号
      • 2. GDB 的机制:为什么会中断并打印后续信息?
      • 3. 如何解决?
    • (2) 另一个例子,管道破裂于continue
    • 2、handle 定义 GDB 的信号拦截行为
      • 核心参数
      • 关于 `nopass` 与 `pass`
      • 常见的实际应用
      • 信号范围的指定
      • 与之前 `SIGPIPE` 案例的关联
    • 3、常见的 signal
    • 4、gdb中的常用命令
  • 二、调试Coredump

一、调试程序

1、gdb 启动

(1) 使用名称拉起进程

cd/work_dir gdb ./h530_daemon gdb h530_daemon

(2) 附加到已运行进程

gdb-p1234gdb-p$(pgrep h530_daemon)gdb--pid$(pgrep h530_daemon)gdb--pid=$(pgrep h530_daemon)

(3) 先进gdb再attach运行中的进程

pgrep h5u_autorun#查看PID 结果为1241gdb#启动gdb(gdb)attach1241#在gdb内 attach 运行中的进程

2、设置断点

(1)按行号设断点

break文件:行breakfile.c:10#指定文件的行,简写 bb file.c:func#文件中某函数

(2) 按函数名设断点

b 函数 b function_name#查找范围是全局所有符号,有同名函数时会提示让你选具体哪个b main

(3) 地址断点

无符号时的唯一手段

b *0x4005d6#内存地址处b *main#main 函数入口地址

(4) 条件断点

b funcifi==5#i等于5时中断b file.c:20ifx>100condition1i==5#给已存在的1号断点加条件condition1#查看1号断点条件condition1i==5#修改条件

(5) 临时断点(命中一次后自动删除)

tbreak func tbreak file.c:20

(6) 正则表达式断点

rbreak ^func_.*#匹配所有 func_ 开头的函数rbreak file.c:.*#文件中所有函数

B、断点查看与管理

info breakpoints# 简写 info b,列出所有断点info b# 显示编号、类型、使能、地址、位置、命中次数

输出字段说明:

字段含义
Num断点编号
Typebreakpoint / watchpoint / catchpoint
Disp命中后是否保留(keep / del)
Enb是否启用(y / n)
Address内存地址
What位置(文件:行号 或 函数)
启用 / 禁用
disable1# 禁用1号断点disable# 禁用所有enable1enableonce1# 启用一次后自动禁用enabledelete1# 启用一次后自动删除
删除断点
delete1# 删除1号断点delete# 删除所有(会提示确认)clearfunc# 删除函数处断点clearfile.c:20# 删除指定行断点

C、观察点(Watchpoint)

监视变量/表达式的值变化:

watchvar# 变量被写时中断watchexpr# 表达式值变化时中断rwatch var# 变量被读时中断awatch var# 被读或被写时中断info watchpoints# 查看所有观察点

注意:watch在变量离开作用域后会自动删除;硬件观察点数量有限(通常4个)。

D、捕获点(Catchpoint)

捕获特定事件:

catch throw# C++ 抛异常catch catch# C++ 捕获异常catchexec# exec 调用catch fork# fork 调用catch vfork catch syscall# 系统调用catch syscallopen# 特定系统调用catch signal SIGINT# 信号catch load# 加载动态库catch unload

E、断点命令(命中后自动执行)

给断点绑定命令序列:

b func commands1# 为1号断点设置命令>print x>continue>end

常用技巧:

commands1silent# 不打印断点命中信息printf"x=%d\n", xcontinueend

保存与恢复断点

save breakpoints file.bp# 保存所有断点到文件sourcefile.bp# 恢复断点

七、实战技巧

1. 循环中跳过前 N 次

b func ignore1100# 忽略前100次命中info breakpoints# 查看 ignore 次数

2. 条件断点 vs ignore

  • 条件复杂、命中稀疏 → 用condition
  • 单纯跳过前N次 → 用ignore(性能更好,GDB 不发信号)

3. 断点命中后打印再继续

b func commands silent bt3# 打印3层调用栈continueend

4. 在共享库未加载时设断点

setbreakpoint pending on# 允许对未定义符号设断点b mylib_func# 库加载后自动生效

5. 多线程断点

b func thread2# 只在2号线程中断b funcifthread_id==3

八、常用快捷键速查

命令简写作用
breakb设断点
tbreaktb临时断点
info breakpointsi b查看断点
deleted删除断点
disabledis禁用断点
enableen启用断点
conditioncond条件断点
ignore—忽略N次
watch—观察点
catch—捕获点
commands—断点命令

九、注意事项

  1. 编译需带调试信息:gcc -g -O0
  2. 断点行号会因优化错位:-O2下行号可能不准,建议-O0
  3. 内联函数:b inline_func可能失败,改用地址或加-fno-inline
  4. 模板函数:需用完整实例化名,如b std::vector<int>::push_back
  5. 条件断点有开销:GDB 每次命中都要求值,大量循环会变慢
  6. 硬件断点:hbreak使用硬件寄存器,数量有限但可用于只读内存
  7. pending 断点:库未加载时设的断点,info b显示PENDING

一句话记忆:

b设断点,i b看断点,d删断点,dis/en开关断点,condition/ignore控命中,watch看变量,catch抓事件,commands自动跑。

2、SIGPIPE 管道破裂信号

(1) gdb 调试过程中为什么打印 SIGPIPE

这张截图展示了在使用 GDB 调试程序时,程序因为收到了SIGPIPE信号(管道破裂)而中断的现象。
以下是对“为什么打印这些”的详细解释,分为直接原因和GDB 的机制两部分:

1. 直接原因:程序收到了SIGPIPE信号

  • 红框内容:received signal SIGPIPE, Broken pipe.
    • SIGPIPE:这是一个信号(Signal)。在 Linux/Unix 系统中,当你试图向一个已经关闭了读取端的管道(Pipe)或 Socket写入数据时,系统就会向当前进程发送SIGPIPE信号。
    • 默认行为:SIGPIPE的默认处理动作是终止进程。
    • 触发场景:你的程序里有一个线程叫monitor_tcp_cli,它正在操作网络连接(TCP)。这通常发生在:
      • 客户端(对端)已经关闭了连接(断开了 TCP)。
      • 你的程序没有检测到这个断开,仍然尝试向这个连接send()或write()数据。
      • 内核发现管道/连接已断,于是抛出SIGPIPE。

2. GDB 的机制:为什么会中断并打印后续信息?

  • GDB 的默认设置:
    • 在使用 GDB 调试时,GDB 默认会捕获所有的信号。当程序运行中收到信号(包括SIGPIPE),GDB 会暂停程序的执行,并把控制权交给你(调试者),让你决定是忽略该信号还是继续运行。
  • 线程切换信息:[Switching to Thread 0x41fc3a0 (LWP 19299)]
    • 由于 GDB 正在执行你输入的命令(看起来是在调用statvfs),但此时另一个线程(monitor_tcp_cli)突然收到了信号。GDB 为了展示错误发生的位置,自动切换到了那个出问题的线程上下文。
  • 蓝色文字部分:
    • The program received a signal in another thread while making a function call from GDB.
    • 这解释了为什么你原本想调用的函数(statvfs)没有返回结果。因为 GDB 在“帮”你调用函数时,程序里的其他线程“炸”了(收到了信号),导致 GDB 放弃了当前的函数调用评估。
    • Evaluation of the expression containing the function (malloc) will be abandoned.
    • 这里提到了malloc,说明你调用的statvfs内部或者 GDB 内部正在尝试分配内存,但因为信号中断,这个调用链被强行终止了。

3. 如何解决?

如果你不想让 GDB 在这种信号上停下来,或者想修复代码,有两种思路:

方法 A:在 GDB 中忽略该信号(临时调试用)
你可以告诉 GDB 不要捕获SIGPIPE,让程序按照操作系统的默认逻辑处理(或者程序自己处理)。在 GDB 中输入:

(gdb)handle SIGPIPE nostop noprint pass
  • nostop: 收到信号时不暂停程序。
  • noprint: 不打印提示信息。
  • pass: 把信号传递给程序处理(通常程序会忽略它或做错误处理)。

方法 B:修改代码(根本解决)
这是网络编程中的经典问题。在处理 TCP Socket 时,应该由程序自己处理SIGPIPE,而不是让它杀掉进程。

  1. 设置忽略 SIGPIPE(最简单):
    在程序初始化时(或main函数开头)加入:

    #include<signal.h>signal(SIGPIPE,SIG_IGN);

    这样,当write/send到断开的连接时,不会触发SIGPIPE信号,而是返回-1,并将errno设置为EPIPE。你需要检查send的返回值来处理错误。

  2. 使用MSG_NOSIGNAL标志(推荐):
    在调用send函数时,加上MSG_NOSIGNAL标志:

    send(sockfd,buf,len,MSG_NOSIGNAL);

    这样可以避免发送SIGPIPE信号,直接通过返回值报错。

总结:
截图显示的是你的程序在尝试向一个已经断开的 TCP 连接写数据,导致系统发出了“断开连接”的信号,GDB 捕获到这个信号后暂停了程序,并打断了你正在进行的手动函数调用。

(2) 另一个例子,管道破裂于continue

2、handle 定义 GDB 的信号拦截行为

handle 本质是:用 stop/print/pass 三个开关,为指定信号构建 GDB 拦截/转 发策略。
使用格式:

handle<信号范围><行为关键词>[<行为关键词>...]

参数由三部分组成:
操作对象(信号范围):指定要配置哪些信号。
拦截策略(GDB 行为):GDB 自己该怎么处理这个信号(停不停、打不打印)。
转发策略(程序行为):要不要把这个信号告诉正在调试的程序(pass/nopass)。

handle命令用于告诉 GDB 如何处理各种信号(Signal)。你可以针对某个信号设定一系列“关键词”来控制 GDB 的行为,比如是否暂停程序、是否打印提示、是否把信号传递给正在调试的程序。

核心参数

你提到的nostop、noprint、pass就是控制这些行为的开关。它们可以自由组合,具体含义如下:

关键词含义
stop收到该信号时,暂停程序,把控制权交给你调试。
nostop收到该信号时,不暂停程序,但 GDB 可能会打印一条提示信息。
print收到该信号时,在 GDB 中打印一条信息。
noprint收到该信号时,不打印任何信息。
pass允许程序看到这个信号。程序可以自行处理,也可能因未处理而终止。
nopass不允许程序看到这个信号。
ignore与nopass同义。
noignore与pass同义。

关于nopass与pass

你之前问到的nopass确实可以用,但正如我们分析过的,它会让程序“感觉不到”信号的存在,导致send/write等函数失败后,代码无法通过errno感知到EPIPE错误,从而陷入逻辑错乱。

而在信号调试场景中,pass才是让程序能“看到”信号并做出反应的关键。

常见的实际应用

在调试网络程序时,最常见的做法是将SIGPIPE的处理方式设置为nostop noprint pass,把SIGPIPE信号交给程序自己去处理(比如忽略它,或者检查send的返回值),而不是让 GDB 每次都中断下来。

信号范围的指定

除了直接指定单个信号,你还可以指定一个范围来处理多个信号,例如handle SIGIO-SIGKILL,或者使用all来设置所有信号。

与之前SIGPIPE案例的关联

对SIGPIPE来说,推荐在代码中用signal(SIGPIPE, SIG_IGN)或在send时加上MSG_NOSIGNAL来处理。GDB 中的handle SIGPIPE调试命令只是一个用来配合代码修改的临时手段,让你在调试时不会被信号中断打断。
按信号数值从小到大排序,并将“终止+核心”明确写为“终止 + Core Dump”后的完整表格如下:

3、常见的 signal

按信号数值从小到大排序,英文全称列去掉“Signal”后的完整表格如下:

值信号名称英文全称类别默认动作说明
1SIGHUPHang Up控制/定时类终止控制终端挂起(用户登出)或控制进程结束
2SIGINTInterrupt进程终止类终止从键盘发出的中断(通常由 Ctrl+C 触发)
3SIGQUITQuit进程终止类终止 + Core Dump从键盘发出的退出(通常由 Ctrl+\ 触发)
4SIGILLIllegal Instruction错误/异常类终止 + Core Dump执行了非法硬件指令
6SIGABRTAbort错误/异常类终止 + Core Dump由 abort() 函数触发的非正常终止
7SIGBUSBus Error错误/异常类终止 + Core Dump总线错误(如内存对齐问题)
8SIGFPEFloating-Point Exception错误/异常类终止 + Core Dump浮点运算异常(如除以零)
9SIGKILLKill进程终止类终止最强终止信号,无法被捕获、阻塞或忽略
10SIGUSR1User-defined 1控制/定时类终止留给用户自定义用途
11SIGSEGVSegmentation Violation错误/异常类终止 + Core Dump无效的内存引用(段错误)
12SIGUSR2User-defined 2控制/定时类终止留给用户自定义用途
13SIGPIPEPipe错误/异常类终止向没有读取端的管道或套接字写入数据
14SIGALRMAlarm控制/定时类终止来自 alarm() 函数的超时信号
15SIGTERMTerminate进程终止类终止标准的终止请求,程序可捕获并执行清理
17SIGCHLDChild控制/定时类忽略子进程结束或停止时通知父进程
18SIGCONTContinue停止/继续类继续让暂停的进程继续执行
19SIGSTOPStop停止/继续类停止无条件停止进程,同样无法被忽略
20SIGTSTPTerminal Stop停止/继续类停止从终端发出的停止信号(通常由 Ctrl+Z 触发)

4、gdb中的常用命令

gdb ./a.out # 启动调试
(gdb) break main # 在 main 函数下断点
(gdb) run # 运行程序
(gdb) next # 执行下一行(不进入函数)
(gdb) step # 执行下一行(进入函数)
(gdb) print x # 打印变量 x 的值
(gdb) continue # 继续运行到下一个断点
(gdb) backtrace # 看调用栈(程序在哪崩溃的)
(gdb) quit # 退出

二、调试Coredump

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询