GDB 观察点:追踪变量的读写变化
介绍 GDB 的写观察点、读观察点和读写观察点,并说明多线程程序中的线程限定、原子变量和常见限制。
断点根据代码位置暂停程序,观察点(watchpoint)则根据数据变化暂停程序。当变量的值在复杂代码路径中被意外修改时,观察点通常比逐行跟踪更有效。
一、准备测试程序
下面的程序使用两个线程修改同一个原子变量。使用原子变量是为了避免示例本身包含数据竞争;如果使用普通 int 在两个线程中同时读写,程序会产生未定义行为,不能作为正常的调试示例:
#include <atomic>
#include <thread>
std::atomic<int> g_value{0};
void ThreadFunc1() {
for (int i = 1; i < 100; ++i) {
if (i % 4 == 0) {
g_value.fetch_add(1, std::memory_order_relaxed);
}
}
}
void ThreadFunc2() {
for (int i = 1; i < 100; ++i) {
if (i % 2 == 0) {
g_value.fetch_add(1, std::memory_order_relaxed);
}
}
}
int main() {
std::thread first(ThreadFunc1);
std::thread second(ThreadFunc2);
first.join();
second.join();
}
编译时加入调试信息和线程库:
g++ -std=c++17 -g3 -O0 -pthread watchpoint.cpp -o watchpoint
二、写观察点 watch
启动程序并设置观察点:
(gdb) file ./watchpoint
(gdb) start
(gdb) watch g_value
(gdb) continue
当 g_value 的值发生变化时,GDB 会暂停程序并输出旧值和新值。watch 也可以缩写为 wa。
观察点通常依赖处理器硬件调试寄存器。硬件观察点的数量和可观察的数据大小有限,超过限制时 GDB 可能退化为软件观察点,或者报告无法设置观察点。
三、读观察点和读写观察点
3.1 读观察点
rwatch 可以在表达式被读取时暂停程序:
(gdb) rwatch g_value
3.2 读写观察点
awatch 会在表达式被读取或写入时暂停程序:
(gdb) awatch g_value
这两个命令也支持使用 rw 和 aw 作为缩写。读观察点的触发次数可能很多,适合在已经缩小范围后使用。
四、管理观察点
观察点和断点共用编号空间,可以使用下面的命令查看和管理:
(gdb) info breakpoints
(gdb) info watchpoints
(gdb) disable 2
(gdb) enable 2
(gdb) delete 2
可以给观察点设置条件。例如只在值达到某个范围时暂停:
(gdb) condition 2 g_value >= 10
条件表达式的编号需要替换成观察点实际编号。
五、只观察特定线程
GDB 会为每个线程分配一个调试器线程编号。先查看线程信息:
(gdb) info threads
输出中的编号不一定等于操作系统线程 ID。可以使用 thread 切换当前线程:
(gdb) thread 2
设置只在线程 2 中生效的观察点:
(gdb) watch g_value thread 2
多线程程序的执行顺序通常是不确定的,即使使用原子变量,观察点触发的线程和顺序也可能每次不同。调试时应关注状态变化是否合法,不要依赖某一次运行的具体顺序。
六、观察点不生效时的检查项
如果 GDB 无法观察某个变量,可以依次检查:
- 是否使用
-g生成了调试信息。 - 是否关闭或降低了优化级别,建议先使用
-O0。 - 变量是否已经离开作用域,或只存在于寄存器中。
- 硬件观察点数量是否已经耗尽。
- 观察的表达式是否包含复杂的临时对象或重载运算。
- 多线程程序是否把观察点限定到了错误的 GDB 线程编号。
如果调试目标是定位数据竞争,观察点只能帮助观察某次读写,不能替代线程分析工具。还应结合 ThreadSanitizer 等工具检查同步问题:
g++ -std=c++17 -g -O1 -fsanitize=thread -pthread watchpoint.cpp -o watchpoint-tsan
七、调试截图
下面是使用 watch、删除观察点以及限定线程观察的实验结果:



八、总结
watch追踪写入变化,rwatch追踪读取,awatch同时追踪读取和写入。- 观察点与断点共用编号,可以使用
info watchpoints、disable和delete管理。 - 使用
watch expr thread id可以将观察点限定到指定 GDB 线程。 - 调试多线程程序时应避免在示例中引入未定义的数据竞争。
- 观察点受调试信息、优化级别和硬件观察点数量限制。