使用多线程 python 扩展调试多线程程序时 GDB 挂起
GDB hangs when multi-threaded python extension is used to debug multi threaded program
我正在尝试开发一个 GDB python 扩展,它定义了一个启动新线程的命令,用户可以在其中检查任意类型的变量。我的 python 扩展的框架是这样的:
import gdb
import threading
def plot_thread():
import time
while True:
print('Placeholder for a window event loop.')
time.sleep(1)
pass
pass
class PlotterCommand(gdb.Command):
def __init__(self):
super(PlotterCommand, self).__init__("plot",
gdb.COMMAND_DATA,
gdb.COMPLETE_SYMBOL)
self.dont_repeat()
pass
def invoke(self, arg, from_tty):
plot_thread_instance=threading.Thread(target=plot_thread)
plot_thread_instance.daemon=True
plot_thread_instance.start()
pass
pass
PlotterCommand()
可以看出,我这里定义了一个plot命令。当我尝试调试以下程序时,如果我执行以下操作,GDB 将挂起:
- 在 procedure() 线程内的任意位置放置一个断点(例如,第 9 行,在 while 循环内)。
- 运行 命令plot gdb 命中断点后
- 运行 之后继续。
#include <iostream>
#include <thread>
using namespace std;
void procedure() {
cout << "before loop"<<endl;
while(1) {
cout << "loop iteration"<<endl;
}
}
int main() {
thread t(procedure);
t.join();
return 0;
}
最奇怪的是,如果我将此代码更改为在不启动线程的情况下调用 procedure(),GDB 永远不会挂起(占位符消息仍按我的预期打印).
到目前为止,我已经尝试 运行 使用 GDB 版本 7.5.1 和 7.10 执行此过程,但我总是遇到相同的行为。
我做错了什么? GDB 不支持守护线程吗?这似乎与 section 23.2.2.1 of the documentation 的建议不一致:GDB 可能不是线程安全的,但我不认为它应该在启动这样一个愚蠢的守护线程后挂起。
GDB uses this function (sigsuspend, the function where GDB hangs) to wait for new events from the application execution: when something occurs in the debuggee (see how debuggers work), the kernel will inform GDB of it by sending a SIGCHLD signal. When it's received, GDB awakes and check what happened.
However, the signal is delivered to GDB process, but not necessarily to its main thread. And it practise, it occurs often that it's delivered to the second thread, who doesn't care about it (that's the default behavior), and continues its life as if nothing occurred.
解决方案是配置线程信号处理行为,以便只有 GDB 主线程收到这些信号的通知:
import gdb
import threading
import pysigset, signal # Import these packages!
def plot_thread():
import time
while True:
print('Placeholder for a window event loop.')
time.sleep(1)
pass
pass
class PlotterCommand(gdb.Command):
def __init__(self):
super(PlotterCommand, self).__init__("plot",
gdb.COMMAND_DATA,
gdb.COMPLETE_SYMBOL)
self.dont_repeat()
pass
def invoke(self, arg, from_tty):
with pysigset.suspended_signals(signal.SIGCHLD): # Disable signals here!
plot_thread_instance=threading.Thread(target=plot_thread)
plot_thread_instance.daemon=True
plot_thread_instance.start()
pass
pass
pass
PlotterCommand()
pysigset 软件包是必需的,可以从 pip 安装(sudo pip install pysigset)。
我正在尝试开发一个 GDB python 扩展,它定义了一个启动新线程的命令,用户可以在其中检查任意类型的变量。我的 python 扩展的框架是这样的:
import gdb
import threading
def plot_thread():
import time
while True:
print('Placeholder for a window event loop.')
time.sleep(1)
pass
pass
class PlotterCommand(gdb.Command):
def __init__(self):
super(PlotterCommand, self).__init__("plot",
gdb.COMMAND_DATA,
gdb.COMPLETE_SYMBOL)
self.dont_repeat()
pass
def invoke(self, arg, from_tty):
plot_thread_instance=threading.Thread(target=plot_thread)
plot_thread_instance.daemon=True
plot_thread_instance.start()
pass
pass
PlotterCommand()
可以看出,我这里定义了一个plot命令。当我尝试调试以下程序时,如果我执行以下操作,GDB 将挂起:
- 在 procedure() 线程内的任意位置放置一个断点(例如,第 9 行,在 while 循环内)。
- 运行 命令plot gdb 命中断点后
- 运行 之后继续。
#include <iostream>
#include <thread>
using namespace std;
void procedure() {
cout << "before loop"<<endl;
while(1) {
cout << "loop iteration"<<endl;
}
}
int main() {
thread t(procedure);
t.join();
return 0;
}
最奇怪的是,如果我将此代码更改为在不启动线程的情况下调用 procedure(),GDB 永远不会挂起(占位符消息仍按我的预期打印).
到目前为止,我已经尝试 运行 使用 GDB 版本 7.5.1 和 7.10 执行此过程,但我总是遇到相同的行为。
我做错了什么? GDB 不支持守护线程吗?这似乎与 section 23.2.2.1 of the documentation 的建议不一致:GDB 可能不是线程安全的,但我不认为它应该在启动这样一个愚蠢的守护线程后挂起。
GDB uses this function (sigsuspend, the function where GDB hangs) to wait for new events from the application execution: when something occurs in the debuggee (see how debuggers work), the kernel will inform GDB of it by sending a SIGCHLD signal. When it's received, GDB awakes and check what happened.
However, the signal is delivered to GDB process, but not necessarily to its main thread. And it practise, it occurs often that it's delivered to the second thread, who doesn't care about it (that's the default behavior), and continues its life as if nothing occurred.
解决方案是配置线程信号处理行为,以便只有 GDB 主线程收到这些信号的通知:
import gdb
import threading
import pysigset, signal # Import these packages!
def plot_thread():
import time
while True:
print('Placeholder for a window event loop.')
time.sleep(1)
pass
pass
class PlotterCommand(gdb.Command):
def __init__(self):
super(PlotterCommand, self).__init__("plot",
gdb.COMMAND_DATA,
gdb.COMPLETE_SYMBOL)
self.dont_repeat()
pass
def invoke(self, arg, from_tty):
with pysigset.suspended_signals(signal.SIGCHLD): # Disable signals here!
plot_thread_instance=threading.Thread(target=plot_thread)
plot_thread_instance.daemon=True
plot_thread_instance.start()
pass
pass
pass
PlotterCommand()
pysigset 软件包是必需的,可以从 pip 安装(sudo pip install pysigset)。