# 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

