This commit is contained in:
VALLONGOL 2025-05-26 11:04:04 +02:00
parent 8180c3cf13
commit 40f40ed3f7
3 changed files with 469 additions and 906 deletions

File diff suppressed because it is too large Load Diff

View File

@ -34,18 +34,7 @@ class SymbolAnalyzer:
target_exe_path: str,
progress_callback: Optional[Callable[[str], None]] = None,
status_callback: Optional[Callable[[str], None]] = None) -> Dict[str, Any]:
"""
Performs a full symbol analysis of the target executable using GDB.
Args:
target_exe_path: The full path to the target executable.
progress_callback: A callback function (str) -> None to log detailed progress.
status_callback: A callback function (str) -> None to update general status.
Returns:
A dictionary containing all analyzed symbol data, or an empty dict on critical failure.
"""
# Inizializza la struttura dati completa per l'analisi
analysis_data_dict: Dict[str, Any] = {
"analyzed_executable_path": target_exe_path,
"executable_checksum": None,
@ -55,12 +44,12 @@ class SymbolAnalyzer:
"symbols": {
"functions": [],
"functions_count": 0,
"global_variables": [], # Per Iterazione 3
"global_variables_count": 0,# Per Iterazione 3
"types": [], # Per Iterazione 3
"types_count": 0, # Per Iterazione 3
"source_files": [], # Per Iterazione 3
"source_files_count": 0 # Per Iterazione 3
"global_variables": [],
"global_variables_count": 0,
"types": [],
"types_count": 0,
"source_files": [], # Placeholder per futura iterazione
"source_files_count": 0 # Placeholder per futura iterazione
}
}
@ -79,8 +68,9 @@ class SymbolAnalyzer:
self._temp_gdb_session = GDBSession(
gdb_path=self.gdb_exe_path,
executable_path=target_exe_path,
gdb_script_full_path=None, # Non serve lo script dumper per l'analisi
dumper_options={}
gdb_script_full_path=None,
dumper_options={},
use_mi_mode=True
)
startup_timeout = self._get_timeout("timeouts", "gdb_start", 30)
@ -98,28 +88,39 @@ class SymbolAnalyzer:
log_progress(f"GDB Version: {analysis_data_dict['gdb_version_info']}")
set_status("Fetching function list..."); log_progress("Fetching function list from GDB...")
# Diamo più tempo per 'info functions' in caso di librerie grandi
functions = self._temp_gdb_session.list_functions(timeout=command_timeout * 4)
analysis_data_dict["symbols"]["functions"] = functions
analysis_data_dict["symbols"]["functions_count"] = len(functions)
log_progress(f"Found {len(functions)} functions.")
# --- In future iterations, add calls for other symbol types (variables, types, sources) ---
# set_status("Fetching global variables..."); log_progress("Fetching global variables...")
# global_vars = self._temp_gdb_session.list_global_variables(timeout=command_timeout * 2)
# analysis_data_dict["symbols"]["global_variables"] = global_vars
# analysis_data_dict["symbols"]["global_variables_count"] = len(global_vars)
# log_progress(f"Found {len(global_vars)} global variables.")
# ... (similarly for types and source_files) ...
# --- NUOVO: Recupera variabili globali ---
set_status("Fetching global variables..."); log_progress("Fetching global variables from GDB...")
global_variables = self._temp_gdb_session.list_global_variables(timeout=command_timeout * 2)
analysis_data_dict["symbols"]["global_variables"] = global_variables
analysis_data_dict["symbols"]["global_variables_count"] = len(global_variables)
log_progress(f"Found {len(global_variables)} global variables.")
# --- NUOVO: Recupera tipi ---
set_status("Fetching data types..."); log_progress("Fetching data types from GDB...")
types = self._temp_gdb_session.list_types(timeout=command_timeout * 2)
analysis_data_dict["symbols"]["types"] = types
analysis_data_dict["symbols"]["types_count"] = len(types)
log_progress(f"Found {len(types)} types.")
# --- Placeholder per futura iterazione: file sorgente ---
# set_status("Fetching source files..."); log_progress("Fetching source files from GDB...")
# source_files = self._temp_gdb_session.list_source_files(timeout=command_timeout * 2)
# analysis_data_dict["symbols"]["source_files"] = source_files
# analysis_data_dict["symbols"]["source_files_count"] = len(source_files)
# log_progress(f"Found {len(source_files)} source files.")
set_status("Calculating file checksum and timestamp..."); log_progress("Calculating file checksum and timestamp...")
# Utilizza la funzione esterna calculate_file_checksum
analysis_data_dict["executable_checksum"] = calculate_file_checksum(target_exe_path)
try:
mtime = os.path.getmtime(target_exe_path)
analysis_data_dict["executable_timestamp"] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(mtime))
except OSError: pass # Se il file non esiste (già controllato), o permessi. Lascia N/A
except OSError: pass
log_progress(f"Checksum (MD5): {analysis_data_dict['executable_checksum'] or 'N/A'}")
log_progress(f"File Timestamp: {analysis_data_dict['executable_timestamp']}")
@ -133,7 +134,7 @@ class SymbolAnalyzer:
logger.error(f"Error during symbol analysis for '{target_exe_path}': {e}", exc_info=True)
log_progress(f"\nCRITICAL ERROR during analysis: {type(e).__name__} - {e}")
set_status(f"Error during analysis: {e}")
return {} # Ritorna un dizionario vuoto in caso di errore critico
return {}
finally:
if self._temp_gdb_session and self._temp_gdb_session.is_alive():
log_progress("Closing GDB session..."); set_status("Closing GDB session...")

