如何无源码修改通过GDB单线程暂停自动复现多线程竞态条件
竞态条件自动化复现方案
GDB 原生支持你需要的调度控制能力
你设想的逻辑完全可以通过GDB的非停止模式实现,不需要修改源码,也不需要手动交互,可直接作为测试脚本运行。
你提到的set scheduler-locking = on是「仅运行当前选中线程,暂停其余所有线程」,而你需要的「仅暂停命中断点的写线程1,其余线程全部继续运行」是GDB非停止模式的默认行为,不需要反向配置。
可直接使用如下无交互GDB脚本:
# 开启非停止模式:仅断点命中的线程会被暂停,其余线程不受影响继续调度 set non-stop on # 关闭交互分页,避免脚本阻塞 set pagination off # 加载目标二进制程序 file ./your_target_binary # 在线程1执行写入内存地址A的指令位置下断点 # 可直接写指令地址:break *0x401589,也可写 函数名+偏移:break write_func+27 break *<线程1写地址A的指令位置> # 配置断点触发后自动执行的逻辑 commands # 向地址A写入非法垃圾值,比如构造不可访问的指针值触发段错误 set *(uint64_t*)<内存地址A> = 0xdeadbeefdeadbeef # 保持当前线程(线程1)暂停,继续运行其余所有线程 continue -a end # 捕获段错误信号,确认竞态触发成功 catch signal SIGSEGV commands print "竞态复现成功:读线程读取到垃圾数据触发段错误" quit 1 end # 启动程序 run
脚本运行直接执行gdb -x race_trigger.gdb -batch即可,全程无人工操作,退出码1代表复现成功,0代表未触发,可直接接入集成测试流程。
如果目标程序开启了ASLR,不需要修改程序,可在脚本中先启动程序停在入口点,自动通过/proc/<pid>/maps计算堆、栈、动态库的实际基址,再换算出断点位置和地址A的实际内存地址即可。
非GDB轻量方案(更适合CI集成)
如果不想引入GDB依赖,可选择以下两种无源码修改的方案:
- 基于
ptrace实现轻量注入脚本:用Python的python-ptrace库可以实现和上述GDB逻辑完全一致的效果,代码量不超过100行,运行开销远低于GDB。核心逻辑为:启动目标进程后在对应指令位置写入软断点指令,命中断点后仅将对应线程置为TASK_STOPPED状态,修改地址A的内容为垃圾值后,其余线程继续运行,监听进程收到的SIGSEGV信号即可判断复现成功。 - 动态插桩注入:如果目标程序逻辑复杂、断点位置不好定位,可使用动态插桩框架在写入地址A的指令位置插入探针,探针触发时阻塞当前写线程、修改目标内存值后放行其余线程,稳定性更高。
为了保证100%复现率,可在放行其余线程后,临时将读线程2的调度优先级设置为最高,避免系统调度波动导致读线程未及时读取垃圾值,完全消除测试flaky问题。
内容的提问来源于stack exchange,提问作者Yunus King
相关产品推荐
相关产品推荐

