如何在Linux下使用GDB释放Python线程Lock或Futex?
一、解决程序立即死锁无法调试的问题
程序启动后快速进入死锁,没法设置断点,有两种简单解决方案:
方法1:修改脚本增加调试窗口
在脚本的main函数末尾(线程start之后)添加暂停逻辑,给你足够时间附加GDB:
if __name__ == '__main__': print(os.getpid()) thread1 = threading.Thread(target=func1, args=['thread1',]) thread2 = threading.Thread(target=func2, args=['thread2',]) thread1.start() thread2.start() # 添加这行,程序会等待输入后再继续 input("Attach GDB now, then press Enter to proceed...")
运行脚本后,控制台会打印PID,此时程序暂停,你可以用sudo gdb -p <PID>附加调试。
方法2:用GDB启动程序并提前设断点
直接用GDB启动Python脚本,提前在死锁前的代码行设置断点:
gdb --args python3 m4.py
在GDB交互界面中,设置断点到func1尝试获取lock2之前的行(对应脚本第12行左右):
(gdb) b m4.py:12 (gdb) run
程序会在断点处暂停,此时你可以继续调试,或执行continue让程序运行到接近死锁的状态,再手动暂停(Ctrl+C)。
二、通过GDB解锁Futex的具体步骤
Python的threading.Lock底层基于Linux Futex实现,你可以直接通过GDB调用Futex系统调用唤醒阻塞线程,或操作Python Lock对象本身释放锁。
步骤1:定位Lock对应的Futex地址
附加GDB后,切换到主线程,用GDB的Python扩展获取全局lock1和lock2的底层Futex地址:
(gdb) thread 1 (gdb) python import gdb # 获取lock1的底层锁信息 lock1 = gdb.parse_and_eval("lock1") py_lock1 = lock1["_lock"] futex_lock1 = py_lock1["lock"] print(f"lock1 对应的Futex地址: {futex_lock1}") # 获取lock2的底层锁信息 lock2 = gdb.parse_and_eval("lock2") py_lock2 = lock2["_lock"] futex_lock2 = py_lock2["lock"] print(f"lock2 对应的Futex地址: {futex_lock2}") end
对比输出的地址和info threads中的futex_word,就能对应上哪个线程在等待哪个锁。
步骤2:调用Futex系统调用唤醒线程
在x86_64 Ubuntu 18.04中,SYS_futex的系统调用号是202,FUTEX_WAKE的操作码是1。调用该系统调用唤醒等待目标Futex的线程:
# 替换成你要解锁的Futex地址,比如lock2对应的地址0x1bc3fc0 (gdb) call syscall(202, 0x1bc3fc0, 1, 0, 0, 0, 0)
执行后,该Futex上阻塞的1个线程会被唤醒。
步骤3:验证解锁效果
执行continue让程序继续运行:
(gdb) continue
此时你会看到其中一个线程执行到DEADLOCK: This line will never run.的打印,说明死锁已被解除。
三、替代方案:直接调用Python Lock的release方法
如果你更倾向于操作Python对象本身,可以通过GDB的Python扩展调用Lock的release方法:
(gdb) thread 1 (gdb) python import gdb # 获取lock1对象并调用release lock1 = gdb.parse_and_eval("lock1") release_func = lock1["release"] # 调用release方法,传入self参数 gdb.execute(f"call ((void (*)(PyObject*)){release_func})((PyObject*){lock1})") end
此方法直接操作Python层面的锁,不需要关注底层Futex细节,但需要确保GDB的Python环境与目标程序版本兼容。
注意事项
- 所有操作仅用于个人研究,生产环境禁止使用此类暴力解锁手段,会破坏程序状态一致性。
- 调用
PyThread_release_lock时需传递PyThread_type_lock的地址(而非Futex地址),否则会触发段错误,正确参数可通过步骤1中的py_lock1获取。
内容的提问来源于stack exchange,提问作者Jacob Quisenberry