View File

@ -45,6 +45,7 @@ DEFAULT_PROFILE = {
class ProfileManagerWindow(tk.Toplevel):
# ... (__init__ come prima) ...
def __init__(self, parent: 'GDBGui', app_settings: 'AppSettings'):
super().__init__(parent)
self.parent_window = parent
@ -72,8 +73,8 @@ class ProfileManagerWindow(tk.Toplevel):
# StringVars per i conteggi
self.functions_count_var = tk.StringVar(value="Functions: N/A")
# self.variables_count_var = tk.StringVar(value="Globals: N/A") # Futuro
# self.types_count_var = tk.StringVar(value="Types: N/A") # Futuro
self.variables_count_var = tk.StringVar(value="Globals: N/A") # NUOVO
self.types_count_var = tk.StringVar(value="Types: N/A") # NUOVO
# self.sources_count_var = tk.StringVar(value="Sources: N/A") # Futuro
self._load_profiles_from_settings()
@ -90,26 +91,6 @@ class ProfileManagerWindow(tk.Toplevel):
self.target_exe_var.trace_add("write", lambda *args: self._on_target_exe_changed_in_form())
self.program_params_var.trace_add("write", self._mark_form_as_modified)
def _mark_form_as_modified(self, *args):
self._current_profile_modified_in_form = True
def _on_target_exe_changed_in_form(self, *args):
self._mark_form_as_modified(*args)
self._update_analysis_status_display()
def _load_profiles_from_settings(self) -> None:
self._profiles_data = []
loaded_profiles = self.app_settings.get_profiles()
for profile_dict in loaded_profiles:
copied_profile = json.loads(json.dumps(profile_dict)) # Deep copy
if "actions" not in copied_profile or not isinstance(copied_profile["actions"], list):
copied_profile["actions"] = []
if "symbol_analysis" not in copied_profile:
copied_profile["symbol_analysis"] = None
self._profiles_data.append(copied_profile)
self._profiles_list_changed_overall = False
logger.debug(f"Loaded {len(self._profiles_data)} profiles into ProfileManagerWindow.")
def _create_widgets(self) -> None:
main_frame = ttk.Frame(self, padding="10")
main_frame.pack(expand=True, fill=tk.BOTH)
@ -196,15 +177,20 @@ class ProfileManagerWindow(tk.Toplevel):
self.view_functions_button.grid(row=row_s, column=1, padx=(10,5), pady=2, sticky="w")
row_s += 1
# Placeholder per futuri conteggi e bottoni (Iterazione 3)
# ttk.Label(symbols_summary_frame, textvariable=self.variables_count_var).grid(row=row_s, column=0, sticky="w", padx=5, pady=2)
# self.view_variables_button = ttk.Button(symbols_summary_frame, text="View...", command=self._view_analyzed_variables, state=tk.DISABLED, width=8)
# self.view_variables_button.grid(row=row_s, column=1, padx=(10,5), pady=2, sticky="w")
# row_s += 1
# ... (e così via per tipi e sorgenti)
# --- NUOVI Conteggi e Bottoni View per Variabili Globali e Tipi ---
ttk.Label(symbols_summary_frame, textvariable=self.variables_count_var).grid(row=row_s, column=0, sticky="w", padx=5, pady=2)
self.view_variables_button = ttk.Button(symbols_summary_frame, text="View...", command=self._view_analyzed_variables, state=tk.DISABLED, width=8)
self.view_variables_button.grid(row=row_s, column=1, padx=(10,5), pady=2, sticky="w")
row_s += 1
ttk.Label(symbols_summary_frame, textvariable=self.types_count_var).grid(row=row_s, column=0, sticky="w", padx=5, pady=2)
self.view_types_button = ttk.Button(symbols_summary_frame, text="View...", command=self._view_analyzed_types, state=tk.DISABLED, width=8)
self.view_types_button.grid(row=row_s, column=1, padx=(10,5), pady=2, sticky="w")
row_s += 1
# --- FINE NUOVI Widget ---
actions_ui_frame = ttk.LabelFrame(right_pane, text="Debug Actions", padding="10")
actions_ui_frame.grid(row=3, column=0, sticky="nsew", pady=5) # Riga 3
actions_ui_frame.grid(row=3, column=0, sticky="nsew", pady=5)
actions_ui_frame.rowconfigure(0, weight=1)
actions_ui_frame.columnconfigure(0, weight=1)
actions_ui_frame.columnconfigure(1, weight=0)
@ -390,6 +376,31 @@ class ProfileManagerWindow(tk.Toplevel):
self._update_analysis_status_display()
logger.info(f"Profile '{profile_name}' (index {profile_index}) basic details updated.")
return True
def _load_profiles_from_settings(self) -> None:
"""
Loads profiles from application settings into the internal _profiles_data list.
Ensures deep copy and initializes new keys like 'symbol_analysis' if missing.
"""
self._profiles_data = []
loaded_profiles = self.app_settings.get_profiles() # Questo restituisce una copia
for profile_dict in loaded_profiles:
# Effettua una deep copy robusta del dizionario del profilo per evitare modifiche dirette
# ai dati dell'AppSettings fino al salvataggio esplicito.
copied_profile = json.loads(json.dumps(profile_dict))
# Assicura che le chiavi necessarie esistano per la struttura del profilo,
# fornendo valori di default se mancanti.
if "actions" not in copied_profile or not isinstance(copied_profile["actions"], list):
copied_profile["actions"] = []
if "symbol_analysis" not in copied_profile:
copied_profile["symbol_analysis"] = None # Inizializza la chiave di analisi simboli
self._profiles_data.append(copied_profile)
self._profiles_list_changed_overall = False # Reset della flag di modifica complessiva
logger.debug(f"Loaded {len(self._profiles_data)} profiles into ProfileManagerWindow.")
def _new_profile(self) -> None:
if self._selected_profile_index is not None and self._current_profile_modified_in_form:
@ -446,6 +457,10 @@ class ProfileManagerWindow(tk.Toplevel):
self._populate_profiles_listbox()
self._select_profile_by_index(len(self._profiles_data) - 1)
self._mark_form_as_modified()
def _mark_form_as_modified(self, *args):
"""Callback to mark the current profile form as modified when a StringVar is changed."""
self._current_profile_modified_in_form = True
def _delete_profile(self) -> None:
if self._selected_profile_index is None or not self._profiles_data: return
@ -652,6 +667,10 @@ class ProfileManagerWindow(tk.Toplevel):
self.analyse_symbols_button.config(state=tk.DISABLED)
self.functions_count_var.set("Functions: N/A")
self.view_functions_button.config(state=tk.DISABLED)
self.variables_count_var.set("Globals: N/A") # Reset
self.view_variables_button.config(state=tk.DISABLED) # Reset
self.types_count_var.set("Types: N/A") # Reset
self.view_types_button.config(state=tk.DISABLED) # Reset
return
profile = self._profiles_data[self._selected_profile_index]
@ -661,8 +680,14 @@ class ProfileManagerWindow(tk.Toplevel):
details_text_lines = [f"Target in Form: {exe_display_name}"]
status_text = "Symbol Analysis: "
status_color = "blue"
# Inizializza tutti i conteggi a N/A e bottoni View a DISABLED
funcs_count_text = "Functions: N/A"
view_funcs_btn_state = tk.DISABLED
vars_count_text = "Globals: N/A"
view_vars_btn_state = tk.DISABLED
types_count_text = "Types: N/A"
view_types_btn_state = tk.DISABLED
analysis_button_state = tk.DISABLED
if target_exe_in_form and os.path.isfile(target_exe_in_form):
@ -677,11 +702,20 @@ class ProfileManagerWindow(tk.Toplevel):
analysis_data = profile.get("symbol_analysis")
if analysis_data and isinstance(analysis_data, dict):
symbols_dict = analysis_data.get("symbols", {})
# Popola i conteggi e abilita i bottoni View se i dati ci sono
num_functions = symbols_dict.get("functions_count", 0)
funcs_count_text = f"Functions: {num_functions}"
if num_functions > 0 :
view_funcs_btn_state = tk.NORMAL
if num_functions > 0 : view_funcs_btn_state = tk.NORMAL
num_variables = symbols_dict.get("global_variables_count", 0)
vars_count_text = f"Globals: {num_variables}"
if num_variables > 0 : view_vars_btn_state = tk.NORMAL
num_types = symbols_dict.get("types_count", 0)
types_count_text = f"Types: {num_types}"
if num_types > 0 : view_types_btn_state = tk.NORMAL
saved_checksum = analysis_data.get("executable_checksum")
saved_analysis_ts_str = analysis_data.get("analysis_timestamp")
saved_exe_at_analysis = analysis_data.get("analyzed_executable_path", "Unknown")
@ -698,18 +732,24 @@ class ProfileManagerWindow(tk.Toplevel):
if os.path.normpath(saved_exe_at_analysis) != os.path.normpath(target_exe_in_form):
status_text += "TARGET CHANGED since last analysis. RE-ANALYSIS RECOMMENDED."
status_color = "orange red"
view_funcs_btn_state = tk.DISABLED
view_funcs_btn_state = tk.DISABLED # Disabilita bottoni View se il target è cambiato
view_vars_btn_state = tk.DISABLED
view_types_btn_state = tk.DISABLED
elif saved_checksum and current_checksum_for_form_exe and saved_checksum == current_checksum_for_form_exe:
status_text += "Up-to-date."
status_color = "dark green"
elif saved_checksum and current_checksum_for_form_exe and saved_checksum != current_checksum_for_form_exe:
status_text += "EXECUTABLE CHANGED since last analysis. RE-ANALYSIS REQUIRED."
status_color = "red"
view_funcs_btn_state = tk.DISABLED
view_funcs_btn_state = tk.DISABLED # Disabilita bottoni View se l'eseguibile è cambiato
view_vars_btn_state = tk.DISABLED
view_types_btn_state = tk.DISABLED
else:
status_text += "Status unclear. Consider re-analysing."
status_color = "orange red"
view_funcs_btn_state = tk.DISABLED
view_funcs_btn_state = tk.DISABLED # Disabilita bottoni View se lo stato è incerto
view_vars_btn_state = tk.DISABLED
view_types_btn_state = tk.DISABLED
else:
status_text += "Not performed. Click 'Analyse' to generate."
status_color = "blue"
@ -721,6 +761,10 @@ class ProfileManagerWindow(tk.Toplevel):
self.functions_count_var.set(funcs_count_text)
self.view_functions_button.config(state=view_funcs_btn_state)
self.variables_count_var.set(vars_count_text) # Aggiorna
self.view_variables_button.config(state=view_vars_btn_state) # Aggiorna
self.types_count_var.set(types_count_text) # Aggiorna
self.view_types_button.config(state=view_types_btn_state) # Aggiorna
def _trigger_symbol_analysis(self) -> None:
if self._selected_profile_index is None:
@ -742,7 +786,6 @@ class ProfileManagerWindow(tk.Toplevel):
self.progress_dialog = SymbolAnalysisProgressDialog(self)
# Crea l'istanza di SymbolAnalyzer
symbol_analyzer = SymbolAnalyzer(gdb_exe_path, self.app_settings)
analysis_thread = threading.Thread(
@ -753,10 +796,10 @@ class ProfileManagerWindow(tk.Toplevel):
analysis_thread.start()
def _perform_symbol_analysis_thread(self, profile_to_update: Dict[str, Any],
target_exe_path: str, symbol_analyzer: SymbolAnalyzer, # Ora prende SymbolAnalyzer
target_exe_path: str, symbol_analyzer: SymbolAnalyzer,
progress_dialog: SymbolAnalysisProgressDialog):
analysis_data_dict: Dict[str, Any] = {} # Sarà popolato da SymbolAnalyzer.analyze()
analysis_data_dict: Dict[str, Any] = {}
analysis_succeeded_overall = False
def gui_log(msg: str):
@ -771,14 +814,13 @@ class ProfileManagerWindow(tk.Toplevel):
gui_log(f"Starting symbol analysis for: {os.path.basename(target_exe_path)}")
gui_set_status(f"Analyzing {os.path.basename(target_exe_path)}...")
# Chiama il metodo analyze di SymbolAnalyzer
analysis_data_dict = symbol_analyzer.analyze(
target_exe_path=target_exe_path,
progress_callback=gui_log,
status_callback=gui_set_status
)
if analysis_data_dict: # Se non è un dizionario vuoto (errore critico in SymbolAnalyzer)
if analysis_data_dict:
analysis_succeeded_overall = True
gui_set_status("Symbol analysis successfully completed."); gui_log("\nSymbol analysis successfully completed.")
else:
@ -813,6 +855,7 @@ class ProfileManagerWindow(tk.Toplevel):
self._update_analysis_status_display()
# --- Metodi per Visualizzare Simboli Analizzati ---
def _view_analyzed_functions(self) -> None:
if self._selected_profile_index is None or \
not (0 <= self._selected_profile_index < len(self._profiles_data)):
@ -845,4 +888,64 @@ class ProfileManagerWindow(tk.Toplevel):
title_suffix = " (Analysis might be obsolete)" if is_obsolete else ""
dialog_title = f"Analyzed Functions for '{exe_name_for_title}'{title_suffix}"
SymbolListViewerDialog(self, functions_list, title=dialog_title)
SymbolListViewerDialog(self, functions_list, title=dialog_title)
def _view_analyzed_variables(self) -> None: # NUOVO METODO PER VARIABILI GLOBALI
if self._selected_profile_index is None or not (0 <= self._selected_profile_index < len(self._profiles_data)):
messagebox.showinfo("Info", "No profile selected or data available.", parent=self)
return
profile = self._profiles_data[self._selected_profile_index]
analysis_data = profile.get("symbol_analysis")
if not analysis_data or not isinstance(analysis_data.get("symbols"), dict):
messagebox.showinfo("No Analysis Data", "No symbol analysis data available for this profile.", parent=self)
return
variables_list = analysis_data["symbols"].get("global_variables", [])
if not variables_list:
messagebox.showinfo("No Global Variables", "No global variables found in the last analysis for this profile.", parent=self)
return
target_exe_in_form = self.target_exe_var.get()
analyzed_exe_path = analysis_data.get("analyzed_executable_path", "")
exe_name_for_title = os.path.basename(target_exe_in_form) if target_exe_in_form else "Unknown Executable"
is_obsolete = True
if os.path.normpath(analyzed_exe_path) == os.path.normpath(target_exe_in_form):
current_checksum = file_utils.calculate_file_checksum(target_exe_in_form)
saved_checksum = analysis_data.get("executable_checksum")
if current_checksum and saved_checksum and current_checksum == saved_checksum:
is_obsolete = False
title_suffix = " (Analysis might be obsolete)" if is_obsolete else ""
dialog_title = f"Analyzed Global Variables for '{exe_name_for_title}'{title_suffix}"
SymbolListViewerDialog(self, variables_list, title=dialog_title)
def _view_analyzed_types(self) -> None: # NUOVO METODO PER TIPI
if self._selected_profile_index is None or not (0 <= self._selected_profile_index < len(self._profiles_data)):
messagebox.showinfo("Info", "No profile selected or data available.", parent=self)
return
profile = self._profiles_data[self._selected_profile_index]
analysis_data = profile.get("symbol_analysis")
if not analysis_data or not isinstance(analysis_data.get("symbols"), dict):
messagebox.showinfo("No Analysis Data", "No symbol analysis data available for this profile.", parent=self)
return
types_list = analysis_data["symbols"].get("types", [])
if not types_list:
messagebox.showinfo("No Types", "No types found in the last analysis for this profile.", parent=self)
return
target_exe_in_form = self.target_exe_var.get()
analyzed_exe_path = analysis_data.get("analyzed_executable_path", "")
exe_name_for_title = os.path.basename(target_exe_in_form) if target_exe_in_form else "Unknown Executable"
is_obsolete = True
if os.path.normpath(analyzed_exe_path) == os.path.normpath(target_exe_in_form):
current_checksum = file_utils.calculate_file_checksum(target_exe_in_form)
saved_checksum = analysis_data.get("executable_checksum")
if current_checksum and saved_checksum and current_checksum == saved_checksum:
is_obsolete = False
title_suffix = " (Analysis might be obsolete)" if is_obsolete else ""
dialog_title = f"Analyzed Types for '{exe_name_for_title}'{title_suffix}"
SymbolListViewerDialog(self, types_list, title=dialog_title)