fix parameters to analize function
This commit is contained in:
parent
433d19a8f0
commit
ae194c8fe9
@ -3,8 +3,8 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
import json
|
import json # Not directly used for parsing here, but good for context
|
||||||
import wexpect # NEW: Import wexpect
|
import wexpect
|
||||||
from typing import Dict, List, Optional, Any, Callable
|
from typing import Dict, List, Optional, Any, Callable
|
||||||
|
|
||||||
from .gdb_mi_session import GDBMISession
|
from .gdb_mi_session import GDBMISession
|
||||||
@ -42,19 +42,16 @@ class GDBInteractiveInspector:
|
|||||||
or a string like '[name="var1",name="var2"]' (e.g., from print-values=0).
|
or a string like '[name="var1",name="var2"]' (e.g., from print-values=0).
|
||||||
"""
|
"""
|
||||||
names = []
|
names = []
|
||||||
if isinstance(payload_value, list):
|
if isinstance(payload_value, list): # Handles cases like locals=[{name="var1"}, {name="var2"}]
|
||||||
# Handles cases like locals=[{name="var1"}, {name="var2"}]
|
|
||||||
for item in payload_value:
|
for item in payload_value:
|
||||||
if isinstance(item, dict) and "name" in item:
|
if isinstance(item, dict) and "name" in item:
|
||||||
names.append(item["name"])
|
names.append(item["name"])
|
||||||
elif isinstance(payload_value, str):
|
elif isinstance(payload_value, str): # Handles format like 'locals=[name="var1",name="var2"]'
|
||||||
# Handles format like 'locals=[name="var1",name="var2"]' or 'locals=[name="var1"]'
|
|
||||||
# Regex to find name="value" pairs within the string representation of the list
|
|
||||||
logger.debug(f"Parsing string payload for var names: {payload_value}")
|
logger.debug(f"Parsing string payload for var names: {payload_value}")
|
||||||
for match in re.finditer(r'name="((?:[^"\\]|\\.)*?)"', payload_value):
|
for match in re.finditer(r'name="((?:[^"\\]|\\.)*?)"', payload_value):
|
||||||
var_name = match.group(1).replace('\\\\', '\\').replace('\\"', '"')
|
var_name = match.group(1).replace('\\\\', '\\').replace('\\"', '"')
|
||||||
names.append(var_name)
|
names.append(var_name)
|
||||||
return list(set(names)) # Return unique names
|
return list(set(names))
|
||||||
|
|
||||||
|
|
||||||
def get_variables_in_scope(
|
def get_variables_in_scope(
|
||||||
@ -66,15 +63,6 @@ class GDBInteractiveInspector:
|
|||||||
) -> Dict[str, List[str]]:
|
) -> Dict[str, List[str]]:
|
||||||
"""
|
"""
|
||||||
Starts GDB, runs to a breakpoint, and lists locals and arguments.
|
Starts GDB, runs to a breakpoint, and lists locals and arguments.
|
||||||
|
|
||||||
Args:
|
|
||||||
target_executable: Path to the executable.
|
|
||||||
breakpoint_location: Location for the breakpoint (e.g., "main", "file.cpp:123").
|
|
||||||
program_args: Arguments to pass to the program.
|
|
||||||
status_callback: Optional callback for status updates.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A dictionary like {"locals": ["var1", "var2"], "args": ["arg1"]} or empty lists on error.
|
|
||||||
"""
|
"""
|
||||||
if status_callback is None:
|
if status_callback is None:
|
||||||
status_callback = lambda msg: logger.info(f"Inspector Status: {msg}")
|
status_callback = lambda msg: logger.info(f"Inspector Status: {msg}")
|
||||||
@ -101,7 +89,7 @@ class GDBInteractiveInspector:
|
|||||||
|
|
||||||
status_callback(f"Setting breakpoint at: {breakpoint_location}")
|
status_callback(f"Setting breakpoint at: {breakpoint_location}")
|
||||||
bp_output = self._mi_session.send_mi_cmd(f"-break-insert {breakpoint_location}", timeout=cmd_timeout)
|
bp_output = self._mi_session.send_mi_cmd(f"-break-insert {breakpoint_location}", timeout=cmd_timeout)
|
||||||
bp_mi_result = self._mi_session._parse_mi_result(bp_output) # Use the session's parser
|
bp_mi_result = self._mi_session._parse_mi_result(bp_output)
|
||||||
|
|
||||||
if not (bp_mi_result.get("result_type") == "^done" and "bkpt" in bp_mi_result.get("payload", {})):
|
if not (bp_mi_result.get("result_type") == "^done" and "bkpt" in bp_mi_result.get("payload", {})):
|
||||||
status_callback(f"Error: Could not set or parse breakpoint at '{breakpoint_location}'. GDB output:\n{bp_output}")
|
status_callback(f"Error: Could not set or parse breakpoint at '{breakpoint_location}'. GDB output:\n{bp_output}")
|
||||||
@ -110,15 +98,29 @@ class GDBInteractiveInspector:
|
|||||||
return result
|
return result
|
||||||
logger.info(f"Breakpoint set successfully. MI Response: {bp_mi_result}")
|
logger.info(f"Breakpoint set successfully. MI Response: {bp_mi_result}")
|
||||||
|
|
||||||
|
# --- MODIFIED COMMAND EXECUTION ---
|
||||||
|
if program_args and program_args.strip():
|
||||||
|
set_args_command = f"-exec-arguments {program_args.strip()}"
|
||||||
|
logger.info(f"Setting program arguments with MI command: [{set_args_command}]")
|
||||||
|
args_set_output = self._mi_session.send_mi_cmd(set_args_command, timeout=cmd_timeout)
|
||||||
|
args_set_mi_result = self._mi_session._parse_mi_result(args_set_output)
|
||||||
|
if args_set_mi_result.get("result_type") != "^done":
|
||||||
|
status_callback(f"Error: Could not set program arguments. GDB output:\n{args_set_output}")
|
||||||
|
logger.error(f"Setting program arguments failed. Raw: {args_set_output}, Parsed: {args_set_mi_result}")
|
||||||
|
self._cleanup_session()
|
||||||
|
return result
|
||||||
|
logger.info(f"Program arguments set successfully. MI Response: {args_set_mi_result}")
|
||||||
|
|
||||||
status_callback(f"Running program with args: '{program_args}' to hit breakpoint...")
|
status_callback(f"Running program to hit breakpoint...")
|
||||||
exec_command = f"-exec-run --thread-group i1 {program_args}".strip()
|
exec_command = "-exec-run --thread-group i1"
|
||||||
if self._mi_session.child: # Ensure child is not None
|
logger.info(f"Constructed GDB exec command: [{exec_command}]")
|
||||||
|
# --- END MODIFIED COMMAND EXECUTION ---
|
||||||
|
|
||||||
|
if self._mi_session.child:
|
||||||
self._mi_session.child.sendline(exec_command + "\n")
|
self._mi_session.child.sendline(exec_command + "\n")
|
||||||
else:
|
else:
|
||||||
raise ConnectionError("GDB child process not available before -exec-run.")
|
raise ConnectionError("GDB child process not available before -exec-run.")
|
||||||
|
|
||||||
|
|
||||||
stop_event_timeout = run_timeout
|
stop_event_timeout = run_timeout
|
||||||
start_wait_time = time.time()
|
start_wait_time = time.time()
|
||||||
stopped_output_buffer = ""
|
stopped_output_buffer = ""
|
||||||
@ -128,7 +130,7 @@ class GDBInteractiveInspector:
|
|||||||
while time.time() - start_wait_time < stop_event_timeout:
|
while time.time() - start_wait_time < stop_event_timeout:
|
||||||
if not self._mi_session or not self._mi_session.child:
|
if not self._mi_session or not self._mi_session.child:
|
||||||
logger.error("GDB session or child became None during run loop.")
|
logger.error("GDB session or child became None during run loop.")
|
||||||
program_exited = True # Treat as program exit to stop loop
|
program_exited = True
|
||||||
break
|
break
|
||||||
try:
|
try:
|
||||||
chunk = self._mi_session.child.read_nonblocking(size=1024)
|
chunk = self._mi_session.child.read_nonblocking(size=1024)
|
||||||
@ -153,14 +155,12 @@ class GDBInteractiveInspector:
|
|||||||
status_callback("Program exited (EOF detected).")
|
status_callback("Program exited (EOF detected).")
|
||||||
logger.warning(f"EOF detected during program run. Output buffer:\n{stopped_output_buffer}")
|
logger.warning(f"EOF detected during program run. Output buffer:\n{stopped_output_buffer}")
|
||||||
break
|
break
|
||||||
except Exception as e_read: # Catch other potential read errors
|
except Exception as e_read:
|
||||||
status_callback(f"Error reading GDB output: {e_read}")
|
status_callback(f"Error reading GDB output: {e_read}")
|
||||||
logger.error(f"Error reading GDB output during run: {e_read}", exc_info=True)
|
logger.error(f"Error reading GDB output during run: {e_read}", exc_info=True)
|
||||||
# Decide if this is fatal for the inspection
|
program_exited = True
|
||||||
program_exited = True # Assume we cannot proceed
|
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
if not breakpoint_hit and not program_exited:
|
if not breakpoint_hit and not program_exited:
|
||||||
status_callback(f"Timeout ({stop_event_timeout}s) waiting for breakpoint or program exit.")
|
status_callback(f"Timeout ({stop_event_timeout}s) waiting for breakpoint or program exit.")
|
||||||
logger.warning(f"Timeout waiting for breakpoint. Output buffer:\n{stopped_output_buffer}")
|
logger.warning(f"Timeout waiting for breakpoint. Output buffer:\n{stopped_output_buffer}")
|
||||||
@ -172,23 +172,18 @@ class GDBInteractiveInspector:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
if breakpoint_hit:
|
if breakpoint_hit:
|
||||||
if not self._mi_session or not self._mi_session.child: # Check again
|
if not self._mi_session or not self._mi_session.child:
|
||||||
logger.error("GDB session or child became None before fetching locals/args.")
|
logger.error("GDB session or child became None before fetching locals/args.")
|
||||||
self._cleanup_session()
|
self._cleanup_session()
|
||||||
return result
|
return result
|
||||||
try:
|
try:
|
||||||
# Ensure GDB is ready by expecting the prompt. This also consumes the *stopped output.
|
|
||||||
logger.debug(f"Attempting to expect prompt after breakpoint hit. Buffer before expect: {self._mi_session.child.buffer}")
|
logger.debug(f"Attempting to expect prompt after breakpoint hit. Buffer before expect: {self._mi_session.child.buffer}")
|
||||||
self._mi_session.child.expect_exact(self._mi_session.mi_prompt, timeout=5) # Increased timeout slightly
|
self._mi_session.child.expect_exact(self._mi_session.mi_prompt, timeout=5)
|
||||||
logger.debug(f"Prompt received after breakpoint hit. Before prompt: {self._mi_session.child.before}")
|
logger.debug(f"Prompt received after breakpoint hit. Before prompt: {self._mi_session.child.before}")
|
||||||
except (wexpect.TIMEOUT, wexpect.EOF) as e_prompt:
|
except (wexpect.TIMEOUT, wexpect.EOF) as e_prompt:
|
||||||
logger.warning(f"Did not receive prompt immediately after breakpoint hit ({e_prompt}), proceeding cautiously.")
|
logger.warning(f"Did not receive prompt immediately after breakpoint hit ({e_prompt}), proceeding cautiously.")
|
||||||
# The *stopped event might have already been consumed by read_nonblocking loop.
|
|
||||||
# We can still try to send commands.
|
|
||||||
|
|
||||||
|
|
||||||
status_callback("Fetching local variables...")
|
status_callback("Fetching local variables...")
|
||||||
# Use print-values=0 for names only, which is simpler for MI parsing.
|
|
||||||
locals_output = self._mi_session.send_mi_cmd("-stack-list-locals --thread 1 --frame 0 0", timeout=cmd_timeout)
|
locals_output = self._mi_session.send_mi_cmd("-stack-list-locals --thread 1 --frame 0 0", timeout=cmd_timeout)
|
||||||
mi_locals_result = self._mi_session._parse_mi_result(locals_output)
|
mi_locals_result = self._mi_session._parse_mi_result(locals_output)
|
||||||
logger.debug(f"Raw MI Locals Output: {locals_output}")
|
logger.debug(f"Raw MI Locals Output: {locals_output}")
|
||||||
@ -200,7 +195,6 @@ class GDBInteractiveInspector:
|
|||||||
result["locals"] = self._parse_mi_var_list_payload(locals_payload)
|
result["locals"] = self._parse_mi_var_list_payload(locals_payload)
|
||||||
logger.info(f"Found locals: {result['locals']}")
|
logger.info(f"Found locals: {result['locals']}")
|
||||||
|
|
||||||
|
|
||||||
status_callback("Fetching arguments...")
|
status_callback("Fetching arguments...")
|
||||||
args_output = self._mi_session.send_mi_cmd("-stack-list-arguments --thread 1 --frame 0 0", timeout=cmd_timeout)
|
args_output = self._mi_session.send_mi_cmd("-stack-list-arguments --thread 1 --frame 0 0", timeout=cmd_timeout)
|
||||||
mi_args_result = self._mi_session._parse_mi_result(args_output)
|
mi_args_result = self._mi_session._parse_mi_result(args_output)
|
||||||
@ -209,9 +203,8 @@ class GDBInteractiveInspector:
|
|||||||
|
|
||||||
if mi_args_result.get("result_type") == "^done":
|
if mi_args_result.get("result_type") == "^done":
|
||||||
stack_args_payload = mi_args_result.get("payload", {}).get("stack-args")
|
stack_args_payload = mi_args_result.get("payload", {}).get("stack-args")
|
||||||
# stack-args payload is typically a list of frames, e.g., [{level="0", args=[{name="arg1"}, ...]}]
|
|
||||||
if isinstance(stack_args_payload, list) and stack_args_payload:
|
if isinstance(stack_args_payload, list) and stack_args_payload:
|
||||||
frame_args_list_or_string = stack_args_payload[0].get("args") # Args are usually in the first frame
|
frame_args_list_or_string = stack_args_payload[0].get("args")
|
||||||
if frame_args_list_or_string is not None:
|
if frame_args_list_or_string is not None:
|
||||||
result["args"] = self._parse_mi_var_list_payload(frame_args_list_or_string)
|
result["args"] = self._parse_mi_var_list_payload(frame_args_list_or_string)
|
||||||
logger.info(f"Found args: {result['args']}")
|
logger.info(f"Found args: {result['args']}")
|
||||||
@ -220,13 +213,13 @@ class GDBInteractiveInspector:
|
|||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
status_callback(f"Error: {e}")
|
status_callback(f"Error: {e}")
|
||||||
logger.error(f"Inspector FileNotFoundError: {e}", exc_info=True)
|
logger.error(f"Inspector FileNotFoundError: {e}", exc_info=True)
|
||||||
except (ConnectionError, TimeoutError) as e: # This will catch our raised ConnectionError/TimeoutError
|
except (ConnectionError, TimeoutError) as e:
|
||||||
status_callback(f"GDB Session Error: {e}")
|
status_callback(f"GDB Session Error: {e}")
|
||||||
logger.error(f"Inspector GDB Session Error: {e}", exc_info=True)
|
logger.error(f"Inspector GDB Session Error: {e}", exc_info=True)
|
||||||
except wexpect.TIMEOUT as e_wexpect_timeout: # Catch wexpect specific timeout if not caught by expect_exact
|
except wexpect.TIMEOUT as e_wexpect_timeout:
|
||||||
status_callback(f"GDB Communication Timeout: {e_wexpect_timeout}")
|
status_callback(f"GDB Communication Timeout: {e_wexpect_timeout}")
|
||||||
logger.error(f"Inspector wexpect.TIMEOUT: {e_wexpect_timeout}", exc_info=True)
|
logger.error(f"Inspector wexpect.TIMEOUT: {e_wexpect_timeout}", exc_info=True)
|
||||||
except wexpect.EOF as e_wexpect_eof: # Catch wexpect specific EOF
|
except wexpect.EOF as e_wexpect_eof:
|
||||||
status_callback(f"GDB Communication EOF: {e_wexpect_eof}")
|
status_callback(f"GDB Communication EOF: {e_wexpect_eof}")
|
||||||
logger.error(f"Inspector wexpect.EOF: {e_wexpect_eof}", exc_info=True)
|
logger.error(f"Inspector wexpect.EOF: {e_wexpect_eof}", exc_info=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -238,7 +231,6 @@ class GDBInteractiveInspector:
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# Placeholder for test_action_validity - to be implemented later
|
|
||||||
def test_action_validity(
|
def test_action_validity(
|
||||||
self,
|
self,
|
||||||
target_executable: str,
|
target_executable: str,
|
||||||
|
|||||||
@ -59,7 +59,7 @@ class ProfileManagerWindow(tk.Toplevel):
|
|||||||
|
|
||||||
self._profiles_data: List[Dict[str, Any]] = []
|
self._profiles_data: List[Dict[str, Any]] = []
|
||||||
self._selected_profile_index: Optional[int] = None
|
self._selected_profile_index: Optional[int] = None
|
||||||
self._selected_action_index_in_profile: Optional[int] = None # MODIFIED: Initialized to None
|
self._selected_action_index_in_profile: Optional[int] = None
|
||||||
|
|
||||||
self._current_profile_modified_in_form: bool = False
|
self._current_profile_modified_in_form: bool = False
|
||||||
self._profiles_list_changed_overall: bool = False
|
self._profiles_list_changed_overall: bool = False
|
||||||
@ -74,9 +74,9 @@ class ProfileManagerWindow(tk.Toplevel):
|
|||||||
|
|
||||||
# StringVars per i conteggi
|
# StringVars per i conteggi
|
||||||
self.functions_count_var = tk.StringVar(value="Functions: N/A")
|
self.functions_count_var = tk.StringVar(value="Functions: N/A")
|
||||||
self.variables_count_var = tk.StringVar(value="Globals: N/A") # NEW
|
self.variables_count_var = tk.StringVar(value="Globals: N/A")
|
||||||
self.types_count_var = tk.StringVar(value="Types: N/A") # NEW
|
self.types_count_var = tk.StringVar(value="Types: N/A")
|
||||||
self.sources_count_var = tk.StringVar(value="Sources: N/A") # NEW
|
self.sources_count_var = tk.StringVar(value="Sources: N/A")
|
||||||
|
|
||||||
self._load_profiles_from_settings()
|
self._load_profiles_from_settings()
|
||||||
self._create_widgets()
|
self._create_widgets()
|
||||||
@ -153,8 +153,8 @@ class ProfileManagerWindow(tk.Toplevel):
|
|||||||
right_pane.rowconfigure(0, weight=0)
|
right_pane.rowconfigure(0, weight=0)
|
||||||
right_pane.rowconfigure(1, weight=0)
|
right_pane.rowconfigure(1, weight=0)
|
||||||
right_pane.rowconfigure(2, weight=0)
|
right_pane.rowconfigure(2, weight=0)
|
||||||
right_pane.rowconfigure(3, weight=0) # NEW: added row for symbol counts before debug actions
|
right_pane.rowconfigure(3, weight=0)
|
||||||
right_pane.rowconfigure(4, weight=1) # Debug Actions now at row 4
|
right_pane.rowconfigure(4, weight=1)
|
||||||
right_pane.columnconfigure(0, weight=1)
|
right_pane.columnconfigure(0, weight=1)
|
||||||
|
|
||||||
details_form_frame = ttk.LabelFrame(right_pane, text="Profile Details", padding="10")
|
details_form_frame = ttk.LabelFrame(right_pane, text="Profile Details", padding="10")
|
||||||
@ -199,27 +199,23 @@ class ProfileManagerWindow(tk.Toplevel):
|
|||||||
self.view_functions_button.grid(row=row_s, column=1, padx=(10,5), pady=2, sticky="w")
|
self.view_functions_button.grid(row=row_s, column=1, padx=(10,5), pady=2, sticky="w")
|
||||||
row_s += 1
|
row_s += 1
|
||||||
|
|
||||||
# NEW: Global Variables Count and View Button
|
|
||||||
ttk.Label(symbols_summary_frame, textvariable=self.variables_count_var).grid(row=row_s, column=0, sticky="w", padx=5, pady=2)
|
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 = 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")
|
self.view_variables_button.grid(row=row_s, column=1, padx=(10,5), pady=2, sticky="w")
|
||||||
row_s += 1
|
row_s += 1
|
||||||
|
|
||||||
# NEW: Types Count and View Button
|
|
||||||
ttk.Label(symbols_summary_frame, textvariable=self.types_count_var).grid(row=row_s, column=0, sticky="w", padx=5, pady=2)
|
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 = 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")
|
self.view_types_button.grid(row=row_s, column=1, padx=(10,5), pady=2, sticky="w")
|
||||||
row_s += 1
|
row_s += 1
|
||||||
|
|
||||||
# NEW: Source Files Count and View Button
|
|
||||||
ttk.Label(symbols_summary_frame, textvariable=self.sources_count_var).grid(row=row_s, column=0, sticky="w", padx=5, pady=2)
|
ttk.Label(symbols_summary_frame, textvariable=self.sources_count_var).grid(row=row_s, column=0, sticky="w", padx=5, pady=2)
|
||||||
self.view_sources_button = ttk.Button(symbols_summary_frame, text="View...", command=self._view_analyzed_sources, state=tk.DISABLED, width=8)
|
self.view_sources_button = ttk.Button(symbols_summary_frame, text="View...", command=self._view_analyzed_sources, state=tk.DISABLED, width=8)
|
||||||
self.view_sources_button.grid(row=row_s, column=1, padx=(10,5), pady=2, sticky="w")
|
self.view_sources_button.grid(row=row_s, column=1, padx=(10,5), pady=2, sticky="w")
|
||||||
row_s += 1
|
row_s += 1
|
||||||
|
|
||||||
|
|
||||||
actions_ui_frame = ttk.LabelFrame(right_pane, text="Debug Actions", padding="10")
|
actions_ui_frame = ttk.LabelFrame(right_pane, text="Debug Actions", padding="10")
|
||||||
actions_ui_frame.grid(row=4, column=0, sticky="nsew", pady=5) # Row changed to 4
|
actions_ui_frame.grid(row=4, column=0, sticky="nsew", pady=5)
|
||||||
actions_ui_frame.rowconfigure(0, weight=1)
|
actions_ui_frame.rowconfigure(0, weight=1)
|
||||||
actions_ui_frame.columnconfigure(0, weight=1)
|
actions_ui_frame.columnconfigure(0, weight=1)
|
||||||
actions_ui_frame.columnconfigure(1, weight=0)
|
actions_ui_frame.columnconfigure(1, weight=0)
|
||||||
@ -244,7 +240,7 @@ class ProfileManagerWindow(tk.Toplevel):
|
|||||||
self.remove_action_button.pack(fill=tk.X, pady=2, anchor="n")
|
self.remove_action_button.pack(fill=tk.X, pady=2, anchor="n")
|
||||||
|
|
||||||
bottom_buttons_frame = ttk.Frame(main_frame)
|
bottom_buttons_frame = ttk.Frame(main_frame)
|
||||||
bottom_buttons_frame.grid(row=1, column=0, columnspan=2, sticky="sew", pady=(10,0)) # Row changed to 1
|
bottom_buttons_frame.grid(row=1, column=0, columnspan=2, sticky="sew", pady=(10,0))
|
||||||
bottom_buttons_inner_frame = ttk.Frame(bottom_buttons_frame)
|
bottom_buttons_inner_frame = ttk.Frame(bottom_buttons_frame)
|
||||||
bottom_buttons_inner_frame.pack(side=tk.RIGHT)
|
bottom_buttons_inner_frame.pack(side=tk.RIGHT)
|
||||||
|
|
||||||
@ -284,7 +280,7 @@ class ProfileManagerWindow(tk.Toplevel):
|
|||||||
self.profiles_listbox.selection_set(self._selected_profile_index)
|
self.profiles_listbox.selection_set(self._selected_profile_index)
|
||||||
self.profiles_listbox.activate(self._selected_profile_index)
|
self.profiles_listbox.activate(self._selected_profile_index)
|
||||||
return
|
return
|
||||||
elif response is None:
|
elif response is None: # Cancelled switch
|
||||||
self.profiles_listbox.selection_clear(0, tk.END)
|
self.profiles_listbox.selection_clear(0, tk.END)
|
||||||
if self._selected_profile_index is not None:
|
if self._selected_profile_index is not None:
|
||||||
self.profiles_listbox.selection_set(self._selected_profile_index)
|
self.profiles_listbox.selection_set(self._selected_profile_index)
|
||||||
@ -333,7 +329,6 @@ class ProfileManagerWindow(tk.Toplevel):
|
|||||||
self.add_action_button.config(state=tk.NORMAL)
|
self.add_action_button.config(state=tk.NORMAL)
|
||||||
self._update_analysis_status_display()
|
self._update_analysis_status_display()
|
||||||
|
|
||||||
|
|
||||||
def _clear_profile_form(self) -> None:
|
def _clear_profile_form(self) -> None:
|
||||||
self.profile_name_var.set("")
|
self.profile_name_var.set("")
|
||||||
self.target_exe_var.set("")
|
self.target_exe_var.set("")
|
||||||
@ -363,7 +358,7 @@ class ProfileManagerWindow(tk.Toplevel):
|
|||||||
initial_dir = os.path.dirname(current_path) if current_path and os.path.exists(os.path.dirname(current_path)) else None
|
initial_dir = os.path.dirname(current_path) if current_path and os.path.exists(os.path.dirname(current_path)) else None
|
||||||
path = filedialog.askopenfilename(
|
path = filedialog.askopenfilename(
|
||||||
title="Select Target Executable",
|
title="Select Target Executable",
|
||||||
filetypes=[("Executable files", ("*.exe", "*")), ("All files", "*.*")],
|
filetypes=[("Executable files", ("*.exe", "*")), ("All files", "*.*")], # For cross-platform
|
||||||
initialdir=initial_dir,
|
initialdir=initial_dir,
|
||||||
parent=self
|
parent=self
|
||||||
)
|
)
|
||||||
@ -381,6 +376,7 @@ class ProfileManagerWindow(tk.Toplevel):
|
|||||||
self.profile_name_entry.focus_set()
|
self.profile_name_entry.focus_set()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Check for duplicate names, excluding the current profile being edited
|
||||||
for i, p_item in enumerate(self._profiles_data):
|
for i, p_item in enumerate(self._profiles_data):
|
||||||
if i != profile_index and p_item.get("profile_name") == profile_name:
|
if i != profile_index and p_item.get("profile_name") == profile_name:
|
||||||
messagebox.showerror("Validation Error", f"Profile name '{profile_name}' already exists.", parent=self)
|
messagebox.showerror("Validation Error", f"Profile name '{profile_name}' already exists.", parent=self)
|
||||||
@ -393,19 +389,22 @@ class ProfileManagerWindow(tk.Toplevel):
|
|||||||
target_profile["profile_name"] = profile_name
|
target_profile["profile_name"] = profile_name
|
||||||
target_profile["target_executable"] = self.target_exe_var.get().strip()
|
target_profile["target_executable"] = self.target_exe_var.get().strip()
|
||||||
target_profile["program_parameters"] = self.program_params_var.get()
|
target_profile["program_parameters"] = self.program_params_var.get()
|
||||||
|
# symbol_analysis and actions are managed separately
|
||||||
|
|
||||||
self._current_profile_modified_in_form = False
|
self._current_profile_modified_in_form = False
|
||||||
self._profiles_list_changed_overall = True
|
self._profiles_list_changed_overall = True # Mark that something in the profile data changed
|
||||||
|
|
||||||
|
# Update listbox if name changed
|
||||||
if old_name != profile_name:
|
if old_name != profile_name:
|
||||||
self.profiles_listbox.delete(profile_index)
|
self.profiles_listbox.delete(profile_index)
|
||||||
self.profiles_listbox.insert(profile_index, profile_name)
|
self.profiles_listbox.insert(profile_index, profile_name)
|
||||||
self.profiles_listbox.selection_set(profile_index)
|
self.profiles_listbox.selection_set(profile_index)
|
||||||
|
|
||||||
self._update_analysis_status_display()
|
self._update_analysis_status_display() # Reflect changes to target exe
|
||||||
logger.info(f"Profile '{profile_name}' (index {profile_index}) basic details updated.")
|
logger.info(f"Profile '{profile_name}' (index {profile_index}) basic details updated in internal list.")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _new_profile(self) -> None:
|
def _new_profile(self) -> None:
|
||||||
if self._selected_profile_index is not None and self._current_profile_modified_in_form:
|
if self._selected_profile_index is not None and self._current_profile_modified_in_form:
|
||||||
response = messagebox.askyesnocancel("Unsaved Changes",
|
response = messagebox.askyesnocancel("Unsaved Changes",
|
||||||
@ -414,12 +413,13 @@ class ProfileManagerWindow(tk.Toplevel):
|
|||||||
default=messagebox.CANCEL, parent=self)
|
default=messagebox.CANCEL, parent=self)
|
||||||
if response is True:
|
if response is True:
|
||||||
if not self._save_current_form_to_profile_data(self._selected_profile_index):
|
if not self._save_current_form_to_profile_data(self._selected_profile_index):
|
||||||
return
|
return # Save failed, abort new profile creation
|
||||||
elif response is None:
|
elif response is None: # User cancelled
|
||||||
return
|
return
|
||||||
|
|
||||||
new_p = json.loads(json.dumps(DEFAULT_PROFILE))
|
new_p = json.loads(json.dumps(DEFAULT_PROFILE)) # Deep copy of default
|
||||||
|
|
||||||
|
# Generate a unique name
|
||||||
base_name = "New Profile"
|
base_name = "New Profile"
|
||||||
name_candidate = base_name
|
name_candidate = base_name
|
||||||
count = 1
|
count = 1
|
||||||
@ -432,38 +432,42 @@ class ProfileManagerWindow(tk.Toplevel):
|
|||||||
self._profiles_data.append(new_p)
|
self._profiles_data.append(new_p)
|
||||||
self._profiles_list_changed_overall = True
|
self._profiles_list_changed_overall = True
|
||||||
self._populate_profiles_listbox()
|
self._populate_profiles_listbox()
|
||||||
self._select_profile_by_index(len(self._profiles_data) - 1)
|
self._select_profile_by_index(len(self._profiles_data) - 1) # Select the new profile
|
||||||
self.profile_name_entry.focus_set()
|
self.profile_name_entry.focus_set()
|
||||||
self.profile_name_entry.selection_range(0, tk.END)
|
self.profile_name_entry.selection_range(0, tk.END)
|
||||||
self._mark_form_as_modified()
|
self._mark_form_as_modified() # New profile is considered modified until first explicit save of details
|
||||||
|
|
||||||
def _duplicate_profile(self) -> None:
|
def _duplicate_profile(self) -> None:
|
||||||
if self._selected_profile_index is None: return
|
if self._selected_profile_index is None:
|
||||||
|
return
|
||||||
|
|
||||||
if self._current_profile_modified_in_form:
|
if self._current_profile_modified_in_form:
|
||||||
|
# Save changes in the form to the current profile data before duplicating
|
||||||
if not self._save_current_form_to_profile_data(self._selected_profile_index):
|
if not self._save_current_form_to_profile_data(self._selected_profile_index):
|
||||||
return
|
return # Save failed, abort duplication
|
||||||
|
|
||||||
original_profile = self._profiles_data[self._selected_profile_index]
|
original_profile = self._profiles_data[self._selected_profile_index]
|
||||||
duplicated_profile = json.loads(json.dumps(original_profile))
|
duplicated_profile = json.loads(json.dumps(original_profile)) # Deep copy
|
||||||
|
|
||||||
|
# Generate a unique name for the duplicated profile
|
||||||
base_name = f"{original_profile.get('profile_name', 'Profile')}_copy"
|
base_name = f"{original_profile.get('profile_name', 'Profile')}_copy"
|
||||||
name_candidate = base_name
|
name_candidate = base_name
|
||||||
count = 1
|
count = 1
|
||||||
existing_names = {p.get("profile_name") for p in self._profiles_data}
|
existing_names = {p.get("profile_name") for p in self._profiles_data}
|
||||||
while name_candidate in existing_names:
|
while name_candidate in existing_names:
|
||||||
name_candidate = f"{base_name}_{count}"
|
name_candidate = f"{base_name}_{count}" # Use underscore if copy(count) exists
|
||||||
count += 1
|
count += 1
|
||||||
duplicated_profile["profile_name"] = name_candidate
|
duplicated_profile["profile_name"] = name_candidate
|
||||||
|
|
||||||
self._profiles_data.append(duplicated_profile)
|
self._profiles_data.append(duplicated_profile)
|
||||||
self._profiles_list_changed_overall = True
|
self._profiles_list_changed_overall = True
|
||||||
self._populate_profiles_listbox()
|
self._populate_profiles_listbox()
|
||||||
self._select_profile_by_index(len(self._profiles_data) - 1)
|
self._select_profile_by_index(len(self._profiles_data) - 1) # Select the duplicated profile
|
||||||
self._mark_form_as_modified()
|
self._mark_form_as_modified()
|
||||||
|
|
||||||
def _delete_profile(self) -> None:
|
def _delete_profile(self) -> None:
|
||||||
if self._selected_profile_index is None or not self._profiles_data: return
|
if self._selected_profile_index is None or not self._profiles_data:
|
||||||
|
return
|
||||||
|
|
||||||
profile_to_delete = self._profiles_data[self._selected_profile_index]
|
profile_to_delete = self._profiles_data[self._selected_profile_index]
|
||||||
profile_name = profile_to_delete.get("profile_name", "this profile")
|
profile_name = profile_to_delete.get("profile_name", "this profile")
|
||||||
@ -476,64 +480,82 @@ class ProfileManagerWindow(tk.Toplevel):
|
|||||||
self._profiles_list_changed_overall = True
|
self._profiles_list_changed_overall = True
|
||||||
|
|
||||||
new_list_size = len(self._profiles_data)
|
new_list_size = len(self._profiles_data)
|
||||||
self._selected_profile_index = None
|
self._selected_profile_index = None # Reset selection before repopulating
|
||||||
self._populate_profiles_listbox()
|
self._populate_profiles_listbox()
|
||||||
|
|
||||||
if new_list_size == 0:
|
if new_list_size == 0:
|
||||||
self._clear_profile_form()
|
self._clear_profile_form()
|
||||||
else:
|
else:
|
||||||
|
# Try to select a reasonable item after deletion
|
||||||
new_selection_idx = min(idx_to_delete, new_list_size - 1)
|
new_selection_idx = min(idx_to_delete, new_list_size - 1)
|
||||||
if new_selection_idx >= 0:
|
if new_selection_idx >= 0:
|
||||||
self._select_profile_by_index(new_selection_idx)
|
self._select_profile_by_index(new_selection_idx)
|
||||||
else:
|
else: # Should not happen if new_list_size > 0
|
||||||
self._clear_profile_form()
|
self._clear_profile_form()
|
||||||
|
|
||||||
|
|
||||||
def _save_all_profiles_to_settings(self) -> None:
|
def _save_all_profiles_to_settings(self) -> None:
|
||||||
|
# First, ensure any pending changes in the form for the currently selected profile are saved to our internal list
|
||||||
if self._selected_profile_index is not None and self._current_profile_modified_in_form:
|
if self._selected_profile_index is not None and self._current_profile_modified_in_form:
|
||||||
if not self._save_current_form_to_profile_data(self._selected_profile_index):
|
if not self._save_current_form_to_profile_data(self._selected_profile_index):
|
||||||
messagebox.showerror("Save Error", "Could not save changes from the form for the selected profile. Aborting save to settings.", parent=self)
|
messagebox.showerror("Save Error", "Could not save changes from the form for the selected profile. Aborting save to settings.", parent=self)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Basic validation on all profiles before saving to settings
|
||||||
profile_names_seen = set()
|
profile_names_seen = set()
|
||||||
for i, profile in enumerate(self._profiles_data):
|
for i, profile in enumerate(self._profiles_data):
|
||||||
name = profile.get("profile_name", "").strip()
|
name = profile.get("profile_name", "").strip()
|
||||||
if not name:
|
if not name:
|
||||||
messagebox.showerror("Validation Error", f"Profile at index {i} has an empty name.", parent=self)
|
messagebox.showerror("Validation Error", f"Profile at index {i} (0-based) has an empty name. Please provide a name.", parent=self)
|
||||||
|
if self._selected_profile_index != i: # If it's not the current one
|
||||||
|
self._select_profile_by_index(i) # Select it so user can fix
|
||||||
|
self.profile_name_entry.focus_set()
|
||||||
return
|
return
|
||||||
if name in profile_names_seen:
|
if name in profile_names_seen:
|
||||||
messagebox.showerror("Validation Error", f"Duplicate profile name '{name}'.", parent=self)
|
messagebox.showerror("Validation Error", f"Duplicate profile name '{name}' found. Profile names must be unique.", parent=self)
|
||||||
|
# Attempt to select the first instance of the duplicate if not current
|
||||||
|
first_occurrence_index = next((idx for idx, p in enumerate(self._profiles_data) if p.get("profile_name") == name), -1)
|
||||||
|
if first_occurrence_index != -1 and self._selected_profile_index != first_occurrence_index :
|
||||||
|
self._select_profile_by_index(first_occurrence_index)
|
||||||
|
self.profile_name_entry.focus_set()
|
||||||
return
|
return
|
||||||
profile_names_seen.add(name)
|
profile_names_seen.add(name)
|
||||||
|
|
||||||
|
# Validate actions structure (basic check)
|
||||||
actions = profile.get("actions")
|
actions = profile.get("actions")
|
||||||
if not isinstance(actions, list):
|
if not isinstance(actions, list): # Must be a list
|
||||||
messagebox.showerror("Data Error", f"Profile '{name}' has malformed actions.", parent=self)
|
messagebox.showerror("Data Error", f"Profile '{name}' has malformed actions data (not a list). Please correct or re-create actions.", parent=self)
|
||||||
return
|
return
|
||||||
for idx_a, action in enumerate(actions):
|
for idx_a, action in enumerate(actions):
|
||||||
if not isinstance(action, dict):
|
if not isinstance(action, dict): # Each action must be a dictionary
|
||||||
messagebox.showerror("Data Error", f"Profile '{name}', action {idx_a+1} is malformed.", parent=self)
|
messagebox.showerror("Data Error", f"Profile '{name}', action #{idx_a+1} is malformed (not a dictionary). Please re-create this action.", parent=self)
|
||||||
return
|
return
|
||||||
|
# Further validation of action content could be added here if needed
|
||||||
|
|
||||||
|
# If all validations pass, save to settings
|
||||||
self.app_settings.set_profiles(self._profiles_data)
|
self.app_settings.set_profiles(self._profiles_data)
|
||||||
if self.app_settings.save_settings():
|
if self.app_settings.save_settings():
|
||||||
logger.info(f"All {len(self._profiles_data)} profiles saved to AppSettings.")
|
logger.info(f"All {len(self._profiles_data)} profiles saved to AppSettings.")
|
||||||
messagebox.showinfo("Profiles Saved", "All profile changes have been saved.", parent=self)
|
messagebox.showinfo("Profiles Saved", "All profile changes have been saved.", parent=self)
|
||||||
self._current_profile_modified_in_form = False
|
self._current_profile_modified_in_form = False # Reset modified flag for current form
|
||||||
self._profiles_list_changed_overall = False
|
self._profiles_list_changed_overall = False # Reset overall change flag
|
||||||
else:
|
else:
|
||||||
messagebox.showerror("Save Error", "Could not save profiles to the settings file. Check logs.", parent=self)
|
messagebox.showerror("Save Error", "Could not save profiles to the settings file. Check logs.", parent=self)
|
||||||
|
|
||||||
def _on_closing_button(self) -> None:
|
def _on_closing_button(self) -> None:
|
||||||
needs_save_prompt = False
|
needs_save_prompt = False
|
||||||
prompt_message = ""
|
prompt_message = ""
|
||||||
|
|
||||||
|
# Check if current form has unsaved changes
|
||||||
if self._selected_profile_index is not None and self._current_profile_modified_in_form:
|
if self._selected_profile_index is not None and self._current_profile_modified_in_form:
|
||||||
needs_save_prompt = True
|
needs_save_prompt = True
|
||||||
profile_name = self.profile_name_var.get() or self._profiles_data[self._selected_profile_index].get('profile_name', 'current profile')
|
profile_name = self.profile_name_var.get() or self._profiles_data[self._selected_profile_index].get('profile_name', 'current profile')
|
||||||
prompt_message = f"Profile '{profile_name}' has unsaved changes in the form.\n"
|
prompt_message = f"Profile '{profile_name}' has unsaved changes in the form.\n"
|
||||||
|
|
||||||
|
# Check if the overall list of profiles has changed (e.g., new profile, deleted, duplicated, or content of other profiles changed)
|
||||||
if self._profiles_list_changed_overall:
|
if self._profiles_list_changed_overall:
|
||||||
needs_save_prompt = True
|
needs_save_prompt = True
|
||||||
if prompt_message:
|
if prompt_message: # If already have a message for current form
|
||||||
prompt_message += "Additionally, the overall list of profiles (or their content) has changed.\n"
|
prompt_message += "Additionally, the overall list of profiles (or their content) has changed.\n"
|
||||||
else:
|
else:
|
||||||
prompt_message = "The list of profiles (or their content) has changed.\n"
|
prompt_message = "The list of profiles (or their content) has changed.\n"
|
||||||
@ -541,27 +563,33 @@ class ProfileManagerWindow(tk.Toplevel):
|
|||||||
if needs_save_prompt:
|
if needs_save_prompt:
|
||||||
prompt_message += "Do you want to save all changes before closing?"
|
prompt_message += "Do you want to save all changes before closing?"
|
||||||
response = messagebox.askyesnocancel("Unsaved Changes", prompt_message, default=messagebox.CANCEL, parent=self)
|
response = messagebox.askyesnocancel("Unsaved Changes", prompt_message, default=messagebox.CANCEL, parent=self)
|
||||||
if response is True:
|
if response is True: # Save
|
||||||
self._save_all_profiles_to_settings()
|
self._save_all_profiles_to_settings()
|
||||||
elif response is None:
|
# If save was successful, _profiles_list_changed_overall and _current_profile_modified_in_form are reset
|
||||||
|
# So, we can proceed to close. If save failed, the user saw an error, they might choose to close or not.
|
||||||
|
elif response is None: # Cancel close
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# If analysis is running (progress_dialog exists and is visible)
|
||||||
if self.progress_dialog and self.progress_dialog.winfo_exists():
|
if self.progress_dialog and self.progress_dialog.winfo_exists():
|
||||||
logger.warning("Closing ProfileManagerWindow while symbol analysis dialog might be open.")
|
logger.warning("Closing ProfileManagerWindow while symbol analysis dialog might be open. This should ideally be handled (e.g., by disabling close or stopping analysis).")
|
||||||
|
# For now, we'll just log it. A more robust solution might involve:
|
||||||
|
# 1. Disabling the close button of ProfileManagerWindow while analysis is running.
|
||||||
|
# 2. Prompting the user to stop the analysis.
|
||||||
|
# 3. Or, ensuring the analysis thread is properly managed on close.
|
||||||
|
|
||||||
self.parent_window.focus_set()
|
self.parent_window.focus_set() # Return focus to main window
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
# --- Gestione Azioni (Listbox e Bottoni Azione) ---
|
|
||||||
def _populate_actions_listbox(self) -> None:
|
def _populate_actions_listbox(self) -> None:
|
||||||
self.actions_listbox.delete(0, tk.END)
|
self.actions_listbox.delete(0, tk.END)
|
||||||
self._selected_action_index_in_profile = None
|
self._selected_action_index_in_profile = None # Reset selection
|
||||||
if self._selected_profile_index is not None and \
|
if self._selected_profile_index is not None and \
|
||||||
0 <= self._selected_profile_index < len(self._profiles_data):
|
0 <= self._selected_profile_index < len(self._profiles_data):
|
||||||
profile = self._profiles_data[self._selected_profile_index]
|
profile = self._profiles_data[self._selected_profile_index]
|
||||||
actions = profile.get("actions", [])
|
actions = profile.get("actions", [])
|
||||||
for i, action in enumerate(actions):
|
for i, action in enumerate(actions):
|
||||||
bp = action.get("breakpoint_location", "N/A")[:30]
|
bp = action.get("breakpoint_location", "N/A")[:30] # Truncate for display
|
||||||
num_vars = len(action.get("variables_to_dump", []))
|
num_vars = len(action.get("variables_to_dump", []))
|
||||||
fmt = action.get("output_format", "N/A")
|
fmt = action.get("output_format", "N/A")
|
||||||
cont = "Yes" if action.get("continue_after_dump", False) else "No"
|
cont = "Yes" if action.get("continue_after_dump", False) else "No"
|
||||||
@ -580,158 +608,179 @@ class ProfileManagerWindow(tk.Toplevel):
|
|||||||
def _update_action_buttons_state(self) -> None:
|
def _update_action_buttons_state(self) -> None:
|
||||||
profile_selected = self._selected_profile_index is not None
|
profile_selected = self._selected_profile_index is not None
|
||||||
action_selected = self._selected_action_index_in_profile is not None
|
action_selected = self._selected_action_index_in_profile is not None
|
||||||
|
|
||||||
self.add_action_button.config(state=tk.NORMAL if profile_selected else tk.DISABLED)
|
self.add_action_button.config(state=tk.NORMAL if profile_selected else tk.DISABLED)
|
||||||
self.edit_action_button.config(state=tk.NORMAL if action_selected else tk.DISABLED)
|
self.edit_action_button.config(state=tk.NORMAL if action_selected else tk.DISABLED)
|
||||||
self.remove_action_button.config(state=tk.NORMAL if action_selected else tk.DISABLED)
|
self.remove_action_button.config(state=tk.NORMAL if action_selected else tk.DISABLED)
|
||||||
|
|
||||||
def _add_action(self) -> None:
|
def _add_action(self) -> None:
|
||||||
if self._selected_profile_index is None: return
|
if self._selected_profile_index is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Save current profile form data to internal list if modified, before opening action editor
|
||||||
|
if self._current_profile_modified_in_form:
|
||||||
|
if not self._save_current_form_to_profile_data(self._selected_profile_index):
|
||||||
|
return # Abort if save failed
|
||||||
|
|
||||||
current_profile = self._profiles_data[self._selected_profile_index]
|
current_profile = self._profiles_data[self._selected_profile_index]
|
||||||
current_profile_target_exe = self.target_exe_var.get()
|
current_profile_target_exe = current_profile.get("target_executable", "") # Get from saved data
|
||||||
|
current_profile_program_params = current_profile.get("program_parameters", "") # Get from saved data
|
||||||
symbol_analysis_data = current_profile.get("symbol_analysis")
|
symbol_analysis_data = current_profile.get("symbol_analysis")
|
||||||
|
|
||||||
editor = ActionEditorWindow(
|
editor = ActionEditorWindow(
|
||||||
self, action_data=None, is_new=True,
|
self,
|
||||||
|
action_data=None, # For new action
|
||||||
|
is_new=True,
|
||||||
target_executable_path=current_profile_target_exe,
|
target_executable_path=current_profile_target_exe,
|
||||||
app_settings=self.app_settings,
|
app_settings=self.app_settings,
|
||||||
symbol_analysis_data=symbol_analysis_data
|
symbol_analysis_data=symbol_analysis_data,
|
||||||
|
program_parameters_for_scope=current_profile_program_params # Pass program params
|
||||||
)
|
)
|
||||||
new_action_data = editor.get_result()
|
new_action_data = editor.get_result()
|
||||||
|
|
||||||
if new_action_data:
|
if new_action_data:
|
||||||
if "actions" not in current_profile or not isinstance(current_profile["actions"], list):
|
if "actions" not in current_profile or not isinstance(current_profile["actions"], list):
|
||||||
current_profile["actions"] = []
|
current_profile["actions"] = [] # Initialize if missing or malformed
|
||||||
current_profile["actions"].append(new_action_data)
|
current_profile["actions"].append(new_action_data)
|
||||||
self._profiles_list_changed_overall = True
|
self._profiles_list_changed_overall = True # Mark that overall list has changed
|
||||||
self._current_profile_modified_in_form = True
|
# self._current_profile_modified_in_form = True # This is more for the profile details form
|
||||||
self._populate_actions_listbox()
|
self._populate_actions_listbox()
|
||||||
self.actions_listbox.selection_set(tk.END)
|
self.actions_listbox.selection_set(tk.END) # Select the new action
|
||||||
self._on_action_select_in_listbox()
|
self._on_action_select_in_listbox() # Update button states
|
||||||
|
|
||||||
def _edit_action(self) -> None:
|
def _edit_action(self) -> None:
|
||||||
if self._selected_profile_index is None or self._selected_action_index_in_profile is None: return
|
if self._selected_profile_index is None or self._selected_action_index_in_profile is None:
|
||||||
current_profile = self._profiles_data[self._selected_profile_index]
|
return
|
||||||
|
|
||||||
|
# Save current profile form data to internal list if modified
|
||||||
|
if self._current_profile_modified_in_form:
|
||||||
|
if not self._save_current_form_to_profile_data(self._selected_profile_index):
|
||||||
|
return
|
||||||
|
|
||||||
|
current_profile = self._profiles_data[self._selected_profile_index]
|
||||||
actions_list = current_profile.get("actions", [])
|
actions_list = current_profile.get("actions", [])
|
||||||
if not (0 <= self._selected_action_index_in_profile < len(actions_list)): return
|
|
||||||
|
if not (0 <= self._selected_action_index_in_profile < len(actions_list)):
|
||||||
|
logger.error("Selected action index is out of bounds for the current profile's actions.")
|
||||||
|
return
|
||||||
|
|
||||||
action_to_edit = actions_list[self._selected_action_index_in_profile]
|
action_to_edit = actions_list[self._selected_action_index_in_profile]
|
||||||
current_profile_target_exe = self.target_exe_var.get()
|
current_profile_target_exe = current_profile.get("target_executable", "")
|
||||||
|
current_profile_program_params = current_profile.get("program_parameters", "")
|
||||||
symbol_analysis_data = current_profile.get("symbol_analysis")
|
symbol_analysis_data = current_profile.get("symbol_analysis")
|
||||||
|
|
||||||
editor = ActionEditorWindow(
|
editor = ActionEditorWindow(
|
||||||
self, action_data=action_to_edit, is_new=False,
|
self,
|
||||||
|
action_data=action_to_edit,
|
||||||
|
is_new=False,
|
||||||
target_executable_path=current_profile_target_exe,
|
target_executable_path=current_profile_target_exe,
|
||||||
app_settings=self.app_settings,
|
app_settings=self.app_settings,
|
||||||
symbol_analysis_data=symbol_analysis_data
|
symbol_analysis_data=symbol_analysis_data,
|
||||||
|
program_parameters_for_scope=current_profile_program_params # Pass program params
|
||||||
)
|
)
|
||||||
updated_action_data = editor.get_result()
|
updated_action_data = editor.get_result()
|
||||||
|
|
||||||
if updated_action_data:
|
if updated_action_data:
|
||||||
current_profile["actions"][self._selected_action_index_in_profile] = updated_action_data
|
current_profile["actions"][self._selected_action_index_in_profile] = updated_action_data
|
||||||
self._profiles_list_changed_overall = True
|
self._profiles_list_changed_overall = True
|
||||||
self._current_profile_modified_in_form = True
|
# self._current_profile_modified_in_form = True
|
||||||
|
idx_to_reselect = self._selected_action_index_in_profile # Preserve selection
|
||||||
idx_to_reselect = self._selected_action_index_in_profile
|
|
||||||
self._populate_actions_listbox()
|
self._populate_actions_listbox()
|
||||||
if idx_to_reselect is not None and 0 <= idx_to_reselect < self.actions_listbox.size():
|
if idx_to_reselect is not None and 0 <= idx_to_reselect < self.actions_listbox.size():
|
||||||
self.actions_listbox.selection_set(idx_to_reselect)
|
self.actions_listbox.selection_set(idx_to_reselect)
|
||||||
self._on_action_select_in_listbox()
|
self._on_action_select_in_listbox()
|
||||||
|
|
||||||
def _remove_action(self) -> None:
|
def _remove_action(self) -> None:
|
||||||
if self._selected_profile_index is None or self._selected_action_index_in_profile is None: return
|
if self._selected_profile_index is None or self._selected_action_index_in_profile is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# No need to save form here as removing an action doesn't depend on form values
|
||||||
|
# but on the selected action in the listbox.
|
||||||
|
|
||||||
profile = self._profiles_data[self._selected_profile_index]
|
profile = self._profiles_data[self._selected_profile_index]
|
||||||
action_summary_to_delete = self.actions_listbox.get(self._selected_action_index_in_profile)
|
action_summary_to_delete = self.actions_listbox.get(self._selected_action_index_in_profile)
|
||||||
|
|
||||||
if not messagebox.askyesno("Confirm Delete Action",
|
if not messagebox.askyesno("Confirm Delete Action",
|
||||||
f"Are you sure you want to delete this action?\n\n{action_summary_to_delete}",
|
f"Are you sure you want to delete this action?\n\n{action_summary_to_delete}",
|
||||||
parent=self): return
|
parent=self):
|
||||||
|
return
|
||||||
|
|
||||||
actions_list = profile.get("actions")
|
actions_list = profile.get("actions")
|
||||||
if isinstance(actions_list, list) and 0 <= self._selected_action_index_in_profile < len(actions_list):
|
if isinstance(actions_list, list) and 0 <= self._selected_action_index_in_profile < len(actions_list):
|
||||||
del actions_list[self._selected_action_index_in_profile]
|
del actions_list[self._selected_action_index_in_profile]
|
||||||
self._profiles_list_changed_overall = True
|
self._profiles_list_changed_overall = True
|
||||||
self._current_profile_modified_in_form = True
|
# self._current_profile_modified_in_form = True # Modifying actions changes the profile
|
||||||
idx_to_reselect_after_delete = self._selected_action_index_in_profile
|
idx_to_reselect_after_delete = self._selected_action_index_in_profile
|
||||||
self._populate_actions_listbox()
|
self._populate_actions_listbox() # Repopulates and resets selection
|
||||||
num_actions_remaining = len(actions_list)
|
num_actions_remaining = len(actions_list)
|
||||||
if num_actions_remaining > 0:
|
if num_actions_remaining > 0:
|
||||||
new_selection = min(idx_to_reselect_after_delete, num_actions_remaining - 1)
|
new_selection = min(idx_to_reselect_after_delete, num_actions_remaining - 1)
|
||||||
if new_selection >=0: self.actions_listbox.selection_set(new_selection)
|
if new_selection >=0:
|
||||||
self._on_action_select_in_listbox()
|
self.actions_listbox.selection_set(new_selection)
|
||||||
else: logger.error("Could not remove action: 'actions' list missing or index invalid.")
|
self._on_action_select_in_listbox() # Update button states based on new selection (or lack thereof)
|
||||||
|
else:
|
||||||
|
logger.error("Could not remove action: 'actions' list missing or index invalid for the current profile.")
|
||||||
|
|
||||||
# --- Logica per Analisi Simboli ---
|
|
||||||
def _update_analysis_status_display(self) -> None:
|
def _update_analysis_status_display(self) -> None:
|
||||||
if self._selected_profile_index is None or not (0 <= self._selected_profile_index < len(self._profiles_data)):
|
if self._selected_profile_index is None or not (0 <= self._selected_profile_index < len(self._profiles_data)):
|
||||||
self._current_profile_target_exe_details_label_var.set("Target: N/A")
|
self._current_profile_target_exe_details_label_var.set("Target: N/A")
|
||||||
self._current_profile_analysis_status_label_var.set("Symbol Analysis: Select a profile.")
|
self._current_profile_analysis_status_label_var.set("Symbol Analysis: Select a profile.")
|
||||||
self.analyse_symbols_button.config(state=tk.DISABLED)
|
self.analyse_symbols_button.config(state=tk.DISABLED)
|
||||||
# Reset all counts and view buttons
|
|
||||||
self.functions_count_var.set("Functions: N/A")
|
self.functions_count_var.set("Functions: N/A")
|
||||||
self.view_functions_button.config(state=tk.DISABLED)
|
self.view_functions_button.config(state=tk.DISABLED)
|
||||||
self.variables_count_var.set("Globals: N/A") # NEW
|
self.variables_count_var.set("Globals: N/A")
|
||||||
self.view_variables_button.config(state=tk.DISABLED) # NEW
|
self.view_variables_button.config(state=tk.DISABLED)
|
||||||
self.types_count_var.set("Types: N/A") # NEW
|
self.types_count_var.set("Types: N/A")
|
||||||
self.view_types_button.config(state=tk.DISABLED) # NEW
|
self.view_types_button.config(state=tk.DISABLED)
|
||||||
self.sources_count_var.set("Sources: N/A") # NEW
|
self.sources_count_var.set("Sources: N/A")
|
||||||
self.view_sources_button.config(state=tk.DISABLED) # NEW
|
self.view_sources_button.config(state=tk.DISABLED)
|
||||||
return
|
return
|
||||||
|
|
||||||
profile = self._profiles_data[self._selected_profile_index]
|
profile = self._profiles_data[self._selected_profile_index]
|
||||||
target_exe_in_form = self.target_exe_var.get()
|
target_exe_in_form = self.target_exe_var.get() # Use current form value for checks
|
||||||
|
|
||||||
exe_display_name = os.path.basename(target_exe_in_form) if target_exe_in_form else "N/A"
|
exe_display_name = os.path.basename(target_exe_in_form) if target_exe_in_form else "N/A"
|
||||||
details_text_lines = [f"Target in Form: {exe_display_name}"]
|
details_text_lines = [f"Target in Form: {exe_display_name}"]
|
||||||
status_text = "Symbol Analysis: "
|
status_text = "Symbol Analysis: "
|
||||||
status_color = "blue"
|
status_color = "blue"
|
||||||
|
|
||||||
# Initialize counts and button states
|
|
||||||
funcs_count_text = "Functions: N/A"
|
funcs_count_text = "Functions: N/A"
|
||||||
view_funcs_btn_state = tk.DISABLED
|
view_funcs_btn_state = tk.DISABLED
|
||||||
vars_count_text = "Globals: N/A" # NEW
|
vars_count_text = "Globals: N/A"
|
||||||
view_vars_btn_state = tk.DISABLED # NEW
|
view_vars_btn_state = tk.DISABLED
|
||||||
types_count_text = "Types: N/A" # NEW
|
types_count_text = "Types: N/A"
|
||||||
view_types_btn_state = tk.DISABLED # NEW
|
view_types_btn_state = tk.DISABLED
|
||||||
sources_count_text = "Sources: N/A" # NEW
|
sources_count_text = "Sources: N/A"
|
||||||
view_sources_btn_state = tk.DISABLED # NEW
|
view_sources_btn_state = tk.DISABLED
|
||||||
|
|
||||||
analysis_button_state = tk.DISABLED
|
analysis_button_state = tk.DISABLED
|
||||||
if target_exe_in_form and os.path.isfile(target_exe_in_form):
|
if target_exe_in_form and os.path.isfile(target_exe_in_form):
|
||||||
analysis_button_state = tk.NORMAL
|
analysis_button_state = tk.NORMAL # Can analyse if exe path is valid
|
||||||
|
|
||||||
if not target_exe_in_form:
|
if not target_exe_in_form:
|
||||||
status_text += "Target executable not specified in form."
|
status_text += "Target executable not specified in form."
|
||||||
elif not os.path.isfile(target_exe_in_form):
|
elif not os.path.isfile(target_exe_in_form):
|
||||||
status_text += f"Target '{exe_display_name}' not found on disk."
|
status_text += f"Target '{exe_display_name}' not found on disk."
|
||||||
status_color = "red"
|
status_color = "red"
|
||||||
else:
|
else: # Target in form is a valid file
|
||||||
analysis_data = profile.get("symbol_analysis")
|
analysis_data = profile.get("symbol_analysis") # Use analysis data from the *profile object*
|
||||||
if analysis_data and isinstance(analysis_data, dict):
|
if analysis_data and isinstance(analysis_data, dict):
|
||||||
symbols_dict = analysis_data.get("symbols", {})
|
symbols_dict = analysis_data.get("symbols", {})
|
||||||
|
|
||||||
# Functions
|
|
||||||
num_functions = symbols_dict.get("functions_count", 0)
|
num_functions = symbols_dict.get("functions_count", 0)
|
||||||
funcs_count_text = f"Functions: {num_functions}"
|
funcs_count_text = f"Functions: {num_functions}"
|
||||||
if num_functions > 0 :
|
if num_functions > 0: view_funcs_btn_state = tk.NORMAL
|
||||||
view_funcs_btn_state = tk.NORMAL
|
|
||||||
|
|
||||||
# Global Variables (NEW)
|
|
||||||
num_variables = symbols_dict.get("global_variables_count", 0)
|
num_variables = symbols_dict.get("global_variables_count", 0)
|
||||||
vars_count_text = f"Globals: {num_variables}"
|
vars_count_text = f"Globals: {num_variables}"
|
||||||
if num_variables > 0 :
|
if num_variables > 0: view_vars_btn_state = tk.NORMAL
|
||||||
view_vars_btn_state = tk.NORMAL
|
|
||||||
|
|
||||||
# Types (NEW)
|
|
||||||
num_types = symbols_dict.get("types_count", 0)
|
num_types = symbols_dict.get("types_count", 0)
|
||||||
types_count_text = f"Types: {num_types}"
|
types_count_text = f"Types: {num_types}"
|
||||||
if num_types > 0 :
|
if num_types > 0: view_types_btn_state = tk.NORMAL
|
||||||
view_types_btn_state = tk.NORMAL
|
|
||||||
|
|
||||||
# Source Files (NEW)
|
|
||||||
num_sources = symbols_dict.get("source_files_count", 0)
|
num_sources = symbols_dict.get("source_files_count", 0)
|
||||||
sources_count_text = f"Sources: {num_sources}"
|
sources_count_text = f"Sources: {num_sources}"
|
||||||
if num_sources > 0 :
|
if num_sources > 0: view_sources_btn_state = tk.NORMAL
|
||||||
view_sources_btn_state = tk.NORMAL
|
|
||||||
|
|
||||||
|
|
||||||
saved_checksum = analysis_data.get("executable_checksum")
|
saved_checksum = analysis_data.get("executable_checksum")
|
||||||
saved_analysis_ts_str = analysis_data.get("analysis_timestamp")
|
saved_analysis_ts_str = analysis_data.get("analysis_timestamp")
|
||||||
@ -749,7 +798,7 @@ class ProfileManagerWindow(tk.Toplevel):
|
|||||||
if os.path.normpath(saved_exe_at_analysis) != os.path.normpath(target_exe_in_form):
|
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_text += "TARGET CHANGED since last analysis. RE-ANALYSIS RECOMMENDED."
|
||||||
status_color = "orange red"
|
status_color = "orange red"
|
||||||
# If target changed, disable viewing old data, force re-analysis.
|
# Analysis data is for a different exe, so disable view buttons for that old data
|
||||||
view_funcs_btn_state = tk.DISABLED
|
view_funcs_btn_state = tk.DISABLED
|
||||||
view_vars_btn_state = tk.DISABLED
|
view_vars_btn_state = tk.DISABLED
|
||||||
view_types_btn_state = tk.DISABLED
|
view_types_btn_state = tk.DISABLED
|
||||||
@ -758,20 +807,20 @@ class ProfileManagerWindow(tk.Toplevel):
|
|||||||
status_text += "Up-to-date."
|
status_text += "Up-to-date."
|
||||||
status_color = "dark green"
|
status_color = "dark green"
|
||||||
elif saved_checksum and current_checksum_for_form_exe and saved_checksum != current_checksum_for_form_exe:
|
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_text += "EXECUTABLE CHANGED (checksum mismatch). RE-ANALYSIS REQUIRED."
|
||||||
status_color = "red"
|
status_color = "red"
|
||||||
view_funcs_btn_state = tk.DISABLED
|
view_funcs_btn_state = tk.DISABLED
|
||||||
view_vars_btn_state = tk.DISABLED
|
view_vars_btn_state = tk.DISABLED
|
||||||
view_types_btn_state = tk.DISABLED
|
view_types_btn_state = tk.DISABLED
|
||||||
view_sources_btn_state = tk.DISABLED
|
view_sources_btn_state = tk.DISABLED
|
||||||
else:
|
else: # Checksum missing or calc failed
|
||||||
status_text += "Status unclear. Consider re-analysing."
|
status_text += "Status unclear (checksum mismatch or unavailable). Consider re-analysing."
|
||||||
status_color = "orange red"
|
status_color = "orange red"
|
||||||
view_funcs_btn_state = tk.DISABLED
|
view_funcs_btn_state = tk.DISABLED
|
||||||
view_vars_btn_state = tk.DISABLED
|
view_vars_btn_state = tk.DISABLED
|
||||||
view_types_btn_state = tk.DISABLED
|
view_types_btn_state = tk.DISABLED
|
||||||
view_sources_btn_state = tk.DISABLED
|
view_sources_btn_state = tk.DISABLED
|
||||||
else:
|
else: # No analysis data in profile
|
||||||
status_text += "Not performed. Click 'Analyse' to generate."
|
status_text += "Not performed. Click 'Analyse' to generate."
|
||||||
status_color = "blue"
|
status_color = "blue"
|
||||||
|
|
||||||
@ -782,12 +831,12 @@ class ProfileManagerWindow(tk.Toplevel):
|
|||||||
|
|
||||||
self.functions_count_var.set(funcs_count_text)
|
self.functions_count_var.set(funcs_count_text)
|
||||||
self.view_functions_button.config(state=view_funcs_btn_state)
|
self.view_functions_button.config(state=view_funcs_btn_state)
|
||||||
self.variables_count_var.set(vars_count_text) # NEW
|
self.variables_count_var.set(vars_count_text)
|
||||||
self.view_variables_button.config(state=view_vars_btn_state) # NEW
|
self.view_variables_button.config(state=view_vars_btn_state)
|
||||||
self.types_count_var.set(types_count_text) # NEW
|
self.types_count_var.set(types_count_text)
|
||||||
self.view_types_button.config(state=view_types_btn_state) # NEW
|
self.view_types_button.config(state=view_types_btn_state)
|
||||||
self.sources_count_var.set(sources_count_text) # NEW
|
self.sources_count_var.set(sources_count_text)
|
||||||
self.view_sources_button.config(state=view_sources_btn_state) # NEW
|
self.view_sources_button.config(state=view_sources_btn_state)
|
||||||
|
|
||||||
|
|
||||||
def _trigger_symbol_analysis(self) -> None:
|
def _trigger_symbol_analysis(self) -> None:
|
||||||
@ -795,24 +844,25 @@ class ProfileManagerWindow(tk.Toplevel):
|
|||||||
messagebox.showerror("Error", "No profile selected.", parent=self)
|
messagebox.showerror("Error", "No profile selected.", parent=self)
|
||||||
return
|
return
|
||||||
|
|
||||||
target_exe_for_analysis = self.target_exe_var.get()
|
target_exe_for_analysis = self.target_exe_var.get() # Use value from the form
|
||||||
if not target_exe_for_analysis or not os.path.isfile(target_exe_for_analysis):
|
if not target_exe_for_analysis or not os.path.isfile(target_exe_for_analysis):
|
||||||
messagebox.showerror("Error", "Target executable path in the form is invalid or file not found.", parent=self)
|
messagebox.showerror("Error", "Target executable path in the form is invalid or file not found.", parent=self)
|
||||||
self._update_analysis_status_display()
|
self._update_analysis_status_display() # Refresh display
|
||||||
return
|
return
|
||||||
|
|
||||||
gdb_exe_path = self.app_settings.get_setting("general", "gdb_executable_path")
|
gdb_exe_path = self.app_settings.get_setting("general", "gdb_executable_path")
|
||||||
if not gdb_exe_path or not os.path.isfile(gdb_exe_path):
|
if not gdb_exe_path or not os.path.isfile(gdb_exe_path): # Check if GDB is configured
|
||||||
messagebox.showerror("GDB Error", f"GDB executable not found or not configured: {gdb_exe_path}", parent=self)
|
messagebox.showerror("GDB Error", f"GDB executable not found or not configured: {gdb_exe_path}", parent=self)
|
||||||
return
|
return
|
||||||
|
|
||||||
profile_to_update = self._profiles_data[self._selected_profile_index]
|
profile_to_update = self._profiles_data[self._selected_profile_index]
|
||||||
|
|
||||||
|
# Create and show progress dialog
|
||||||
self.progress_dialog = SymbolAnalysisProgressDialog(self)
|
self.progress_dialog = SymbolAnalysisProgressDialog(self)
|
||||||
|
|
||||||
# MODIFIED: Pass GDBMISession class for symbol analysis
|
|
||||||
symbol_analyzer = SymbolAnalyzer(gdb_exe_path, self.app_settings, gdb_session_class=GDBMISession)
|
symbol_analyzer = SymbolAnalyzer(gdb_exe_path, self.app_settings, gdb_session_class=GDBMISession)
|
||||||
|
|
||||||
|
# Run analysis in a separate thread
|
||||||
analysis_thread = threading.Thread(
|
analysis_thread = threading.Thread(
|
||||||
target=self._perform_symbol_analysis_thread,
|
target=self._perform_symbol_analysis_thread,
|
||||||
args=(profile_to_update, target_exe_for_analysis, symbol_analyzer, self.progress_dialog),
|
args=(profile_to_update, target_exe_for_analysis, symbol_analyzer, self.progress_dialog),
|
||||||
@ -820,6 +870,7 @@ class ProfileManagerWindow(tk.Toplevel):
|
|||||||
)
|
)
|
||||||
analysis_thread.start()
|
analysis_thread.start()
|
||||||
|
|
||||||
|
|
||||||
def _perform_symbol_analysis_thread(self, profile_to_update: Dict[str, Any],
|
def _perform_symbol_analysis_thread(self, profile_to_update: Dict[str, Any],
|
||||||
target_exe_path: str, symbol_analyzer: SymbolAnalyzer,
|
target_exe_path: str, symbol_analyzer: SymbolAnalyzer,
|
||||||
progress_dialog: SymbolAnalysisProgressDialog):
|
progress_dialog: SymbolAnalysisProgressDialog):
|
||||||
@ -845,7 +896,7 @@ class ProfileManagerWindow(tk.Toplevel):
|
|||||||
status_callback=gui_set_status
|
status_callback=gui_set_status
|
||||||
)
|
)
|
||||||
|
|
||||||
if analysis_data_dict:
|
if analysis_data_dict: # Check if analyze returned a non-empty dict
|
||||||
analysis_succeeded_overall = True
|
analysis_succeeded_overall = True
|
||||||
gui_set_status("Symbol analysis successfully completed."); gui_log("\nSymbol analysis successfully completed.")
|
gui_set_status("Symbol analysis successfully completed."); gui_log("\nSymbol analysis successfully completed.")
|
||||||
else:
|
else:
|
||||||
@ -857,6 +908,7 @@ class ProfileManagerWindow(tk.Toplevel):
|
|||||||
gui_log(f"\n{error_msg}")
|
gui_log(f"\n{error_msg}")
|
||||||
gui_set_status(error_msg)
|
gui_set_status(error_msg)
|
||||||
finally:
|
finally:
|
||||||
|
# Schedule the final update on the GUI thread
|
||||||
self.after(0, self._finalize_symbol_analysis, profile_to_update, analysis_data_dict, analysis_succeeded_overall, progress_dialog)
|
self.after(0, self._finalize_symbol_analysis, profile_to_update, analysis_data_dict, analysis_succeeded_overall, progress_dialog)
|
||||||
|
|
||||||
def _finalize_symbol_analysis(self, profile_to_update: Dict[str, Any],
|
def _finalize_symbol_analysis(self, profile_to_update: Dict[str, Any],
|
||||||
@ -866,22 +918,24 @@ class ProfileManagerWindow(tk.Toplevel):
|
|||||||
if success:
|
if success:
|
||||||
profile_to_update["symbol_analysis"] = analysis_data
|
profile_to_update["symbol_analysis"] = analysis_data
|
||||||
self._profiles_list_changed_overall = True
|
self._profiles_list_changed_overall = True
|
||||||
self._current_profile_modified_in_form = True
|
# self._current_profile_modified_in_form = True # Analysis data is part of profile content
|
||||||
logger.info(f"Symbol analysis data updated for profile: '{profile_to_update.get('profile_name')}'.")
|
logger.info(f"Symbol analysis data updated for profile: '{profile_to_update.get('profile_name')}'.")
|
||||||
if self.winfo_exists():
|
if self.winfo_exists(): # Check if window still exists
|
||||||
messagebox.showinfo("Analysis Complete", "Symbol analysis has finished successfully.", parent=self)
|
messagebox.showinfo("Analysis Complete", "Symbol analysis has finished successfully.", parent=self)
|
||||||
else:
|
else:
|
||||||
|
# Don't clear old analysis data if new one failed, unless it was explicitly for a new target
|
||||||
logger.error(f"Symbol analysis failed for profile: '{profile_to_update.get('profile_name')}'.")
|
logger.error(f"Symbol analysis failed for profile: '{profile_to_update.get('profile_name')}'.")
|
||||||
if self.winfo_exists():
|
if self.winfo_exists():
|
||||||
messagebox.showerror("Analysis Failed", "Symbol analysis did not complete successfully. Check logs.", parent=self)
|
messagebox.showerror("Analysis Failed", "Symbol analysis did not complete successfully. Check logs.", parent=self)
|
||||||
|
|
||||||
if progress_dialog and progress_dialog.winfo_exists():
|
if progress_dialog and progress_dialog.winfo_exists():
|
||||||
progress_dialog.analysis_complete_or_failed(success)
|
progress_dialog.analysis_complete_or_failed(success)
|
||||||
|
# Consider destroying it or just enabling close button if you want user to review logs in it
|
||||||
|
|
||||||
self._update_analysis_status_display()
|
self._update_analysis_status_display() # Refresh the analysis status and counts
|
||||||
|
|
||||||
# --- NEW: Methods to view other symbol lists ---
|
|
||||||
def _get_symbols_for_display(self, category: str) -> List[Union[str, Dict[str, Any]]]: # Modified return type hint
|
def _get_symbols_for_display(self, category: str) -> List[Union[str, Dict[str, Any]]]:
|
||||||
if self._selected_profile_index is None or \
|
if self._selected_profile_index is None or \
|
||||||
not (0 <= self._selected_profile_index < len(self._profiles_data)):
|
not (0 <= self._selected_profile_index < len(self._profiles_data)):
|
||||||
return []
|
return []
|
||||||
@ -923,23 +977,30 @@ class ProfileManagerWindow(tk.Toplevel):
|
|||||||
return
|
return
|
||||||
self._show_symbol_list_dialog("Source Files", source_files_list)
|
self._show_symbol_list_dialog("Source Files", source_files_list)
|
||||||
|
|
||||||
def _show_symbol_list_dialog(self, symbol_type: str, symbols: List[Union[str, Dict[str, Any]]]) -> None: # Modified symbols type hint
|
def _show_symbol_list_dialog(self, symbol_type: str, symbols: List[Union[str, Dict[str, Any]]]) -> None:
|
||||||
"""Helper to show the SymbolListViewerDialog with appropriate title."""
|
"""Helper to show the SymbolListViewerDialog with appropriate title."""
|
||||||
|
if self._selected_profile_index is None : return # Should not happen if view button is enabled
|
||||||
|
|
||||||
target_exe_in_form = self.target_exe_var.get()
|
target_exe_in_form = self.target_exe_var.get()
|
||||||
profile = self._profiles_data[self._selected_profile_index] # Should be valid here
|
profile = self._profiles_data[self._selected_profile_index]
|
||||||
analysis_data = profile.get("symbol_analysis")
|
analysis_data = profile.get("symbol_analysis")
|
||||||
|
|
||||||
|
if not analysis_data: # Should also not happen if button enabled
|
||||||
|
messagebox.showerror("Error", "No analysis data available to view.", parent=self)
|
||||||
|
return
|
||||||
|
|
||||||
analyzed_exe_path = analysis_data.get("analyzed_executable_path", "")
|
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"
|
exe_name_for_title = os.path.basename(target_exe_in_form) if target_exe_in_form else "Unknown Executable"
|
||||||
|
|
||||||
is_obsolete = True
|
is_obsolete = True # Assume obsolete unless proven otherwise
|
||||||
if os.path.normpath(analyzed_exe_path) == os.path.normpath(target_exe_in_form):
|
if target_exe_in_form and os.path.isfile(target_exe_in_form) and \
|
||||||
|
os.path.normpath(analyzed_exe_path) == os.path.normpath(target_exe_in_form):
|
||||||
current_checksum = file_utils.calculate_file_checksum(target_exe_in_form)
|
current_checksum = file_utils.calculate_file_checksum(target_exe_in_form)
|
||||||
saved_checksum = analysis_data.get("executable_checksum")
|
saved_checksum = analysis_data.get("executable_checksum")
|
||||||
if current_checksum and saved_checksum and current_checksum == saved_checksum:
|
if current_checksum and saved_checksum and current_checksum == saved_checksum:
|
||||||
is_obsolete = False
|
is_obsolete = False
|
||||||
|
|
||||||
title_suffix = " (Analysis might be obsolete)" if is_obsolete else ""
|
title_suffix = " (Analysis data might be obsolete for current form target)" if is_obsolete else ""
|
||||||
dialog_title = f"Analyzed {symbol_type} for '{exe_name_for_title}'{title_suffix}"
|
dialog_title = f"Analyzed {symbol_type} for '{exe_name_for_title}'{title_suffix}"
|
||||||
|
|
||||||
SymbolListViewerDialog(self, symbols, title=dialog_title)
|
SymbolListViewerDialog(self, symbols, title=dialog_title)
|
||||||
0
sim_log.txt
Normal file
0
sim_log.txt
Normal file
102
temp.md
102
temp.md
@ -1,102 +0,0 @@
|
|||||||
Assolutamente! Creare un manuale utente completo è un'ottima idea per rendere il tuo software accessibile.
|
|
||||||
|
|
||||||
Data la complessità e la lunghezza di un manuale completo, lo genererò in sezioni. Inizierò con la versione **inglese**, seguita dalla versione **italiana**.
|
|
||||||
|
|
||||||
Ecco la struttura che seguirò per entrambe le lingue:
|
|
||||||
|
|
||||||
**Manuale Utente: Cpp-Python GDB Debug Helper**
|
|
||||||
|
|
||||||
1. **Introduzione**
|
|
||||||
* Cos'è Cpp-Python GDB Debug Helper?
|
|
||||||
* A chi è rivolto?
|
|
||||||
* Caratteristiche principali
|
|
||||||
2. **Requisiti di Sistema e Configurazione Ambiente**
|
|
||||||
* Sistemi Operativi Supportati
|
|
||||||
* Python
|
|
||||||
* Librerie Python Richieste
|
|
||||||
* Installazione di GDB
|
|
||||||
* Compilazione dell'Applicazione C/C++ Target
|
|
||||||
* Installazione dello Strumento (Cpp-Python GDB Debug Helper)
|
|
||||||
3. **Guida Rapida (Quick Start)**
|
|
||||||
* Avvio dell'Applicazione
|
|
||||||
* Configurazione Iniziale (Percorso GDB)
|
|
||||||
* Prima Sessione di Debug Manuale
|
|
||||||
4. **Panoramica dell'Interfaccia Utente (Finestra Principale)**
|
|
||||||
* Barra dei Menu
|
|
||||||
* Area Stato Configurazione Critica
|
|
||||||
* Pannello Modalità (Tab Debug Manuale, Tab Esecuzione Profilo Automatizzato)
|
|
||||||
* Area Output e Log (Output Raw GDB, Output JSON Parsato, Log Applicazione)
|
|
||||||
* Barra di Stato
|
|
||||||
5. **Finestra di Configurazione (`Options > Configure Application...`)**
|
|
||||||
* Tab "Paths" (Percorsi)
|
|
||||||
* Tab "Timeouts"
|
|
||||||
* Tab "Dumper Options" (Opzioni Dumper)
|
|
||||||
* Salvataggio e Annullamento
|
|
||||||
6. **Modalità Debug Manuale**
|
|
||||||
* Impostare Eseguibile Target e Parametri
|
|
||||||
* Impostare Locazione Breakpoint
|
|
||||||
* Impostare Variabile/Espressione da Dumpare
|
|
||||||
* Pulsanti di Controllo Sessione (Start GDB, Set BP, Run/Continue, Dump Var, Stop GDB)
|
|
||||||
* Interpretare l'Output Raw GDB
|
|
||||||
* Interpretare l'Output JSON Parsato
|
|
||||||
* Salvare i Dati Dumpati (JSON/CSV)
|
|
||||||
7. **Gestore Profili (`Profiles > Manage Profiles...`)**
|
|
||||||
* Panoramica della Finestra Gestore Profili
|
|
||||||
* Gestione dei Profili (Nuovo, Duplica, Elimina, Salva Tutti)
|
|
||||||
* Modifica Dettagli Profilo (Nome, Eseguibile, Parametri)
|
|
||||||
* Analisi dei Simboli (Scopo, Esecuzione, Interpretazione Stato, Visualizzazione)
|
|
||||||
* Gestione Azioni di Debug (Aggiungi, Modifica, Rimuovi)
|
|
||||||
8. **Finestra Editor Azioni (da Gestore Profili)**
|
|
||||||
* Locazione Breakpoint (con pulsanti di aiuto)
|
|
||||||
* Variabili da Dumpare (con pulsanti di aiuto, inclusa ispezione scope live)
|
|
||||||
* Formato Output (JSON/CSV)
|
|
||||||
* Directory Output
|
|
||||||
* Pattern Nome File (spiegazione placeholder)
|
|
||||||
* Opzione "Continue after dump"
|
|
||||||
* Salvataggio e Annullamento Azione
|
|
||||||
9. **Modalità Esecuzione Profilo Automatizzato**
|
|
||||||
* Selezione di un Profilo
|
|
||||||
* Esecuzione del Profilo
|
|
||||||
* Monitoraggio Stato Esecuzione e Barra di Progresso
|
|
||||||
* Interruzione di un Profilo in Esecuzione
|
|
||||||
* Interpretare il "Log File Prodotti"
|
|
||||||
* Aprire la Cartella di Output
|
|
||||||
10. **Risoluzione Problemi / FAQ**
|
|
||||||
* GDB non trovato / Problemi script dumper
|
|
||||||
* "No debugging symbols found"
|
|
||||||
* Errori dump JSON / `<gdb_tool_error>`
|
|
||||||
* Esecuzione profilo fallisce o si blocca
|
|
||||||
* GUI non risponde
|
|
||||||
* Come ottenere più informazioni di debug da `gdb_dumper_debug.log`
|
|
||||||
11. **Casi d'Uso / Esempi**
|
|
||||||
* Dumpare un `std::vector` specifico all'ingresso di una funzione.
|
|
||||||
* Tracciare il valore di una variabile globale attraverso multipli breakpoint.
|
|
||||||
* Salvare automaticamente snapshot di una struttura dati complessa.
|
|
||||||
12. **Avanzato: Lo Script `gdb_dumper.py`**
|
|
||||||
* Ruolo e interazione con GDB.
|
|
||||||
* Posizione del file di log.
|
|
||||||
13. **Appendice (Opzionale)**
|
|
||||||
* Placeholder per i nomi dei file.
|
|
||||||
|
|
||||||
Cominciamo con la versione inglese.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Ottimo! Proseguiamo con le sezioni 9, 10 e 11 del manuale in inglese.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
Okay, here are the final sections (12 and an optional 13) for the English user manual.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
This concludes the English version of the user manual. It's quite extensive!
|
|
||||||
|
|
||||||
Now, I will proceed to generate the **Italian version** of this manual, following the same structure. This will also be a multi-turn process.
|
|
||||||
|
|
||||||
Pronto per iniziare con la versione italiana?
|
|
||||||
11
temp.py
Normal file
11
temp.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
```
|
||||||
|
--- END OF FILE symbol_analyzer.py ---
|
||||||
|
|
||||||
|
**Note Finali sull'Interruzione:**
|
||||||
|
|
||||||
|
* **`GDBMISession.send_mi_cmd` e `GDBController.send_cmd`:** Per rendere l'interruzione veramente efficace *durante* un comando GDB lungo (come `info functions` su un eseguibile enorme), questi metodi dovrebbero essere modificati per controllare `stop_event` in modo più granulare, ad esempio nei loro loop di lettura dell'output. Attualmente, `SymbolAnalyzer` interrompe solo *tra* le chiamate ai metodi di GDB (es. tra `list_functions` e `list_global_variables`). L'interruzione dentro `GDBInteractiveInspector` è un po' più reattiva perché ha un loop di attesa del breakpoint.
|
||||||
|
* **Chiusura Finestra Progresso:** La logica in `SymbolAnalysisProgressDialog` ora tenta di segnalare l'interruzione al thread di lavoro. Il thread di lavoro (in `ActionEditorWindow` o `ProfileManagerWindow`) deve quindi passare questo segnale (`stop_event`) ai metodi di `GDBInteractiveInspector` o `SymbolAnalyzer`.
|
||||||
|
* **Robustezza:** La gestione dell'interruzione di processi esterni e thread può essere complessa. Testa attentamente gli scenari di interruzione.
|
||||||
|
|
||||||
|
Questi moduli dovrebbero ora contenere le modifiche discusse. Ricontrollali attentamente nel contesto del tuo progetto completo.
|
||||||
Loading…
Reference in New Issue
Block a user