fix problem in close program and kill gdb

This commit is contained in:
VALLONGOL 2025-05-22 15:54:32 +02:00
parent edec4eda3e
commit 4319b21559

View File

@ -313,92 +313,135 @@ class GDBSession:
def kill_program(self, timeout: int = DEFAULT_GDB_OPERATION_TIMEOUT) -> str:
logger.info(f"Sending 'kill' command to GDB with timeout {timeout}s.")
try:
return self.send_cmd("kill", expect_prompt=True, timeout=timeout)
except (TimeoutError, wexpect.EOF) as e: # EOF can happen if kill makes GDB quit or target was not running
logger.warning(f"Exception during 'kill' (might be normal if program not running or GDB exits): {e}")
return f"<kill_command_exception: {e}>"
except ConnectionError: # If GDB session died before kill
full_output = ""
if not self.child or not self.child.isalive():
logger.warning("Cannot send 'kill', GDB session not active.")
return "<kill_command_error_no_session>"
except Exception as e: # Other unexpected errors
logger.error(f"Unexpected error during 'kill': {e}", exc_info=True)
return f"<kill_command_unexpected_error: {e}>"
try:
self.child.sendline("kill")
full_output += "kill\n"
# Patterns for expect_list: 0=confirmation, 1=prompt, 2=EOF, 3=TIMEOUT
# Using re.compile for robustness with expect_list
patterns = [
re.compile(r"Kill the program being debugged\s*\?\s*\(y or n\)\s*"),
re.compile(re.escape(self.gdb_prompt)),
wexpect.EOF, # Questa è una costante, non una regex
wexpect.TIMEOUT # Questa è una costante
]
confirmation_timeout = max(5, timeout // 2)
logger.debug(f"Kill: Expecting confirmation or prompt with timeout {confirmation_timeout}s")
index = self.child.expect_list(patterns, timeout=confirmation_timeout)
output_segment = self.child.before if hasattr(self.child, 'before') else ""
full_output += output_segment
if index == 0:
logger.info("Kill: GDB asked for kill confirmation. Sending 'y'.")
self.child.sendline("y")
full_output += "y\n"
# Wait for the final prompt after 'y'
logger.debug(f"Kill: Expecting GDB prompt after 'y' with timeout {confirmation_timeout}s")
self.child.expect_exact(self.gdb_prompt, timeout=confirmation_timeout)
output_segment_after_y = self.child.before if hasattr(self.child, 'before') else ""
full_output += output_segment_after_y
logger.info("Kill: Kill confirmed and acknowledged by GDB.")
elif index == 1:
logger.info("Kill: GDB returned to prompt after 'kill' (program likely not running or no confirmation needed).")
elif index == 2:
logger.warning("Kill: GDB exited (EOF) during 'kill' command/confirmation.")
self.child = None
# Non sollevare EOF qui, ma segnala l'output
full_output += "<EOF reached during kill>"
elif index == 3:
logger.error(f"Kill: Timeout waiting for kill confirmation or prompt. Output so far: {output_segment.strip()}")
full_output += "<Timeout during kill confirmation>"
return full_output.strip()
except (TimeoutError, wexpect.EOF, ConnectionError) as e:
logger.warning(f"Kill: Exception during 'kill' (detail: {type(e).__name__} - {e}). Output: {full_output.strip()}")
return f"<kill_command_exception: {type(e).__name__} - {e}. Output: {full_output.strip()}>"
except Exception as e:
logger.error(f"Kill: Unexpected error during 'kill': {e}. Output: {full_output.strip()}", exc_info=True)
return f"<kill_command_unexpected_error: {e}. Output: {full_output.strip()}>"
def quit(self, timeout: int = DEFAULT_GDB_OPERATION_TIMEOUT) -> None:
"""
Sends 'quit' and 'y' (if needed) to GDB and closes the connection.
Uses the debugged version of quit.
Args:
timeout: Timeout for the quit command sequence.
"""
if self.child and self.child.isalive():
logger.info(f"Attempting GDB quit sequence with timeout {timeout}s for phases.")
quit_ack_timeout = max(2, timeout // 3) # Timeout for "Quit anyway?" and for final exit check
logger.info(f"Attempting GDB quit sequence with overall timeout {timeout}s.")
phase_timeout = max(3, timeout // 3)
try:
self.child.sendline("quit")
logger.debug("Sent 'quit' command to GDB.")
logger.debug("Quit: Sent 'quit' command to GDB.")
# Check for "Quit anyway? (y or n)"
# We need to be careful with expect here, as it might consume the final prompt if GDB quits immediately.
# A short, non-blocking read or a carefully crafted expect might be better.
# For simplicity, let's use expect with a short timeout for the confirmation.
try:
# Expect either the prompt (if quit fails or is rejected by GDB for some reason),
# the confirmation, EOF, or timeout.
patterns = [self.gdb_prompt, r"Quit anyway\? \(y or n\) ", wexpect.EOF, wexpect.TIMEOUT]
index = self.child.expect_list(patterns, timeout=quit_ack_timeout)
response_after_quit = self.child.before if hasattr(self.child, 'before') else ""
logger.debug(f"GDB response after 'quit' (index {index}): {response_after_quit!r}")
# Patterns for expect_list. EOF and TIMEOUT are special constants.
# Regexes should be pre-compiled for reliability with expect_list if issues persist.
expect_patterns_quit = [
re.compile(re.escape(self.gdb_prompt)), # 0: Prompt GDB
re.compile(r"Quit anyway\s*\?\s*\(y or n\)\s*"), # 1: Conferma "Quit anyway?"
wexpect.EOF, # 2: EOF
wexpect.TIMEOUT # 3: TIMEOUT
]
if index == 1: # "Quit anyway?" matched
logger.info("GDB asked for quit confirmation. Sending 'y'.")
self.child.sendline("y")
# Wait for GDB to process 'y' and exit.
# Expect EOF or timeout. Prompt means quit failed.
try:
final_index = self.child.expect_exact([self.gdb_prompt, wexpect.EOF, wexpect.TIMEOUT], timeout=quit_ack_timeout)
final_response = self.child.before if hasattr(self.child, 'before') else ""
logger.debug(f"GDB response after 'y' (index {final_index}): {final_response!r}")
if final_index == 0: # Prompt again
logger.warning("GDB did not quit after 'y' confirmation.")
except wexpect.TIMEOUT:
logger.info("Timeout waiting for GDB to exit after 'y'. Assuming exited or hung.")
except wexpect.EOF:
logger.info("GDB exited after 'y' confirmation (EOF received).")
elif index == 0: # Prompt
logger.warning("GDB did not quit and returned to prompt. Target might still be running or an error occurred.")
elif index == 2: # EOF
logger.info("GDB exited immediately after 'quit' command (EOF received).")
elif index == 3: # Timeout
logger.warning("Timeout waiting for GDB response after 'quit' command. GDB might be hung or exited cleanly without EOF.")
except wexpect.TIMEOUT:
logger.warning("Timeout waiting for GDB response after initial 'quit' command. Assuming GDB exited or hung.")
except wexpect.EOF:
logger.info("GDB exited (EOF) after initial 'quit' command (no confirmation needed).")
logger.debug(f"Quit: Expecting one of the patterns with timeout {phase_timeout}s")
index = self.child.expect_list(expect_patterns_quit, timeout=phase_timeout)
# Final check and cleanup
time.sleep(0.5) # Brief pause to allow process to terminate fully
if self.child and self.child.isalive():
logger.warning("GDB process is still alive after quit sequence. Attempting forceful close.")
self.child.close(force=True) # Use force=True as a last resort
else:
logger.info("GDB process appears to have exited successfully.")
response_after_quit = self.child.before if hasattr(self.child, 'before') else ""
logger.debug(f"Quit: GDB response after 'quit' (index {index}): {response_after_quit!r}")
if index == 1: # "Quit anyway?" matched
logger.info("Quit: GDB asked for quit confirmation. Sending 'y'.")
self.child.sendline("y")
try:
# After 'y', expect EOF or TIMEOUT (if GDB hangs). Prompt means quit failed.
final_expect_patterns_y = [
re.compile(re.escape(self.gdb_prompt)), # 0
wexpect.EOF, # 1
wexpect.TIMEOUT # 2
]
final_index = self.child.expect_list(final_expect_patterns_y, timeout=phase_timeout)
final_response = self.child.before if hasattr(self.child, 'before') else ""
logger.debug(f"Quit: GDB response after 'y' (index {final_index}): {final_response!r}")
if final_index == 0:
logger.warning("Quit: GDB did not quit after 'y' confirmation and returned to prompt.")
elif final_index == 1:
logger.info("Quit: GDB exited after 'y' confirmation (EOF received).")
elif final_index == 2:
logger.info("Quit: Timeout waiting for GDB to exit after 'y'. Assuming exited or hung.")
except wexpect.TIMEOUT:
logger.info("Quit: Timeout (expecting EOF/Prompt) after 'y'. Assuming GDB exited or hung.")
except wexpect.EOF:
logger.info("Quit: GDB exited (EOF expecting EOF/Prompt) after 'y' confirmation.")
elif index == 0:
logger.warning("Quit: GDB did not quit (returned to prompt, no confirmation asked).")
elif index == 2:
logger.info("Quit: GDB exited immediately after 'quit' command (EOF received, no confirmation).")
elif index == 3:
logger.warning("Quit: Timeout waiting for GDB response after 'quit' command (no confirmation). GDB might be hung or exited.")
except wexpect.TIMEOUT:
logger.warning("Quit: Timeout on initial expect after 'quit'. Assuming GDB exited or hung.")
except wexpect.EOF:
logger.info("Quit: EOF on initial expect after 'quit'. GDB exited.")
except Exception as e_quit_main:
logger.error(f"Exception during GDB quit sequence: {e_quit_main}", exc_info=True)
if self.child and self.child.isalive():
try: self.child.close(force=True)
except Exception as e_close_final: logger.error(f"Error during final GDB child close: {e_close_final}")
logger.error(f"Quit: Exception during GDB quit sequence: {e_quit_main}", exc_info=True)
finally:
if self.child and self.child.isalive():
logger.warning("Quit: GDB process is still alive after quit attempts. Closing connection.")
try:
self.child.close() # Rimosso force=True
except Exception as e_close_final:
logger.error(f"Quit: Error during final GDB child close: {e_close_final}", exc_info=True)
elif self.child and not self.child.isalive():
logger.info("Quit: GDB process was already not alive before final close call.")
self.child = None
self.gdb_script_sourced_successfully = False # Reset status
logger.info("GDB session resources (controller-side) released.")
self.gdb_script_sourced_successfully = False
logger.info("Quit: GDB session resources (controller-side) released.")
else:
logger.info("GDB session quit called, but no active child process.")
logger.info("Quit: GDB session quit called, but no active child process.")
def is_alive(self) -> bool:
return self.child is not None and self.child.isalive()