I'm trying to develop a GDB python extension that defines a command that launches a new thread, in which the user can inspect an arbitrary type of variables. The skeleton of my python extension is this:
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()
As can be seen, I define a plot command here. When I try to debug the following program, GDB will hang if I:
#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;
}
The strangest thing is that, if I change this code to call procedure() without launching a thread, GDB never hangs (and the placeholder messages are still printed as I expect).
So far, I've tried to run this procedure with GDB versions 7.5.1 and 7.10, but I always experience the same behavior.
What am I doing wrong? Aren't daemon threads not supported by GDB? That doesn't seem to be in accordance with what the section 23.2.2.1 of the documentation is suggesting: GDB may not be thread safe, but I don't think it should hang after launching such a silly daemon thread.
From this blog post:
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.
The solution is to configure the thread signal handling behavior, so that only the GDB main thread gets notified by these signals:
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()
The pysigset package is required, and can be installed from pip (sudo pip install pysigset).