multithreadinggdbgdb-python

GDB hangs when multi-threaded python extension is used to debug multi threaded program


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:

  1. Put a breakpoint anywhere inside the procedure() thread (say, line 9, inside the while loop).
  2. Run the command plot after gdb hits the breakpoint.
  3. Run continue after that.
#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.


Solution

  • 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).