BUG: Late-timeout during finalize
This adds a script which reproduces this bug after a lot of iterations in gdb and lets us get a backtrace
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
# 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
|
||||
|
||||
Reference in New Issue
Block a user