watch命令无法直接监控共享库符号,需先用add-symbol-file加载调试信息并计算绝对地址;awatch可读写触发但开销大;多线程下应限定线程或锁定调度。

watch 命令只能监控局部/全局变量,不能直接监控共享库里的符号
当你在主程序里用 watch var_name,GDB 默认只认当前加载的可执行文件符号表里的变量。如果 var_name 定义在 .so 里(比如 libutils.so 中的 g_config_flag),GDB 启动时根本看不到它——不是命令没用,是符号压根没加载进来。
常见错误现象:watch g_config_flag 执行后提示 No symbol "g_config_flag" in current context,或者设成功了但程序运行时不触发断点。
- 必须先让 GDB 知道这个变量在哪:用
add-symbol-file手动加载共享库的调试信息 - 需要知道该变量在 .so 中的地址偏移:用
readelf -s libxxx.so | grep g_config_flag查其 value(通常是相对 .text 或 .data 段的偏移) - 还要知道该 .so 实际被加载到进程内存的基址:运行程序后用
info proc mappings或查/proc/PID/maps找对应 .so 行的起始地址(如7f8a2c000000) - 最终计算出绝对地址:
7f8a2c000000 + offset,再用watch *0x7f8a2c001234监控该内存位置
awatch 和 watch 对共享变量的行为差异很关键
watch 只在变量被写入时触发;awatch(access watchpoint)会在读或写时都中断。对共享变量尤其重要——很多 bug 是由另一个线程悄悄读取了过期值导致的,光盯写操作会漏掉。
但要注意:awatch 开销更大,尤其在高频读取的全局 flag 上,可能让程序慢得无法测试。实际中建议:
- 先用
watch确认变量是否被意外修改 - 若怀疑竞态,改用
awatch并配合thread apply all bt快速定位是哪个线程在访问 - 避免对结构体整体设
awatch,应聚焦具体字段,否则极易误触发
多线程下 watch 共享变量必须配合线程控制
默认情况下,GDB 的 watchpoint 是全局生效的,任意线程修改该变量都会中断。但中断后你看到的栈帧可能是随机线程的,容易误判。
更可靠的做法是限制观察范围:
- 用
thread apply 3 watch g_counter只在 3 号线程里启用观察(需先thread 3切过去) - 用
set scheduler-locking on锁定当前线程调度,防止中断时其他线程干扰状态 - 检查是否所有线程都链接了带调试信息的 .so:
info sharedlibrary里要能看到libxxx.so后面标着yes,否则add-symbol-file也白搭
动态加载的共享库变量要等 dlopen 后再设 watch
如果变量所在 .so 是运行时通过 dlopen("libxxx.so", RTLD_GLOBAL) 加载的,GDB 在启动时完全不知道它的存在。这时不能一上来就 watch,必须等 dlopen 返回后手动补符号。
操作顺序:
- 在
dlopen调用前设断点:b dlopen -
r运行,停住后n单步过掉dlopen - 用
info proc mappings找新加载的 .so 地址,再用add-symbol-file ./libxxx.so 0x7f8a2c000000 - 此时才能安全地
watch libxxx.so::g_shared_state(注意双冒号语法)
最容易被忽略的是:即使加了 -g 编译 .so,若链接时用了 -Wl,-z,defs 或 strip 过,调试符号也会丢失——file libxxx.so 输出里不能有 stripped 字样。











