add check if not present debug symbol
This commit is contained in:
parent
6310fb0ce6
commit
503b81ee22
@ -80,19 +80,31 @@ class GDBSession:
|
||||
command = f'"{self.gdb_path}" --nx --quiet "{self.executable_path}"'
|
||||
logger.info(f"Spawning GDB process: {command} with startup timeout: {timeout}s")
|
||||
try:
|
||||
self.child = wexpect.spawn(
|
||||
command, timeout=timeout, encoding="utf-8", errors="replace"
|
||||
)
|
||||
self.child.expect_exact(self.gdb_prompt, timeout=max(timeout, 15))
|
||||
# Aumentiamo leggermente il timeout per wexpect.spawn per dare a GDB il tempo di caricare i simboli (o fallire)
|
||||
# e stampare l'output prima del primo prompt.
|
||||
spawn_timeout = max(timeout, 5) # Assicurati almeno 5 secondi per lo spawn e il primo output
|
||||
self.child = wexpect.spawn(command, timeout=spawn_timeout, encoding='utf-8', errors='replace')
|
||||
|
||||
# Aspetta il primo prompt. L'output prima di questo prompt conterrà i messaggi di caricamento.
|
||||
self.child.expect_exact(self.gdb_prompt, timeout=max(timeout, 15)) # Timeout per il prompt
|
||||
output_at_startup = self.child.before if hasattr(self.child, 'before') else ""
|
||||
logger.debug(f"GDB output at startup (before first prompt):\n{output_at_startup}") # Logga l'output iniziale
|
||||
|
||||
# --- NUOVO: Controllo per assenza di simboli ---
|
||||
no_symbols_message = "No debugging symbols found"
|
||||
if no_symbols_message in output_at_startup:
|
||||
self.symbols_found = False
|
||||
logger.warning(f"'{no_symbols_message}' detected in GDB startup output. Debugging capabilities will be limited.")
|
||||
else:
|
||||
self.symbols_found = True
|
||||
logger.info("Debugging symbols appear to be loaded (no 'No debugging symbols found' message detected).")
|
||||
# --- FINE NUOVO CONTROLLO ---
|
||||
|
||||
logger.info("GDB started successfully and prompt received.")
|
||||
|
||||
pagination_timeout = max(5, timeout // 2)
|
||||
logger.info(
|
||||
f"Disabling GDB pagination ('set pagination off') with timeout: {pagination_timeout}s."
|
||||
)
|
||||
self.send_cmd(
|
||||
"set pagination off", expect_prompt=True, timeout=pagination_timeout
|
||||
)
|
||||
logger.info(f"Disabling GDB pagination ('set pagination off') with timeout: {pagination_timeout}s.")
|
||||
self.send_cmd("set pagination off", expect_prompt=True, timeout=pagination_timeout)
|
||||
logger.info("GDB pagination disabled.")
|
||||
|
||||
if self.gdb_script_path:
|
||||
@ -102,36 +114,27 @@ class GDBSession:
|
||||
logger.info("No GDB dumper script path provided; skipping sourcing.")
|
||||
self.gdb_script_sourced_successfully = False
|
||||
|
||||
except wexpect.TIMEOUT as e_timeout: # Specific wexpect Timeout
|
||||
error_msg = (
|
||||
f"Timeout ({timeout}s) waiting for GDB prompt or during GDB startup."
|
||||
)
|
||||
except wexpect.TIMEOUT as e_timeout:
|
||||
error_msg = f"Timeout ({timeout}s) waiting for GDB prompt or during GDB startup."
|
||||
logger.error(error_msg)
|
||||
debug_output = "<no output captured>"
|
||||
try:
|
||||
if self.child:
|
||||
debug_output = self.child.read_nonblocking(size=2048, timeout=1)
|
||||
except Exception:
|
||||
pass
|
||||
logger.error(f"GDB output before timeout (if any): {debug_output}")
|
||||
if self.child and self.child.isalive():
|
||||
self.child.close()
|
||||
if self.child: debug_output = self.child.read_nonblocking(size=2048, timeout=1)
|
||||
# Aggiungiamo anche l'output catturato da `before` se disponibile
|
||||
if hasattr(e_timeout, 'value') and isinstance(e_timeout.value, str): # wexpect.TIMEOUT può avere 'value'
|
||||
debug_output += "\nOutput before timeout (from exception value):\n" + e_timeout.value
|
||||
elif output_at_startup: # Se abbiamo catturato output_at_startup prima del timeout del prompt
|
||||
debug_output += "\nOutput at startup before timeout:\n" + output_at_startup
|
||||
|
||||
except Exception: pass
|
||||
logger.error(f"GDB output details before timeout: {debug_output}")
|
||||
if self.child and self.child.isalive(): self.child.close()
|
||||
self.child = None
|
||||
raise TimeoutError(
|
||||
error_msg
|
||||
) from e_timeout # Riconverti in TimeoutError standard
|
||||
except Exception as e: # QUALSIASI ALTRA ECCEZIONE
|
||||
# MODIFIED: Log dell'eccezione originale in modo più dettagliato
|
||||
logger.error(
|
||||
f"!!! Unexpected exception in GDBSession.start(): {type(e).__name__}: {e}",
|
||||
exc_info=True,
|
||||
)
|
||||
# logger.error(f"!!! Type of e: {type(e)}") # Aggiungi questo per vedere il tipo esatto
|
||||
# logger.error(f"!!! Args of e: {e.args}") # E i suoi argomenti
|
||||
if self.child and self.child.isalive():
|
||||
self.child.close()
|
||||
raise TimeoutError(error_msg) from e_timeout
|
||||
except Exception as e:
|
||||
logger.error(f"!!! Unexpected exception in GDBSession.start(): {type(e).__name__}: {e}", exc_info=True)
|
||||
if self.child and self.child.isalive(): self.child.close()
|
||||
self.child = None
|
||||
# Rilancia l'eccezione originale per vedere se il messaggio cambia in main_window
|
||||
raise # RILANCIA L'ECCEZIONE ORIGINALE
|
||||
|
||||
def _set_gdb_dumper_variables(
|
||||
|
||||
@ -266,18 +266,16 @@ class ProfileExecutor:
|
||||
"end_time": None,
|
||||
"status": "Initialized",
|
||||
"actions_summary": [
|
||||
{
|
||||
"action_index": i,
|
||||
"breakpoint_spec": action.get("breakpoint_location", "N/A"),
|
||||
"gdb_bp_num_assigned": None, # Will be filled
|
||||
"address_resolved": None, # Will be filled
|
||||
"variables_dumped_count": 0,
|
||||
"status": "Pending",
|
||||
}
|
||||
{"action_index": i,
|
||||
"breakpoint_spec": action.get("breakpoint_location", "N/A"),
|
||||
"gdb_bp_num_assigned": None,
|
||||
"address_resolved": None,
|
||||
"variables_dumped_count": 0,
|
||||
"status": "Pending"}
|
||||
for i, action in enumerate(self.profile.get("actions", []))
|
||||
],
|
||||
"execution_log": [],
|
||||
"files_produced_detailed": [],
|
||||
"files_produced_detailed": []
|
||||
}
|
||||
|
||||
gdb_exe = self._get_setting("general", "gdb_executable_path")
|
||||
@ -289,508 +287,113 @@ class ProfileExecutor:
|
||||
self._log_event(msg, True)
|
||||
self.profile_execution_summary["status"] = "Error: Target not found"
|
||||
self.is_running = False
|
||||
self._finalize_summary_report(None)
|
||||
self._finalize_summary_report(None) # Passa None perché potremmo non avere un output_path
|
||||
return
|
||||
|
||||
actions = self.profile.get("actions", [])
|
||||
if not actions:
|
||||
self._log_event(
|
||||
f"Profile '{profile_name}' has no actions defined. Stopping.", True
|
||||
)
|
||||
self._log_event(f"Profile '{profile_name}' has no actions defined. Stopping.", True)
|
||||
self.profile_execution_summary["status"] = "Error: No actions"
|
||||
self.is_running = False # Assicura che is_running sia False
|
||||
self._finalize_summary_report(None)
|
||||
return
|
||||
|
||||
first_action_output_dir_base = actions[0].get("output_directory", ".")
|
||||
self.current_run_output_path = self._prepare_output_directory(
|
||||
first_action_output_dir_base, profile_name
|
||||
)
|
||||
|
||||
if not self.current_run_output_path:
|
||||
self.profile_execution_summary["status"] = (
|
||||
"Error: Cannot create output directory"
|
||||
)
|
||||
self.is_running = False
|
||||
self._finalize_summary_report(None)
|
||||
return
|
||||
# Prepara la directory di output solo se procediamo
|
||||
# Lo spostiamo dopo il controllo dei simboli per evitare di creare cartelle inutilmente.
|
||||
# self.current_run_output_path = self._prepare_output_directory(...)
|
||||
|
||||
try:
|
||||
self.gdb_session = GDBSession(
|
||||
gdb_path=gdb_exe,
|
||||
executable_path=target_exe,
|
||||
gdb_script_full_path=gdb_script_path,
|
||||
dumper_options=self._get_dumper_options(),
|
||||
gdb_path=gdb_exe, executable_path=target_exe,
|
||||
gdb_script_full_path=gdb_script_path, dumper_options=self._get_dumper_options()
|
||||
)
|
||||
startup_timeout = self._get_setting("timeouts", "gdb_start", 30)
|
||||
self._log_event(
|
||||
f"Spawning GDB for '{os.path.basename(target_exe)}'...", True
|
||||
)
|
||||
self._log_event(f"Spawning GDB for '{os.path.basename(target_exe)}'...", True)
|
||||
self.gdb_session.start(timeout=startup_timeout)
|
||||
self.gdb_output_writer(
|
||||
f"GDB session started for profile '{profile_name}'.\n"
|
||||
)
|
||||
self._log_event("GDB session started.", False)
|
||||
# Non loggare "GDB session started" qui, lo faremo dopo il check dei simboli.
|
||||
|
||||
# --- NUOVO: Controllo simboli e interruzione se necessario ---
|
||||
if not self.gdb_session.symbols_found:
|
||||
msg = (f"Error for profile '{profile_name}': No debugging symbols found in "
|
||||
f"'{os.path.basename(target_exe)}'. Profile execution aborted.")
|
||||
self._log_event(msg, True) # Invia alla GUI
|
||||
self.profile_execution_summary["status"] = "Error: No Debug Symbols"
|
||||
self.profile_execution_summary["end_time"] = datetime.now().isoformat() # Imposta end_time
|
||||
self.profile_execution_summary["execution_log"] = self.execution_event_log # Salva log eventi finora
|
||||
|
||||
# Prepara la directory di output (anche se l'esecuzione è abortita, per salvare il sommario)
|
||||
# Se `actions` non è vuoto, prendi la dir dalla prima azione, altrimenti usa un default.
|
||||
base_output_dir = "."
|
||||
if actions and "output_directory" in actions[0]:
|
||||
base_output_dir = actions[0].get("output_directory", ".")
|
||||
self.current_run_output_path = self._prepare_output_directory(base_output_dir, profile_name)
|
||||
# Se current_run_output_path è None (fallimento creazione dir), _finalize_summary_report lo gestirà.
|
||||
|
||||
self._cleanup_session() # Chiudi GDB
|
||||
self._finalize_summary_report(self.current_run_output_path) # Salva sommario
|
||||
self.is_running = False
|
||||
return # Interrompi l'esecuzione del profilo
|
||||
# --- FINE NUOVO CONTROLLO SIMBOLI ---
|
||||
|
||||
self._log_event("GDB session started.", False) # Ora logghiamo dopo il check dei simboli
|
||||
if gdb_script_path and self.gdb_session.gdb_script_sourced_successfully:
|
||||
self._log_event("GDB dumper script sourced successfully.", False)
|
||||
elif gdb_script_path:
|
||||
self._log_event("Warning: GDB dumper script failed to load.", False)
|
||||
self._log_event("GDB dumper script sourced successfully.", False)
|
||||
elif gdb_script_path: # Implica fallimento sourcing
|
||||
self._log_event(f"Warning: GDB dumper script '{os.path.basename(gdb_script_path)}' failed to load.", False)
|
||||
# Potremmo anche considerare di interrompere qui se lo script è critico, ma per ora solo un warning.
|
||||
|
||||
# --- PHASE 1: Set all breakpoints and build maps ---
|
||||
cmd_timeout = self._get_setting("timeouts", "gdb_command", 30)
|
||||
num_successfully_mapped_breakpoints = 0
|
||||
for action_idx, action_config in enumerate(actions):
|
||||
if self._stop_requested:
|
||||
break
|
||||
bp_spec = action_config.get("breakpoint_location")
|
||||
action_summary = self.profile_execution_summary["actions_summary"][
|
||||
action_idx
|
||||
]
|
||||
# Prepara la directory di output ora che sappiamo che i simboli ci sono (o si procede comunque)
|
||||
first_action_output_dir_base = actions[0].get("output_directory", ".")
|
||||
self.current_run_output_path = self._prepare_output_directory(first_action_output_dir_base, profile_name)
|
||||
|
||||
if not bp_spec:
|
||||
self._log_event(
|
||||
f"Action {action_idx + 1}: No breakpoint location. Skipping.",
|
||||
False,
|
||||
)
|
||||
action_summary["status"] = "Skipped (No BP Spec)"
|
||||
continue
|
||||
|
||||
self._log_event(
|
||||
f"Setting BP for Action {action_idx + 1} ('{bp_spec}')...", False
|
||||
)
|
||||
bp_set_output = self.gdb_session.set_breakpoint(
|
||||
bp_spec, timeout=cmd_timeout
|
||||
)
|
||||
self.gdb_output_writer(bp_set_output)
|
||||
|
||||
parsed_bp_info = self._parse_gdb_set_breakpoint_output(bp_set_output)
|
||||
if parsed_bp_info:
|
||||
gdb_bp_num, address_str = parsed_bp_info
|
||||
action_summary["gdb_bp_num_assigned"] = gdb_bp_num
|
||||
action_summary["address_resolved"] = address_str
|
||||
|
||||
self.gdb_bp_num_to_details_map[gdb_bp_num] = {
|
||||
"address": address_str,
|
||||
"action_index": action_idx,
|
||||
"bp_spec": bp_spec,
|
||||
}
|
||||
if (
|
||||
address_str != "pending"
|
||||
): # Only map non-pending to address map for execution
|
||||
if address_str not in self.address_to_action_indices_map:
|
||||
self.address_to_action_indices_map[address_str] = []
|
||||
if (
|
||||
action_idx
|
||||
not in self.address_to_action_indices_map[address_str]
|
||||
): # Avoid duplicates if GDB maps same BP spec multiple times (unlikely)
|
||||
self.address_to_action_indices_map[address_str].append(
|
||||
action_idx
|
||||
)
|
||||
self._log_event(
|
||||
f"Action {action_idx+1} ('{bp_spec}'): GDB BP {gdb_bp_num} at {address_str}.",
|
||||
False,
|
||||
)
|
||||
num_successfully_mapped_breakpoints += 1
|
||||
else:
|
||||
self._log_event(
|
||||
f"Action {action_idx+1} ('{bp_spec}'): GDB BP {gdb_bp_num} is PENDING. Will not trigger until resolved.",
|
||||
False,
|
||||
)
|
||||
action_summary["status"] = "Pending in GDB"
|
||||
else:
|
||||
self._log_event(
|
||||
f"Error: Action {action_idx + 1}: Failed to parse GDB BP info for '{bp_spec}'. Output: {bp_set_output[:100]}",
|
||||
True,
|
||||
)
|
||||
action_summary["status"] = "Error (BP Set/Parse)"
|
||||
|
||||
if self._stop_requested:
|
||||
raise InterruptedError("User requested stop during BP setup.")
|
||||
if num_successfully_mapped_breakpoints == 0:
|
||||
self._log_event(
|
||||
"No non-pending breakpoints successfully mapped. Aborting profile.",
|
||||
True,
|
||||
)
|
||||
self.profile_execution_summary["status"] = "Error: No BPs Mapped"
|
||||
self._cleanup_session() # Ensure GDB is closed
|
||||
self._finalize_summary_report(self.current_run_output_path)
|
||||
if not self.current_run_output_path: # Se la creazione della directory fallisce
|
||||
self.profile_execution_summary["status"] = "Error: Cannot create output directory"
|
||||
self.profile_execution_summary["end_time"] = datetime.now().isoformat()
|
||||
self._cleanup_session()
|
||||
self._finalize_summary_report(self.current_run_output_path) # Prova a salvare il sommario se possibile
|
||||
self.is_running = False
|
||||
return
|
||||
|
||||
# --- PHASE 2: Run program and handle breakpoint hits ---
|
||||
program_params = self.profile.get("program_parameters", "")
|
||||
self._log_event(
|
||||
f"Running program '{os.path.basename(target_exe)} {program_params}'...",
|
||||
True,
|
||||
)
|
||||
run_timeout = self._get_setting("timeouts", "program_run_continue", 120)
|
||||
gdb_output = self.gdb_session.run_program(
|
||||
program_params, timeout=run_timeout
|
||||
)
|
||||
self.gdb_output_writer(gdb_output)
|
||||
# --- FASE 1: Imposta tutti i breakpoint e costruisci le mappe ---
|
||||
# ... (resto della logica per impostare BP, eseguire, ecc.)
|
||||
cmd_timeout = self._get_setting("timeouts", "gdb_command", 30)
|
||||
# ... (il resto della funzione run() rimane invariato da qui in poi) ...
|
||||
|
||||
program_has_exited = (
|
||||
"Program exited normally" in gdb_output
|
||||
or "exited with code" in gdb_output
|
||||
)
|
||||
if program_has_exited:
|
||||
self._log_event(
|
||||
f"Program exited on initial run. Output: {gdb_output[:100]}", True
|
||||
)
|
||||
|
||||
# Main event loop
|
||||
while (
|
||||
self.gdb_session.is_alive()
|
||||
and not program_has_exited
|
||||
and not self._stop_requested
|
||||
):
|
||||
hit_gdb_bp_num = self._parse_breakpoint_hit_output(gdb_output)
|
||||
|
||||
# NEW: Try to get current PC if no direct BP number is parsed, or to confirm address
|
||||
current_pc_address: Optional[str] = None
|
||||
if (
|
||||
self.gdb_session
|
||||
and self.gdb_session.is_alive()
|
||||
and not program_has_exited
|
||||
):
|
||||
try:
|
||||
# This is an extra command to GDB, use with caution if performance is critical
|
||||
# For now, it helps resolve the actual stopping address.
|
||||
pc_out = self.gdb_session.send_cmd(
|
||||
"p/x $pc", expect_prompt=True, timeout=cmd_timeout
|
||||
)
|
||||
self.gdb_output_writer(f"$pc query: {pc_out}\n")
|
||||
pc_match = re.search(r"=\s*(0x[0-9a-fA-F]+)", pc_out)
|
||||
if pc_match:
|
||||
current_pc_address = pc_match.group(1).lower()
|
||||
self._log_event(f"Current PC: {current_pc_address}", False)
|
||||
except Exception as e_pc:
|
||||
self._log_event(f"Could not get current PC: {e_pc}", False)
|
||||
|
||||
actions_to_process_at_this_stop: List[int] = []
|
||||
hit_bp_details_for_log = "N/A"
|
||||
|
||||
if (
|
||||
hit_gdb_bp_num is not None
|
||||
and hit_gdb_bp_num in self.gdb_bp_num_to_details_map
|
||||
):
|
||||
# GDB reported a direct BP number hit
|
||||
bp_details = self.gdb_bp_num_to_details_map[hit_gdb_bp_num]
|
||||
address_of_hit = bp_details["address"]
|
||||
hit_bp_details_for_log = f"GDB BP {hit_gdb_bp_num} ('{bp_details['bp_spec']}') at {address_of_hit}"
|
||||
if (
|
||||
address_of_hit != "pending"
|
||||
and address_of_hit in self.address_to_action_indices_map
|
||||
):
|
||||
actions_to_process_at_this_stop.extend(
|
||||
self.address_to_action_indices_map[address_of_hit]
|
||||
)
|
||||
elif (
|
||||
current_pc_address
|
||||
and current_pc_address in self.address_to_action_indices_map
|
||||
):
|
||||
# Stopped at a known address, even if GDB didn't report a specific BP number we parsed
|
||||
actions_to_process_at_this_stop.extend(
|
||||
self.address_to_action_indices_map[current_pc_address]
|
||||
)
|
||||
hit_bp_details_for_log = (
|
||||
f"PC {current_pc_address} (mapped to actions)"
|
||||
)
|
||||
|
||||
if actions_to_process_at_this_stop:
|
||||
self._log_event(
|
||||
f"Processing stop at {hit_bp_details_for_log}.", True
|
||||
)
|
||||
|
||||
# Process all actions mapped to this address/hit
|
||||
# Deduplicate action indices in case of multiple GDB BPs mapping to same address and action
|
||||
unique_action_indices_to_process = sorted(
|
||||
list(set(actions_to_process_at_this_stop))
|
||||
)
|
||||
|
||||
should_continue_after_all_these_actions = True # Default
|
||||
for action_idx in unique_action_indices_to_process:
|
||||
if self._stop_requested:
|
||||
break
|
||||
current_action_config = actions[action_idx]
|
||||
action_summary = self.profile_execution_summary[
|
||||
"actions_summary"
|
||||
][action_idx]
|
||||
|
||||
# Check if this action was already completed (e.g. if multiple GDB BPs mapped to it)
|
||||
if action_summary["status"].startswith("Completed"):
|
||||
self._log_event(
|
||||
f"Action {action_idx + 1} ('{current_action_config.get('breakpoint_location')}') already completed. Skipping.",
|
||||
False,
|
||||
)
|
||||
if not current_action_config.get(
|
||||
"continue_after_dump", True
|
||||
):
|
||||
should_continue_after_all_these_actions = (
|
||||
False # If one says stop, we stop
|
||||
)
|
||||
continue
|
||||
|
||||
self._log_event(
|
||||
f"Executing Action {action_idx + 1} ('{current_action_config.get('breakpoint_location')}')...",
|
||||
False,
|
||||
)
|
||||
action_summary["status"] = "Processing Dumps"
|
||||
# ... (dumping logic for variables in current_action_config - same as before)
|
||||
vars_to_dump_for_action = current_action_config.get(
|
||||
"variables_to_dump", []
|
||||
)
|
||||
filename_pattern = current_action_config.get(
|
||||
"filename_pattern",
|
||||
"{breakpoint}_{variable}_{timestamp}.{format}",
|
||||
)
|
||||
output_format_for_action = current_action_config.get(
|
||||
"output_format", "json"
|
||||
).lower()
|
||||
bp_spec_for_file = current_action_config.get(
|
||||
"breakpoint_location", "unknown_bp"
|
||||
)
|
||||
|
||||
dump_success_count = 0
|
||||
for var_name in vars_to_dump_for_action:
|
||||
# ... (dumping and saving logic for each var)
|
||||
# Make sure to use bp_spec_for_file in _add_produced_file_entry and _generate_output_filename
|
||||
# ...
|
||||
dump_timeout = self._get_setting(
|
||||
"timeouts", "dump_variable", 60
|
||||
)
|
||||
dumped_data = None
|
||||
file_save_path = ""
|
||||
dump_status_msg = "Failed"
|
||||
dump_details_msg = ""
|
||||
if (
|
||||
not self.gdb_session.gdb_script_sourced_successfully
|
||||
and output_format_for_action == "json"
|
||||
):
|
||||
msg = f"Dumper script unavailable for '{var_name}' (JSON)."
|
||||
self._log_event(msg, False)
|
||||
dump_details_msg = msg
|
||||
self.json_data_handler(
|
||||
{
|
||||
"_profile_executor_error": msg,
|
||||
"variable": var_name,
|
||||
}
|
||||
)
|
||||
else:
|
||||
dumped_data = self.gdb_session.dump_variable_to_json(
|
||||
var_name, timeout=dump_timeout
|
||||
)
|
||||
self.json_data_handler(dumped_data)
|
||||
if (
|
||||
isinstance(dumped_data, dict)
|
||||
and "_gdb_tool_error" in dumped_data
|
||||
):
|
||||
err_detail = dumped_data.get(
|
||||
"details", dumped_data["_gdb_tool_error"]
|
||||
)
|
||||
self._log_event(
|
||||
f"Error dumping '{var_name}': {err_detail}",
|
||||
False,
|
||||
)
|
||||
dump_details_msg = f"GDB Tool Error: {err_detail}"
|
||||
elif dumped_data is not None:
|
||||
output_filename = self._generate_output_filename(
|
||||
filename_pattern,
|
||||
profile_name,
|
||||
bp_spec_for_file,
|
||||
var_name,
|
||||
output_format_for_action,
|
||||
)
|
||||
file_save_path = os.path.join(
|
||||
self.current_run_output_path, output_filename
|
||||
)
|
||||
try:
|
||||
if output_format_for_action == "json":
|
||||
save_data_to_json_file(
|
||||
dumped_data, file_save_path
|
||||
)
|
||||
elif output_format_for_action == "csv":
|
||||
data_for_csv = (
|
||||
dumped_data # Adapt as before
|
||||
)
|
||||
if isinstance(
|
||||
data_for_csv, dict
|
||||
) and not isinstance(data_for_csv, list):
|
||||
data_for_csv = [data_for_csv] # etc.
|
||||
save_data_to_csv_file(
|
||||
data_for_csv, file_save_path
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Unsupported format: {output_format_for_action}"
|
||||
)
|
||||
self._log_event(
|
||||
f"Saved '{var_name}' to '{output_filename}'.",
|
||||
False,
|
||||
)
|
||||
dump_status_msg = "Success"
|
||||
dump_success_count += 1
|
||||
except Exception as save_e:
|
||||
self._log_event(
|
||||
f"Error saving dump of '{var_name}': {save_e}",
|
||||
False,
|
||||
)
|
||||
dump_details_msg = f"Save Error: {save_e}"
|
||||
else:
|
||||
self._log_event(
|
||||
f"Dump of '{var_name}' returned no data.", False
|
||||
)
|
||||
dump_details_msg = "Dump returned no data"
|
||||
self._add_produced_file_entry(
|
||||
bp_spec_for_file,
|
||||
var_name,
|
||||
file_save_path,
|
||||
dump_status_msg,
|
||||
gdb_bp_num=hit_gdb_bp_num,
|
||||
address=current_pc_address,
|
||||
details=dump_details_msg,
|
||||
)
|
||||
|
||||
action_summary["variables_dumped_count"] = dump_success_count
|
||||
if (
|
||||
dump_success_count == len(vars_to_dump_for_action)
|
||||
and vars_to_dump_for_action
|
||||
):
|
||||
action_summary["status"] = "Completed"
|
||||
elif not vars_to_dump_for_action:
|
||||
action_summary["status"] = "Completed (No Vars)"
|
||||
else:
|
||||
action_summary["status"] = "Completed with Errors"
|
||||
|
||||
if not current_action_config.get("continue_after_dump", True):
|
||||
should_continue_after_all_these_actions = False
|
||||
|
||||
if self._stop_requested:
|
||||
break
|
||||
if should_continue_after_all_these_actions:
|
||||
self._log_event(
|
||||
f"Continuing after processing actions at {hit_bp_details_for_log}...",
|
||||
True,
|
||||
)
|
||||
gdb_output = self.gdb_session.continue_execution(
|
||||
timeout=run_timeout
|
||||
)
|
||||
self.gdb_output_writer(gdb_output)
|
||||
if (
|
||||
"Program exited normally" in gdb_output
|
||||
or "exited with code" in gdb_output
|
||||
):
|
||||
program_has_exited = True
|
||||
self._log_event(
|
||||
f"Program exited after continue. Output: {gdb_output[:100]}",
|
||||
True,
|
||||
)
|
||||
else:
|
||||
self._log_event(
|
||||
f"Execution halted after processing actions at {hit_bp_details_for_log} as per profile.",
|
||||
True,
|
||||
)
|
||||
program_has_exited = (
|
||||
True # Treat as if program ended for the profile
|
||||
)
|
||||
|
||||
elif (
|
||||
"Program exited normally" in gdb_output
|
||||
or "exited with code" in gdb_output
|
||||
):
|
||||
program_has_exited = True
|
||||
self._log_event(f"Program exited. Output: {gdb_output[:100]}", True)
|
||||
elif "received signal" in gdb_output.lower():
|
||||
program_has_exited = True
|
||||
self._log_event(
|
||||
f"Program received signal. Output: {gdb_output[:100]}", True
|
||||
)
|
||||
self.profile_execution_summary["status"] = (
|
||||
"Completed (Program Signalled/Crashed)"
|
||||
)
|
||||
else:
|
||||
self._log_event(
|
||||
f"GDB unresponsive or no recognized output after previous step. Output: {gdb_output[:200]}",
|
||||
True,
|
||||
)
|
||||
program_has_exited = True # Assume cannot proceed
|
||||
|
||||
if program_has_exited:
|
||||
break # Exit while loop
|
||||
|
||||
# After loop summary status update
|
||||
final_status = "Completed"
|
||||
if program_has_exited and not self._stop_requested:
|
||||
# Check if any actions are still pending (implies program exited before all BPs were hit)
|
||||
if any(
|
||||
s["status"] == "Pending" or s["status"] == "Pending in GDB"
|
||||
for s in self.profile_execution_summary["actions_summary"]
|
||||
):
|
||||
final_status = "Completed (Program Exited Prematurely)"
|
||||
# Preserve crash status if set
|
||||
if self.profile_execution_summary["status"] not in [
|
||||
"Initialized",
|
||||
"Error: No BPs Mapped",
|
||||
]: # if not already an error
|
||||
if (
|
||||
"Crashed" in self.profile_execution_summary["status"]
|
||||
or "Signalled" in self.profile_execution_summary["status"]
|
||||
):
|
||||
pass # Keep the more specific crash status
|
||||
else:
|
||||
self.profile_execution_summary["status"] = final_status
|
||||
elif self._stop_requested:
|
||||
self.profile_execution_summary["status"] = "Completed (User Stopped)"
|
||||
elif (
|
||||
not (self.gdb_session and self.gdb_session.is_alive())
|
||||
and not program_has_exited
|
||||
):
|
||||
self.profile_execution_summary["status"] = (
|
||||
"Error: GDB Died Unexpectedly"
|
||||
)
|
||||
self._log_event(
|
||||
"Error: GDB session died unexpectedly during execution.", True
|
||||
)
|
||||
else: # Loop finished, all actions processed or halted by continue=false
|
||||
self.profile_execution_summary["status"] = (
|
||||
"Completed (All Actions Processed or Halted by Profile)"
|
||||
)
|
||||
|
||||
except InterruptedError as ie: # Custom for user stop
|
||||
# ... (gestione eccezioni e blocco finally rimangono invariati) ...
|
||||
except InterruptedError as ie: # Custom for user stop
|
||||
self.profile_execution_summary["status"] = "Interrupted (User Stop)"
|
||||
self._log_event(str(ie), True)
|
||||
except FileNotFoundError as fnf_e: # ... (standard error handling)
|
||||
except FileNotFoundError as fnf_e:
|
||||
msg = f"Error running profile '{profile_name}': File not found - {fnf_e}"
|
||||
self._log_event(msg, True)
|
||||
self.profile_execution_summary["status"] = f"Error: {fnf_e}"
|
||||
self._log_event(msg, True); self.profile_execution_summary["status"] = f"Error: {fnf_e}"
|
||||
except (ConnectionError, TimeoutError) as session_e:
|
||||
msg = f"Session error running profile '{profile_name}': {type(session_e).__name__} - {session_e}"
|
||||
self._log_event(msg, True)
|
||||
self.profile_execution_summary["status"] = f"Error: {session_e}"
|
||||
self._log_event(msg, True); self.profile_execution_summary["status"] = f"Error: {session_e}"
|
||||
except Exception as e:
|
||||
msg = f"Unexpected error running profile '{profile_name}': {type(e).__name__} - {e}"
|
||||
self._log_event(msg, True)
|
||||
logger.critical(msg, exc_info=True)
|
||||
self._log_event(msg, True); logger.critical(msg, exc_info=True)
|
||||
self.profile_execution_summary["status"] = f"Critical Error: {e}"
|
||||
finally:
|
||||
self._cleanup_session()
|
||||
self.profile_execution_summary["end_time"] = datetime.now().isoformat()
|
||||
self.profile_execution_summary["execution_log"] = self.execution_event_log
|
||||
self.profile_execution_summary["files_produced_detailed"] = (
|
||||
self.produced_files_log
|
||||
)
|
||||
# Assicurati che end_time e altri campi del sommario siano impostati anche se c'è un return anticipato
|
||||
if self.profile_execution_summary.get("end_time") is None:
|
||||
self.profile_execution_summary["end_time"] = datetime.now().isoformat()
|
||||
if not self.profile_execution_summary.get("execution_log"): # Se non già popolato da un errore precedente
|
||||
self.profile_execution_summary["execution_log"] = self.execution_event_log
|
||||
if not self.profile_execution_summary.get("files_produced_detailed"):
|
||||
self.profile_execution_summary["files_produced_detailed"] = self.produced_files_log
|
||||
|
||||
summary_file_path = self._finalize_summary_report(
|
||||
self.current_run_output_path
|
||||
)
|
||||
# Add summary file to produced_files_log AFTER it's written, if successful.
|
||||
# This needs to be handled carefully to avoid adding it if _finalize_summary_report fails.
|
||||
# The _finalize_summary_report could internally call _add_produced_file_entry,
|
||||
# or we add it here based on its return value.
|
||||
# For now, _finalize_summary_report does not call it to prevent recursion on error.
|
||||
self._cleanup_session() # Questo ora viene chiamato prima di _finalize_summary_report nel flusso normale
|
||||
# e anche nei casi di errore sopra.
|
||||
|
||||
self._log_event(
|
||||
f"Profile '{profile_name}' execution cycle finished. Summary report generation attempt at: {summary_file_path if summary_file_path else 'N/A'}.",
|
||||
True,
|
||||
)
|
||||
# Il path del sommario viene determinato e il sommario scritto da _finalize_summary_report
|
||||
# Se current_run_output_path non è stato creato (es. errore simboli prima della creazione dir),
|
||||
# _finalize_summary_report non salverà su file specifico ma loggherà il sommario.
|
||||
summary_file_path = self._finalize_summary_report(self.current_run_output_path)
|
||||
|
||||
final_gui_message = (f"Profile '{profile_name}' execution cycle finished. "
|
||||
f"Status: {self.profile_execution_summary.get('status', 'Unknown')}. "
|
||||
f"Summary report attempt at: {summary_file_path if summary_file_path else 'N/A (see logs)'}.")
|
||||
self._log_event(final_gui_message, True)
|
||||
self.is_running = False
|
||||
|
||||
def _finalize_summary_report(self, run_output_path: Optional[str]) -> Optional[str]:
|
||||
|
||||
@ -660,155 +660,125 @@ class GDBGui(tk.Tk):
|
||||
messagebox.showerror("GDB Operation Error", error_message, parent=self)
|
||||
|
||||
def _start_gdb_session_action(self):
|
||||
# ... (implementation as before, including disabling profile controls) ...
|
||||
if self.profile_executor_instance and self.profile_executor_instance.is_running:
|
||||
messagebox.showwarning(
|
||||
"Profile Running",
|
||||
"An automated profile is running. Please stop it first.",
|
||||
parent=self,
|
||||
)
|
||||
messagebox.showwarning("Profile Running", "An automated profile is running. Please stop it first.", parent=self)
|
||||
return
|
||||
|
||||
gdb_exe = self.app_settings.get_setting("general", "gdb_executable_path")
|
||||
target_exe = self.exe_path_var.get()
|
||||
gdb_script = self.app_settings.get_setting("general", "gdb_dumper_script_path")
|
||||
|
||||
# ... (validazioni iniziali per gdb_exe, target_exe, etc. rimangono invariate) ...
|
||||
if not gdb_exe or not os.path.isfile(gdb_exe):
|
||||
messagebox.showerror(
|
||||
"Configuration Error",
|
||||
"GDB executable path is not configured correctly. Please check Options > Configure.",
|
||||
parent=self,
|
||||
)
|
||||
messagebox.showerror("Configuration Error", "GDB executable path is not configured correctly. Please check Options > Configure.", parent=self)
|
||||
self._check_critical_configs_and_update_gui()
|
||||
return
|
||||
if not target_exe:
|
||||
messagebox.showerror(
|
||||
"Input Error", "Target executable path is required.", parent=self
|
||||
)
|
||||
messagebox.showerror("Input Error", "Target executable path is required.", parent=self)
|
||||
return
|
||||
if not os.path.exists(target_exe):
|
||||
messagebox.showerror(
|
||||
"File Not Found",
|
||||
f"Target executable not found: {target_exe}",
|
||||
parent=self,
|
||||
)
|
||||
messagebox.showerror("File Not Found", f"Target executable not found: {target_exe}", parent=self)
|
||||
return
|
||||
|
||||
dumper_script_invalid = False
|
||||
if gdb_script and not os.path.isfile(gdb_script):
|
||||
messagebox.showwarning(
|
||||
"Configuration Warning",
|
||||
f"GDB dumper script path is set to:\n'{gdb_script}'\nbut the file was not found or is invalid.\n\nJSON dumping via script will be unavailable. You can correct this in Options > Configure.",
|
||||
parent=self,
|
||||
)
|
||||
gdb_script = None
|
||||
self.gdb_dumper_status_var.set(
|
||||
f"Dumper: '{self.app_settings.get_setting('general', 'gdb_dumper_script_path')}' (Not Found!)"
|
||||
)
|
||||
dumper_script_invalid = True
|
||||
self.gdb_dumper_status_var.set(f"Dumper: '{self.app_settings.get_setting('general', 'gdb_dumper_script_path')}' (Not Found!)")
|
||||
|
||||
|
||||
if self.gdb_session and self.gdb_session.is_alive():
|
||||
messagebox.showwarning(
|
||||
"Session Active",
|
||||
"A GDB session is already active. Please stop it first.",
|
||||
parent=self,
|
||||
)
|
||||
messagebox.showwarning("Session Active", "A GDB session is already active. Please stop it first.", parent=self)
|
||||
return
|
||||
|
||||
self._update_status_bar("Starting GDB session...")
|
||||
self._update_gdb_raw_output(
|
||||
"Attempting to start GDB session...\n", append=False
|
||||
)
|
||||
self._update_gdb_raw_output("Attempting to start GDB session...\n", append=False)
|
||||
self._update_parsed_json_output(None)
|
||||
|
||||
try:
|
||||
startup_timeout = self.app_settings.get_setting("timeouts", "gdb_start", 30)
|
||||
current_dumper_options = self.app_settings.get_category_settings(
|
||||
"dumper_options", {}
|
||||
) # Ensure this is fetched
|
||||
quit_timeout_on_no_symbols = self.app_settings.get_setting("timeouts", "gdb_quit", 10) # Timeout per quit
|
||||
current_dumper_options = self.app_settings.get_category_settings("dumper_options", {})
|
||||
|
||||
self.gdb_session = GDBSession(
|
||||
gdb_path=gdb_exe,
|
||||
executable_path=target_exe,
|
||||
gdb_script_full_path=gdb_script,
|
||||
dumper_options=current_dumper_options,
|
||||
gdb_path=gdb_exe, executable_path=target_exe,
|
||||
gdb_script_full_path=gdb_script, dumper_options=current_dumper_options
|
||||
)
|
||||
self.gdb_session.start(
|
||||
timeout=startup_timeout
|
||||
) # This call internally sets gdb_script_sourced_successfully
|
||||
self.gdb_session.start(timeout=startup_timeout)
|
||||
|
||||
self._update_gdb_raw_output(
|
||||
f"GDB session started for '{os.path.basename(target_exe)}'.\n"
|
||||
)
|
||||
|
||||
# --- NUOVO LOGGING DIAGNOSTICO ---
|
||||
self._update_gdb_raw_output(f"GDB session started for '{os.path.basename(target_exe)}'.\n")
|
||||
|
||||
# Logging diagnostico (mantenuto)
|
||||
logger.info(f"MAIN_WINDOW_CHECK: gdb_script path = '{gdb_script}'")
|
||||
if self.gdb_session: # Assicurati che gdb_session esista
|
||||
logger.info(
|
||||
f"MAIN_WINDOW_CHECK: self.gdb_session.gdb_script_path (internal) = '{self.gdb_session.gdb_script_path}'"
|
||||
)
|
||||
logger.info(
|
||||
f"MAIN_WINDOW_CHECK: self.gdb_session.gdb_script_sourced_successfully = {self.gdb_session.gdb_script_sourced_successfully}"
|
||||
)
|
||||
if self.gdb_session:
|
||||
logger.info(f"MAIN_WINDOW_CHECK: self.gdb_session.gdb_script_path (internal GDBSession path) = '{self.gdb_session.gdb_script_path}'")
|
||||
logger.info(f"MAIN_WINDOW_CHECK: self.gdb_session.gdb_script_sourced_successfully = {self.gdb_session.gdb_script_sourced_successfully}")
|
||||
logger.info(f"MAIN_WINDOW_CHECK: self.gdb_session.symbols_found = {self.gdb_session.symbols_found}")
|
||||
else:
|
||||
logger.error(
|
||||
"MAIN_WINDOW_CHECK: self.gdb_session is None at the point of checking dumper status!"
|
||||
)
|
||||
# --- FINE NUOVO LOGGING DIAGNOSTICO ---
|
||||
logger.error("MAIN_WINDOW_CHECK: self.gdb_session is None at the point of checking dumper/symbols status!")
|
||||
|
||||
if (
|
||||
gdb_script
|
||||
and self.gdb_session
|
||||
and self.gdb_session.gdb_script_sourced_successfully
|
||||
): # Aggiunto check self.gdb_session
|
||||
self._update_gdb_raw_output(
|
||||
f"GDB dumper script '{os.path.basename(gdb_script)}' sourced successfully.\n",
|
||||
append=True,
|
||||
)
|
||||
self._update_status_bar(
|
||||
f"GDB active. Dumper '{os.path.basename(gdb_script)}' loaded."
|
||||
)
|
||||
self.gdb_dumper_status_var.set(
|
||||
f"Dumper: {os.path.basename(gdb_script)} (Loaded)"
|
||||
)
|
||||
elif (
|
||||
gdb_script and self.gdb_session
|
||||
): # Aggiunto check self.gdb_session, implica che gdb_script_sourced_successfully è False
|
||||
self._update_gdb_raw_output(
|
||||
f"Warning: GDB dumper script '{os.path.basename(gdb_script)}' specified but failed to load.\n",
|
||||
append=True,
|
||||
)
|
||||
self._update_status_bar(
|
||||
f"GDB active. Dumper script issues (check logs).", is_error=True
|
||||
)
|
||||
self.gdb_dumper_status_var.set(
|
||||
f"Dumper: {os.path.basename(gdb_script)} (Load Failed!)"
|
||||
)
|
||||
# Mostra il warning solo se la finestra esiste ancora
|
||||
|
||||
symbols_ok = self.gdb_session and self.gdb_session.symbols_found
|
||||
dumper_loaded_successfully = self.gdb_session and self.gdb_session.gdb_script_sourced_successfully
|
||||
|
||||
if not symbols_ok:
|
||||
self._update_gdb_raw_output("ERROR: No debugging symbols found in the executable. GDB session will be terminated.\n", append=True)
|
||||
# Mostra il warning all'utente
|
||||
if self.winfo_exists():
|
||||
messagebox.showwarning(
|
||||
"Dumper Script Issue",
|
||||
f"The GDB dumper script '{os.path.basename(gdb_script)}' may have failed to load.\nJSON dumping might be affected. Check logs.",
|
||||
parent=self,
|
||||
)
|
||||
elif not gdb_script and self.gdb_session: # Aggiunto check self.gdb_session
|
||||
self._update_gdb_raw_output(
|
||||
"No GDB dumper script. JSON dump via script unavailable.\n",
|
||||
append=True,
|
||||
)
|
||||
self._update_status_bar("GDB session active. No dumper script.")
|
||||
messagebox.showwarning("No Debug Symbols - Session Aborted",
|
||||
f"GDB reported no debugging symbols found in:\n{os.path.basename(target_exe)}\n\n"
|
||||
"The GDB session will be terminated as debugging capabilities are severely limited.",
|
||||
parent=self)
|
||||
|
||||
self._update_status_bar("GDB session aborted: No debug symbols.", is_error=True)
|
||||
|
||||
# Termina la sessione GDB e resetta la GUI
|
||||
if self.gdb_session and self.gdb_session.is_alive():
|
||||
try:
|
||||
self.gdb_session.quit(timeout=quit_timeout_on_no_symbols)
|
||||
except Exception as e_quit:
|
||||
logger.error(f"Exception during GDB quit (no symbols scenario): {e_quit}")
|
||||
self.gdb_session = None
|
||||
self._reset_gui_to_stopped_state()
|
||||
self._check_critical_configs_and_update_gui() # Per aggiornare lo stato GDB/Dumper
|
||||
return # Esci dalla funzione _start_gdb_session_action
|
||||
|
||||
# Fallback per lo stato del dumper se non è configurato
|
||||
if not self.app_settings.get_setting("general", "gdb_dumper_script_path"):
|
||||
self.gdb_dumper_status_var.set("Dumper: Not Configured (Optional).")
|
||||
# Se siamo qui, i simboli sono stati trovati (symbols_ok è True)
|
||||
# Procedi con la logica del dumper script
|
||||
if gdb_script:
|
||||
if dumper_script_invalid:
|
||||
self._update_gdb_raw_output(f"Warning: GDB dumper script path '{gdb_script}' is invalid and was not found.\n", append=True)
|
||||
self._update_status_bar(f"GDB active. Dumper script path invalid.", is_error=True)
|
||||
self.gdb_dumper_status_var.set(f"Dumper: {os.path.basename(gdb_script)} (Path Invalid!)")
|
||||
elif dumper_loaded_successfully:
|
||||
self._update_gdb_raw_output(f"GDB dumper script '{os.path.basename(gdb_script)}' sourced successfully.\n", append=True)
|
||||
self._update_status_bar(f"GDB active. Dumper '{os.path.basename(gdb_script)}' loaded.")
|
||||
self.gdb_dumper_status_var.set(f"Dumper: {os.path.basename(gdb_script)} (Loaded)")
|
||||
else: # path valido, ma caricamento fallito
|
||||
self._update_gdb_raw_output(f"Warning: GDB dumper script '{os.path.basename(gdb_script)}' specified but FAILED to load correctly.\n", append=True)
|
||||
self._update_status_bar(f"GDB active. Dumper script load issue (check logs).", is_error=True)
|
||||
if self.winfo_exists():
|
||||
messagebox.showwarning("Dumper Script Issue",
|
||||
f"The GDB dumper script '{os.path.basename(gdb_script)}' may have failed to load correctly.\n"
|
||||
"JSON dumping might be affected. Check logs.",
|
||||
parent=self)
|
||||
self.gdb_dumper_status_var.set(f"Dumper: {os.path.basename(gdb_script)} (Load Failed!)")
|
||||
elif self.gdb_session: # Nessuno script dumper specificato
|
||||
self._update_gdb_raw_output("No GDB dumper script specified. JSON dump via script unavailable.\n", append=True)
|
||||
self._update_status_bar("GDB session active. No dumper script.")
|
||||
self.gdb_dumper_status_var.set("Dumper: Not Configured (Optional).")
|
||||
|
||||
|
||||
# Abilita i bottoni della GUI dato che i simboli sono OK
|
||||
self.start_gdb_button.config(state=tk.DISABLED)
|
||||
self.set_bp_button.config(state=tk.NORMAL)
|
||||
self.run_button.config(
|
||||
state=tk.DISABLED, text="3. Run Program"
|
||||
) # Inizialmente disabilitato fino a BP
|
||||
self.dump_var_button.config(
|
||||
state=tk.DISABLED
|
||||
) # Disabilitato finché non si è a un breakpoint
|
||||
self.run_button.config(state=tk.DISABLED, text="3. Run Program")
|
||||
self.dump_var_button.config(state=tk.DISABLED)
|
||||
self.stop_gdb_button.config(state=tk.NORMAL)
|
||||
# Disabilita controlli profilo
|
||||
if hasattr(self, "run_profile_button"):
|
||||
self.run_profile_button.config(state=tk.DISABLED)
|
||||
if hasattr(self, "profile_selection_combo"):
|
||||
self.profile_selection_combo.config(state=tk.DISABLED)
|
||||
|
||||
self.program_started_once = False # Resetta per la nuova sessione
|
||||
if hasattr(self, 'run_profile_button'): self.run_profile_button.config(state=tk.DISABLED)
|
||||
if hasattr(self, 'profile_selection_combo'): self.profile_selection_combo.config(state=tk.DISABLED)
|
||||
|
||||
self.program_started_once = False
|
||||
self.last_dumped_data = None
|
||||
self._disable_save_buttons()
|
||||
|
||||
@ -816,16 +786,13 @@ class GDBGui(tk.Tk):
|
||||
self._handle_gdb_operation_error("start session", e_specific)
|
||||
self.gdb_session = None
|
||||
self._reset_gui_to_stopped_state()
|
||||
self._check_critical_configs_and_update_gui()
|
||||
except Exception as e:
|
||||
logger.critical(
|
||||
f"!!! MAIN_WINDOW CATCH-ALL: Unhandled exception type: {type(e).__name__}, message: '{e}'",
|
||||
exc_info=True,
|
||||
)
|
||||
self._handle_gdb_operation_error(
|
||||
"start session (unexpected from main_window catch-all)", e
|
||||
)
|
||||
logger.critical(f"!!! MAIN_WINDOW CATCH-ALL: Unhandled exception type: {type(e).__name__}, message: '{e}'", exc_info=True)
|
||||
self._handle_gdb_operation_error("start session (unexpected from main_window catch-all)", e)
|
||||
self.gdb_session = None
|
||||
self._reset_gui_to_stopped_state()
|
||||
self._check_critical_configs_and_update_gui() # Resetta la GUI
|
||||
|
||||
def _set_gdb_breakpoint_action(self):
|
||||
# ... (implementation as before)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user