From 503b81ee22b8f290ef44d1aa55d2bd8588db7931 Mon Sep 17 00:00:00 2001 From: VALLONGOL Date: Fri, 23 May 2025 13:32:57 +0200 Subject: [PATCH] add check if not present debug symbol --- cpp_python_debug/core/gdb_controller.py | 73 +-- cpp_python_debug/core/profile_executor.py | 563 ++++------------------ cpp_python_debug/gui/main_window.py | 215 ++++----- 3 files changed, 212 insertions(+), 639 deletions(-) diff --git a/cpp_python_debug/core/gdb_controller.py b/cpp_python_debug/core/gdb_controller.py index 2616357..64521f2 100644 --- a/cpp_python_debug/core/gdb_controller.py +++ b/cpp_python_debug/core/gdb_controller.py @@ -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 = "" 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( diff --git a/cpp_python_debug/core/profile_executor.py b/cpp_python_debug/core/profile_executor.py index 5b27570..ff194f6 100644 --- a/cpp_python_debug/core/profile_executor.py +++ b/cpp_python_debug/core/profile_executor.py @@ -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]: diff --git a/cpp_python_debug/gui/main_window.py b/cpp_python_debug/gui/main_window.py index 6624149..c715ac0 100644 --- a/cpp_python_debug/gui/main_window.py +++ b/cpp_python_debug/gui/main_window.py @@ -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)