Files

124 lines
4.4 KiB
Plaintext
Raw Permalink Normal View History

2025-10-30 22:18:33 -04:00
# 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