# GDB command file for reproducing UdpCommandDemuxer heisenbug # This script runs salmanoff, waits a random time, sends SIGINT, and catches segfaults # Disable pager so output doesn't pause for user input set pagination off # Set up signal handling - catch segfaults and stop handle SIGSEGV stop print # Allow SIGINT to pass through to program silently - make it unremarkable # nostop: don't stop execution, noprint: don't print message, pass: pass to program handle SIGINT nostop noprint pass # Use Python to set up automatic handling of stop events and SIGINT injection python import time import random import threading import os import signal sigint_thread_started = False def send_sigint_after_delay(): # Wait random milliseconds between 2000-3000 delay_ms = random.randint(2000, 3000) print(f"Waiting {delay_ms}ms before sending SIGINT...") time.sleep(delay_ms / 1000.0) # Send SIGINT directly to the process using its PID # This works even when the program is running (unlike gdb.execute("signal SIGINT")) try: inferior = gdb.selected_inferior() if inferior and inferior.is_valid(): pid = inferior.pid print(f"Sending SIGINT to program (PID: {pid})...") os.kill(pid, signal.SIGINT) else: print("Program is not running - cannot send SIGINT") except Exception as e: print(f"Failed to send SIGINT: {e}") def start_sigint_thread(): global sigint_thread_started if not sigint_thread_started: sigint_thread_started = True thread = threading.Thread(target=send_sigint_after_delay, daemon=True) thread.start() # Hook to check stop reason and handle segfaults def stop_handler(event): if isinstance(event, gdb.SignalEvent): if event.stop_signal == "SIGSEGV": # Segfault detected print("\n=== SEGFAULT DETECTED ===") gdb.execute("bt") print("\n=== GDB is now interactive - you can inspect the state ===") # Don't quit - stay in interactive mode elif event.stop_signal == "SIGINT": # SIGINT received - with "nostop pass", SIGINT should pass through automatically # But if we get here (shouldn't happen with nostop), just let it pass pass elif isinstance(event, gdb.ExitedEvent): # Program exited normally if event.exit_code == 0: print("\nProgram exited normally. Continuing loop...") gdb.post_event(lambda: gdb.execute("quit", False)) else: print(f"\nProgram exited with code {event.exit_code}") gdb.post_event(lambda: gdb.execute("quit", False)) # Hook for when program continues/starts running def cont_handler(event): # When program continues (or starts running), start the SIGINT thread start_sigint_thread() # Register event handlers gdb.events.stop.connect(stop_handler) gdb.events.cont.connect(cont_handler) # Start SIGINT thread before running - it will wait and then send SIGINT # The thread will send SIGINT even if program is stopped (signal will be delivered on continue) start_sigint_thread() end # Start the program echo Starting program...\n run # After run completes, check if program exited or stopped # If program exited, quit GDB. If program stopped (has threads), continue. python try: inferior = gdb.selected_inferior() if inferior and inferior.is_valid(): # Check if there are threads (indicates program has not exited) try: threads = inferior.threads() if threads: # Program has threads - continue execution # SIGINT thread is already running and will send signal when ready gdb.execute("continue", False) else: # No threads - program has exited print("\nProgram has exited (no threads found).") gdb.execute("quit", False) except Exception as e: # If we can't check threads, assume program exited print(f"\nError checking threads: {e}") print("Assuming program exited.") gdb.execute("quit", False) else: # Inferior is not valid - program has exited print("\nProgram has exited (inferior not valid).") gdb.execute("quit", False) except Exception as e: print(f"Error checking program state: {e}") # If we can't determine state, try to quit try: gdb.execute("quit", False) except: pass end