add analyze function for exe
This commit is contained in:
parent
4aed5cee70
commit
b6db1c8623
@ -76,6 +76,60 @@ class GDBSession:
|
||||
if self.dumper_options:
|
||||
logger.info(f"Dumper options provided: {self.dumper_options}")
|
||||
|
||||
def get_gdb_version(self, timeout: int = DEFAULT_GDB_OPERATION_TIMEOUT) -> Optional[str]:
|
||||
"""
|
||||
Retrieves the GDB version string.
|
||||
|
||||
Args:
|
||||
timeout: Timeout for the GDB command.
|
||||
|
||||
Returns:
|
||||
The GDB version string (typically the first line of 'gdb --version'),
|
||||
or None if an error occurs or version cannot be parsed.
|
||||
"""
|
||||
if not self.child or not self.child.isalive():
|
||||
# Questo metodo potrebbe essere chiamato anche prima che una sessione completa sia "startata"
|
||||
# per l'analisi dei simboli, quindi potremmo dover avviare GDB solo per questo.
|
||||
# Per ora, assumiamo che sia chiamato su una sessione già avviata,
|
||||
# o che il chiamante gestisca l'avvio/chiusura di una sessione temporanea.
|
||||
# In alternativa, potrebbe essere un metodo statico o una funzione helper
|
||||
# che lancia 'gdb --version' come processo separato.
|
||||
# Per coerenza con gli altri metodi, lo lasciamo come metodo d'istanza.
|
||||
# Se la sessione non è 'start()'ata (cioè non c'è un eseguibile caricato),
|
||||
# GDB potrebbe comunque rispondere a 'show version'.
|
||||
logger.warning("GDB session not fully active, attempting 'show version'.")
|
||||
# Se child non esiste, non possiamo fare nulla qui.
|
||||
# Il chiamante (es. ProfileManagerWindow per l'analisi) dovrà gestire
|
||||
# l'avvio di una sessione GDB se necessario.
|
||||
# Questa implementazione assume che self.child esista.
|
||||
if not self.child:
|
||||
logger.error("No GDB child process available to get version.")
|
||||
return None
|
||||
|
||||
# Usiamo 'show version' che funziona all'interno di una sessione GDB attiva
|
||||
# 'gdb --version' è per l'uso da riga di comando esterna.
|
||||
command = "show version"
|
||||
logger.info(f"Requesting GDB version with command: '{command}'")
|
||||
try:
|
||||
output = self.send_cmd(command, expect_prompt=True, timeout=timeout)
|
||||
# L'output di 'show version' è multiriga. La prima riga è di solito quella che vogliamo.
|
||||
# Esempio:
|
||||
# GNU gdb (GDB) 16.2
|
||||
# Copyright (C) 2024 Free Software Foundation, Inc.
|
||||
# ...
|
||||
if output:
|
||||
first_line = output.splitlines()[0].strip()
|
||||
logger.info(f"GDB version string: {first_line}")
|
||||
return first_line
|
||||
logger.warning("No output received for 'show version' command.")
|
||||
return None
|
||||
except (ConnectionError, TimeoutError) as e:
|
||||
logger.error(f"Error getting GDB version: {e}", exc_info=True)
|
||||
return None
|
||||
except Exception as e_parse:
|
||||
logger.error(f"Error parsing 'show version' output: {e_parse}", exc_info=True)
|
||||
return None
|
||||
|
||||
def start(self, timeout: int = DEFAULT_GDB_OPERATION_TIMEOUT) -> None:
|
||||
command = f'"{self.gdb_path}" --nx --quiet "{self.executable_path}"'
|
||||
logger.info(f"Spawning GDB process: {command} with startup timeout: {timeout}s")
|
||||
@ -140,6 +194,8 @@ class GDBSession:
|
||||
def list_functions(self, regex_filter: Optional[str] = None, timeout: int = DEFAULT_GDB_OPERATION_TIMEOUT) -> List[str]:
|
||||
"""
|
||||
Lists functions known to GDB, optionally filtered by a regex.
|
||||
(Implementazione precedente di list_functions è già abbastanza buona, la riporto qui per completezza
|
||||
assicurandoci che sia allineata con le necessità)
|
||||
|
||||
Args:
|
||||
regex_filter: Optional regex to filter function names.
|
||||
@ -159,109 +215,83 @@ class GDBSession:
|
||||
logger.info(f"Requesting GDB function list with command: '{command}'")
|
||||
functions: List[str] = []
|
||||
try:
|
||||
# Assicurarsi che la paginazione sia disattivata è gestito in start()
|
||||
output = self.send_cmd(command, expect_prompt=True, timeout=timeout)
|
||||
# L'output di 'info functions' può essere complesso.
|
||||
# Esempio:
|
||||
# File my_source.cpp:
|
||||
# 123: void MyClass::myMethod(int);
|
||||
# 456: int anotherFunction();
|
||||
# Non-debugging symbols:
|
||||
# 0x00401000 _start
|
||||
# 0x004010a0 __do_global_dtors_aux
|
||||
#
|
||||
# Cerchiamo di estrarre nomi di funzioni che sembrano validi identificatori C/C++
|
||||
# Questo regex cerca identificatori che possono includere :: e <template parametri>,
|
||||
# spesso terminanti con ( o a volte solo il nome se è un simbolo non-debugging.
|
||||
# È un'euristica e potrebbe aver bisogno di affinamenti.
|
||||
# Prioritizziamo le linee che iniziano con numeri di riga o nomi di file.
|
||||
|
||||
potential_function_lines = []
|
||||
current_file_context = None
|
||||
# Flag per indicare se siamo nella sezione "Non-debugging symbols"
|
||||
in_non_debugging_symbols_section = False
|
||||
|
||||
for line in output.splitlines():
|
||||
line_strip = line.strip()
|
||||
if not line_strip:
|
||||
in_non_debugging_symbols_section = False # Una riga vuota potrebbe resettare la sezione
|
||||
continue
|
||||
|
||||
# Cattura il contesto del file, se presente
|
||||
if line_strip.startswith("All defined functions"): # Ignora questa intestazione comune
|
||||
continue
|
||||
if line_strip.startswith("File "): # Resetta contesto non-debug se incontriamo un nuovo file
|
||||
in_non_debugging_symbols_section = False
|
||||
file_match = re.match(r"File\s+(.+):", line_strip)
|
||||
if file_match:
|
||||
current_file_context = file_match.group(1).strip()
|
||||
logger.debug(f"Function parsing context: File '{current_file_context}'")
|
||||
continue # Passa alla riga successiva
|
||||
|
||||
# Ignora le sezioni "Non-debugging symbols" per ora, a meno che non si vogliano includere
|
||||
if line_strip.startswith("Non-debugging symbols:") or \
|
||||
(line_strip.startswith("0x") and " " in line_strip and not line_strip.endswith(";") and not line_strip.endswith(")")): # Heuristica per simboli non-debugging
|
||||
logger.debug(f"Skipping non-debugging symbol line: {line_strip}")
|
||||
current_file_context = None # Resetta contesto file se entriamo in questa sezione
|
||||
continue
|
||||
|
||||
# Regex per estrarre il nome della funzione da righe tipo "123: void MyNamespace::MyClass<Template>::Method(int);"
|
||||
# o "ReturnType funcName(params)"
|
||||
# Questo cerca qualcosa che assomigli a un nome di funzione prima di una parentesi aperta
|
||||
# o prima di un punto e virgola se non ci sono parametri visibili.
|
||||
# Il nome può contenere '::', '<', '>', '_', alfanumerici.
|
||||
# ^\s*(?:\w+\s+)?([a-zA-Z_][\w:<>()]*?(?:::[a-zA-Z_][\w:<>()]*?)*)\s*\(.*?\);?$
|
||||
# ^\s*(?:[^\s]+\s+)*([a-zA-Z_][\w:<>()~*\s&-]*?(?:::[a-zA-Z_][\w:<>()~*\s&-]*?)*)\s*\(
|
||||
# Il seguente è un tentativo più semplice e robusto
|
||||
# Cerca un identificatore valido seguito da una parentesi aperta '(',
|
||||
# opzionalmente preceduto da tipo di ritorno e numero di riga.
|
||||
# Pattern per identificatore C++: ([a-zA-Z_]\w*(::[a-zA-Z_]\w*)*(<[^>]*>)?(?:\s*const)?)
|
||||
# Regex per matchare: opzionale(numero_riga: tipo_ritorno) NOME_FUNZIONE (parametri) ;
|
||||
# Difficile fare un regex perfetto. Proviamo un approccio più semplice per ora:
|
||||
# cerca un nome valido seguito da '('.
|
||||
if line_strip.startswith("Non-debugging symbols:"):
|
||||
in_non_debugging_symbols_section = True
|
||||
logger.debug("Entering Non-debugging symbols section.")
|
||||
continue
|
||||
|
||||
# Tentativo 1: Estrarre da linee con numero di riga e tipo
|
||||
# es: "123: void MyClass::myMethod(int);"
|
||||
m = re.match(r"^\s*\d+:\s+(?:[\w\s:*&<>~]+\s+)?([a-zA-Z_][\w:<>\s~*&-]*?(?:::[a-zA-Z_][\w:<>\s~*&-]*?)*)\s*\(", line_strip)
|
||||
if m:
|
||||
func_name = m.group(1).strip()
|
||||
# Rimuovi eventuali spazi extra o qualificatori 'const' alla fine se catturati per errore
|
||||
func_name = re.sub(r'\s+const\s*$', '', func_name)
|
||||
if func_name not in functions: # Evita duplicati se GDB è verboso
|
||||
# Se siamo nella sezione non-debugging, i simboli sono spesso solo indirizzo e nome
|
||||
if in_non_debugging_symbols_section:
|
||||
# Esempio: 0x00401000 _start
|
||||
m_non_debug = re.match(r"^\s*0x[0-9a-fA-F]+\s+([a-zA-Z_][\w:<>\.~]*)", line_strip)
|
||||
if m_non_debug:
|
||||
func_name = m_non_debug.group(1)
|
||||
if func_name not in functions:
|
||||
functions.append(func_name)
|
||||
logger.debug(f"Found function (type 1): {func_name}")
|
||||
continue
|
||||
logger.debug(f"Found non-debugging symbol/function: {func_name}")
|
||||
continue # Processa la prossima riga
|
||||
|
||||
# Tentativo 2: Linee che iniziano direttamente con il nome della funzione o tipo di ritorno
|
||||
# es: "int main()" o "MyClass::MyClass()"
|
||||
# ([a-zA-Z_][\w:<>\s~*&-]*?(?:::[a-zA-Z_][\w:<>\s~*&-]*?)*)\s*\(
|
||||
m2 = re.match(r"^\s*(?:[\w\s:*&<>~]+\s+)?([a-zA-Z_][\w:<>\s~*&-]*?(?:::[a-zA-Z_][\w:<>\s~*&-]*?)*)\s*\(", line_strip)
|
||||
if m2:
|
||||
func_name = m2.group(1).strip()
|
||||
func_name = re.sub(r'\s+const\s*$', '', func_name)
|
||||
if func_name and func_name not in functions: # Assicurati che non sia vuoto
|
||||
|
||||
# Pattern per simboli di debug (più strutturati)
|
||||
# Tentativo 1: "numero_riga: [tipo_ritorno] nome_funzione(parametri);"
|
||||
m_debug_line = re.match(r"^\s*\d+:\s+(?:[\w\s:*&<>~\[\]]+\s+)?([a-zA-Z_][\w:<>\s~*&\-\[\]]*?(?:::[a-zA-Z_][\w:<>\s~*&\-\[\]]*?)*)\s*\(", line_strip)
|
||||
if m_debug_line:
|
||||
func_name = m_debug_line.group(1).strip()
|
||||
func_name = re.sub(r'\s+const\s*$', '', func_name).strip() # Rimuovi 'const' alla fine e spazi
|
||||
if func_name and func_name not in functions:
|
||||
functions.append(func_name)
|
||||
logger.debug(f"Found function (type 2): {func_name}")
|
||||
logger.debug(f"Found function (debug, type 1): {func_name}")
|
||||
continue
|
||||
|
||||
# A volte 'info functions' lista solo il nome, specialmente per simboli senza info complete
|
||||
# Es. "_ZN12MyNamespace8MyClassILi1EE11anotherFuncEv" (mangled)
|
||||
# O simboli semplici come "main" su una riga a sé se non c'è file context
|
||||
# Questo è più rischioso perché potrebbe catturare non-funzioni.
|
||||
# Lo attiviamo solo se non c'è un contesto di file attivo (per evitare di prendere nomi di file come funzioni)
|
||||
# e se la linea sembra un identificatore C++ valido.
|
||||
if not current_file_context and re.match(r"^[a-zA-Z_][\w:<>\.~]*$", line_strip) and '(' not in line_strip and ';' not in line_strip:
|
||||
# Evita di aggiungere cose che sono chiaramente tipi o altro
|
||||
if "::" in line_strip or line_strip.startswith("_Z") or (not any(c.islower() for c in line_strip) and len(line_strip) > 4): # Heuristica per nomi mangled o solo maiuscole (costanti?)
|
||||
if line_strip not in functions:
|
||||
functions.append(line_strip)
|
||||
logger.debug(f"Found potential symbol (type 3): {line_strip}")
|
||||
# Tentativo 2: "[tipo_ritorno] nome_funzione(parametri)" (senza numero riga)
|
||||
m_debug_no_line = re.match(r"^\s*(?:[\w\s:*&<>~\[\]]+\s+)?([a-zA-Z_][\w:<>\s~*&\-\[\]]*?(?:::[a-zA-Z_][\w:<>\s~*&\-\[\]]*?)*)\s*\(", line_strip)
|
||||
if m_debug_no_line:
|
||||
func_name = m_debug_no_line.group(1).strip()
|
||||
func_name = re.sub(r'\s+const\s*$', '', func_name).strip()
|
||||
if func_name and func_name not in functions:
|
||||
# Evita di aggiungere tipi o parole chiave come funzioni
|
||||
if not (func_name in ["void", "int", "char", "short", "long", "float", "double", "bool",
|
||||
"class", "struct", "enum", "union", "typename", "template"] or func_name.endswith("operator")):
|
||||
functions.append(func_name)
|
||||
logger.debug(f"Found function (debug, type 2): {func_name}")
|
||||
continue
|
||||
|
||||
|
||||
if not functions and output: # Se non abbiamo trovato nulla con regex ma c'era output
|
||||
logger.warning(f"Could not parse function names reliably from 'info functions' output. Output was:\n{output}")
|
||||
elif functions:
|
||||
if functions:
|
||||
logger.info(f"Successfully parsed {len(functions)} function names.")
|
||||
functions.sort() # Ordina per una visualizzazione migliore
|
||||
functions.sort()
|
||||
elif output: # C'era output ma non abbiamo parsato nulla
|
||||
logger.warning(f"Could not parse any function names from 'info functions' output, though output was received. First 200 chars of output:\n{output[:200]}")
|
||||
|
||||
except (ConnectionError, TimeoutError) as e:
|
||||
logger.error(f"Error listing functions from GDB: {e}", exc_info=True)
|
||||
return [] # Ritorna lista vuota in caso di errore di comunicazione
|
||||
return []
|
||||
except Exception as e_parse:
|
||||
logger.error(f"Error parsing 'info functions' output: {e_parse}", exc_info=True)
|
||||
return [] # Ritorna lista vuota in caso di errore di parsing
|
||||
return []
|
||||
|
||||
return functions
|
||||
|
||||
|
||||
@ -65,21 +65,22 @@ class ActionEditorWindow(tk.Toplevel):
|
||||
def __init__(self, parent: tk.Widget,
|
||||
action_data: Optional[Dict[str, Any]] = None,
|
||||
is_new: bool = True,
|
||||
target_executable_path: Optional[str] = None, # NUOVO PARAMETRO
|
||||
app_settings: Optional[AppSettings] = None): # NUOVO PARAMETRO
|
||||
target_executable_path: Optional[str] = None,
|
||||
app_settings: Optional[AppSettings] = None,
|
||||
symbol_analysis_data: Optional[Dict[str, Any]] = None): # NUOVO PARAMETRO
|
||||
super().__init__(parent)
|
||||
self.parent_window = parent
|
||||
self.is_new_action = is_new
|
||||
self.result: Optional[Dict[str, Any]] = None
|
||||
|
||||
# --- NUOVI ATTRIBUTI ---
|
||||
self.target_executable_path = target_executable_path
|
||||
self.app_settings = app_settings # Necessario per gdb_path, timeouts
|
||||
# --- FINE NUOVI ATTRIBUTI ---
|
||||
self.app_settings = app_settings
|
||||
self.symbol_analysis_data = symbol_analysis_data # NUOVO ATTRIBUTO
|
||||
|
||||
# ... (resto dell'init come prima) ...
|
||||
title = "Add New Action" if self.is_new_action else "Edit Action"
|
||||
self.title(title)
|
||||
self.geometry("650x580") # Aumentato leggermente per il nuovo bottone
|
||||
self.geometry("650x580") # Potrebbe servire aggiustare
|
||||
self.resizable(False, False)
|
||||
|
||||
self.transient(parent)
|
||||
@ -256,6 +257,28 @@ class ActionEditorWindow(tk.Toplevel):
|
||||
|
||||
# --- NUOVA FUNZIONE ---
|
||||
def _browse_functions(self) -> None:
|
||||
# --- LOGICA AGGIORNATA ---
|
||||
functions_to_show: List[str] = []
|
||||
source_of_functions = "live GDB query" # Default
|
||||
|
||||
# 1. Prova a usare i dati di analisi dei simboli pre-calcolati, se validi
|
||||
if self.symbol_analysis_data and isinstance(self.symbol_analysis_data, dict):
|
||||
# Verifica che i dati di analisi siano per l'eseguibile corrente
|
||||
# (confronto semplice del path, idealmente si usa checksum/timestamp in futuro)
|
||||
analyzed_exe = self.symbol_analysis_data.get("analyzed_executable_path")
|
||||
if analyzed_exe and os.path.normpath(analyzed_exe) == os.path.normpath(str(self.target_executable_path)): # Confronta path normalizzati
|
||||
cached_functions = self.symbol_analysis_data.get("symbols", {}).get("functions", [])
|
||||
if isinstance(cached_functions, list) and cached_functions: # Se abbiamo funzioni cachate
|
||||
functions_to_show = cached_functions
|
||||
source_of_functions = "cached analysis"
|
||||
logger.info(f"Using {len(functions_to_show)} cached functions for browsing.")
|
||||
else:
|
||||
logger.info("Cached symbol analysis is for a different executable or path mismatch. Will perform live query if target is valid.")
|
||||
if analyzed_exe: logger.debug(f"Analyzed exe: '{analyzed_exe}', Current target: '{self.target_executable_path}'")
|
||||
|
||||
|
||||
# 2. Se non ci sono funzioni cachate valide, esegui una query live a GDB
|
||||
if not functions_to_show:
|
||||
if not self.target_executable_path or not os.path.isfile(self.target_executable_path):
|
||||
messagebox.showerror("Error", "Target executable for the profile is not set or invalid. Cannot browse functions.", parent=self)
|
||||
return
|
||||
@ -268,68 +291,40 @@ class ActionEditorWindow(tk.Toplevel):
|
||||
messagebox.showerror("GDB Error", f"GDB executable not found or not configured: {gdb_exe_path}", parent=self)
|
||||
return
|
||||
|
||||
# Mostra un messaggio di attesa
|
||||
self.config(cursor="watch")
|
||||
self.update_idletasks()
|
||||
|
||||
temp_gdb_session: Optional[GDBSession] = None
|
||||
functions_list: List[str] = []
|
||||
error_occurred = False
|
||||
|
||||
live_query_error = False
|
||||
try:
|
||||
logger.info(f"Creating temporary GDBSession for function listing. Target: {self.target_executable_path}")
|
||||
# Creiamo una sessione GDBSession senza script dumper e senza opzioni dumper,
|
||||
# serve solo per 'info functions'.
|
||||
temp_gdb_session = GDBSession(
|
||||
gdb_path=gdb_exe_path,
|
||||
executable_path=self.target_executable_path,
|
||||
gdb_script_full_path=None, # Non serve lo script dumper per info functions
|
||||
dumper_options={}
|
||||
)
|
||||
|
||||
# Usiamo timeout brevi per l'avvio e il comando
|
||||
startup_timeout = self.app_settings.get_setting("timeouts", "gdb_start", 30) // 2 # Più breve
|
||||
command_timeout = self.app_settings.get_setting("timeouts", "gdb_command", 30) // 2 # Più breve
|
||||
|
||||
logger.debug("Starting temporary GDB session...")
|
||||
temp_gdb_session.start(timeout=startup_timeout) # Avvia GDB
|
||||
logger.info(f"Performing live GDB query for functions. Target: {self.target_executable_path}")
|
||||
temp_gdb_session = GDBSession(gdb_exe_path, self.target_executable_path, None, {})
|
||||
startup_timeout = self.app_settings.get_setting("timeouts", "gdb_start", 30) // 2
|
||||
command_timeout = self.app_settings.get_setting("timeouts", "gdb_command", 30) // 2
|
||||
|
||||
temp_gdb_session.start(timeout=startup_timeout)
|
||||
if not temp_gdb_session.symbols_found:
|
||||
messagebox.showwarning("No Debug Symbols",
|
||||
"GDB reported no debugging symbols found in the executable. "
|
||||
"The function list might be empty or incomplete.", parent=self)
|
||||
# Continuiamo comunque, GDB potrebbe listare alcuni simboli non-debugging.
|
||||
|
||||
logger.debug("Listing functions...")
|
||||
functions_list = temp_gdb_session.list_functions(timeout=command_timeout)
|
||||
|
||||
except FileNotFoundError as fnf_e:
|
||||
logger.error(f"Error during function browsing (FileNotFound): {fnf_e}", exc_info=True)
|
||||
messagebox.showerror("Error", f"File not found during GDB interaction: {fnf_e}", parent=self)
|
||||
error_occurred = True
|
||||
except (ConnectionError, TimeoutError) as session_e:
|
||||
logger.error(f"Session error during function browsing: {session_e}", exc_info=True)
|
||||
messagebox.showerror("GDB Error", f"Could not communicate with GDB to list functions: {session_e}", parent=self)
|
||||
error_occurred = True
|
||||
"GDB reported no debugging symbols in the executable. "
|
||||
"The function list (live query) might be empty or incomplete.", parent=self)
|
||||
functions_to_show = temp_gdb_session.list_functions(timeout=command_timeout)
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error during function browsing: {e}", exc_info=True)
|
||||
messagebox.showerror("Error", f"An unexpected error occurred: {e}", parent=self)
|
||||
error_occurred = True
|
||||
logger.error(f"Error during live function query: {e}", exc_info=True)
|
||||
messagebox.showerror("GDB Query Error", f"Could not retrieve functions from GDB: {e}", parent=self)
|
||||
live_query_error = True
|
||||
finally:
|
||||
if temp_gdb_session and temp_gdb_session.is_alive():
|
||||
logger.debug("Quitting temporary GDB session...")
|
||||
quit_timeout = self.app_settings.get_setting("timeouts", "gdb_quit", 10) // 2
|
||||
try:
|
||||
temp_gdb_session.quit(timeout=quit_timeout)
|
||||
except Exception as e_quit:
|
||||
logger.error(f"Error quitting temporary GDB session: {e_quit}")
|
||||
self.config(cursor="") # Ripristina cursore
|
||||
try: temp_gdb_session.quit(timeout=quit_timeout)
|
||||
except Exception: pass # Ignora errori nel quit della sessione temporanea
|
||||
self.config(cursor="")
|
||||
if live_query_error: return # Non mostrare la dialog se c'è stato un errore
|
||||
|
||||
if not error_occurred:
|
||||
if functions_list:
|
||||
dialog = FunctionSelectorDialog(self, functions_list)
|
||||
# 3. Mostra la dialog con le funzioni (cachate o da query live)
|
||||
if functions_to_show:
|
||||
logger.info(f"Displaying FunctionSelectorDialog with {len(functions_to_show)} functions from '{source_of_functions}'.")
|
||||
dialog = FunctionSelectorDialog(self, functions_to_show, title=f"Select Function (from {source_of_functions})")
|
||||
selected_function = dialog.result
|
||||
if selected_function:
|
||||
self.breakpoint_var.set(selected_function)
|
||||
else:
|
||||
messagebox.showinfo("No Functions", "No functions found or GDB did not return any.", parent=self)
|
||||
messagebox.showinfo("No Functions", f"No functions found (source: {source_of_functions}). Ensure target is compiled with debug symbols.", parent=self)
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user