Search code examples
gdb

Programatically interrupt gdb run


I am debugging a program that is supposed to run continuously. It gives a terminal that accepts user input and reacts to it.

It's randomly segfaulting at startup and I want to debug it by spawning a run, waiting for 3 seconds, interrupting it and spawning another run until it segfaults. I tried something like:

(gdb) while (1)
 >run
 >shell sleep 3
 >interrupt
 >end

But this does not interrupt my application after 3 seconds. If instead I do something like:

(gdb) while (1)
 >run
 >end

The application runs in a loop but I have to keep pressing Ctrl+C to manually interrupt it and when it segfaults it automatically restarts so I can't debug it.

Is there a way to manually interrupt it until it segfaults? What is a good way to do this?


Solution

  • Place the following into a file called run_and_interrupt.py:

    import threading
    import time
    import os
    import signal
    
    # A thread calss which waits for DELAY seconds then sends SIGINT to
    # process PID.
    class interrupting_thread(threading.Thread):
        def __init__(self, delay, pid):
            threading.Thread.__init__(self)
            self.delay = delay
            self.pid = pid
    
        def run(self):
            time.sleep(self.delay)
            os.kill(self.pid, signal.SIGINT)
    
    # The last signal that stopped the inferior.
    last_stop_signal = "SIGINT"
    
    # Handle stop events.  Look for signal stops and record the signal
    # into the global LAST_STOP_SIGNAL.
    def stop_handler(event):
        global last_stop_signal
    
        if isinstance(event, gdb.SignalEvent):
            last_stop_signal = event.stop_signal
    
    # Register the stop event handler.
    gdb.events.stop.connect(stop_handler)
    
    class run_and_interrupt(gdb.Command):
        """run-and-interrupt ARGS
    
        Run the current inferior passing in ARGS.
        """
    
        def __init__(self):
            gdb.Command.__init__(self, "run-and-interrupt", gdb.COMMAND_RUNNING)
    
        def invoke(self, args, from_tty):
            global last_stop_signal
    
            while last_stop_signal == "SIGINT":
                gdb.execute(f"starti {args}")
                inf = gdb.selected_inferior()
                pid = inf.pid
    
                # Start a new thread.
                thr = interrupting_thread(3, pid)
                thr.start()
    
                gdb.execute("continue")
                thr.join()
    
    # Register the new command.
    run_and_interrupt()
    

    Then in a GDB session source run_and_interrupt.py.

    You now have a new GDB command run-and-interrupt which will start the current executable, wait 3 seconds, then send SIGINT to the inferior.

    The command does this repeatedly until the inferior doesn't stop with SIGINT.

    The Python code is a little rough, and can certainly be improved, but this should be a good starting point.