move save file into dumper
This commit is contained in:
parent
42401b115f
commit
6fa0ce0fea
1002
cpp_python_debug/core/gdb_controller.old
Normal file
1002
cpp_python_debug/core/gdb_controller.old
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -10,7 +10,7 @@ from typing import Dict, Any, Optional, Callable, List, Tuple
|
|||||||
from .gdb_controller import GDBSession
|
from .gdb_controller import GDBSession
|
||||||
from .config_manager import AppSettings
|
from .config_manager import AppSettings
|
||||||
from .output_formatter import save_to_json as save_data_to_json_file
|
from .output_formatter import save_to_json as save_data_to_json_file
|
||||||
from .output_formatter import save_to_csv as save_data_to_csv_file
|
from .output_formatter import save_to_csv as save_data_to_csv_file # Ensure this is correctly used
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ class ProfileExecutor:
|
|||||||
app_settings: AppSettings,
|
app_settings: AppSettings,
|
||||||
status_update_callback: Optional[Callable[[str], None]] = None,
|
status_update_callback: Optional[Callable[[str], None]] = None,
|
||||||
gdb_output_callback: Optional[Callable[[str], None]] = None,
|
gdb_output_callback: Optional[Callable[[str], None]] = None,
|
||||||
json_output_callback: Optional[Callable[[Any], None]] = None,
|
json_output_callback: Optional[Callable[[Any], None]] = None, # Will show status JSON now
|
||||||
execution_log_callback: Optional[Callable[[ExecutionLogEntry], None]] = None,
|
execution_log_callback: Optional[Callable[[ExecutionLogEntry], None]] = None,
|
||||||
):
|
):
|
||||||
self.profile = profile_data
|
self.profile = profile_data
|
||||||
@ -58,6 +58,7 @@ class ProfileExecutor:
|
|||||||
self.gdb_output_writer = (
|
self.gdb_output_writer = (
|
||||||
gdb_output_callback if gdb_output_callback else self._default_gdb_output
|
gdb_output_callback if gdb_output_callback else self._default_gdb_output
|
||||||
)
|
)
|
||||||
|
# json_data_handler will now receive the status payload from the dumper
|
||||||
self.json_data_handler = (
|
self.json_data_handler = (
|
||||||
json_output_callback if json_output_callback else self._default_json_data
|
json_output_callback if json_output_callback else self._default_json_data
|
||||||
)
|
)
|
||||||
@ -77,8 +78,8 @@ class ProfileExecutor:
|
|||||||
def _default_gdb_output(self, msg: str):
|
def _default_gdb_output(self, msg: str):
|
||||||
logger.debug(f"GDB Output: {msg}")
|
logger.debug(f"GDB Output: {msg}")
|
||||||
|
|
||||||
def _default_json_data(self, data: Any):
|
def _default_json_data(self, data: Any): # data is now status payload
|
||||||
logger.debug(f"JSON Data: {str(data)[:200]}")
|
logger.debug(f"Dumper Status/JSON Data: {str(data)[:200]}")
|
||||||
|
|
||||||
def _default_execution_log(self, entry: ExecutionLogEntry):
|
def _default_execution_log(self, entry: ExecutionLogEntry):
|
||||||
logger.info(f"Execution Log: {entry}")
|
logger.info(f"Execution Log: {entry}")
|
||||||
@ -95,11 +96,12 @@ class ProfileExecutor:
|
|||||||
self,
|
self,
|
||||||
breakpoint_loc_spec: str,
|
breakpoint_loc_spec: str,
|
||||||
variable_name: str,
|
variable_name: str,
|
||||||
file_path: str,
|
final_file_path: str, # Path of the final file (JSON or CSV)
|
||||||
status: str,
|
status: str, # "Success", "Failed GDB Dump", "Failed CSV Conversion"
|
||||||
gdb_bp_num: Optional[int] = None,
|
gdb_bp_num: Optional[int] = None,
|
||||||
address: Optional[str] = None,
|
address: Optional[str] = None,
|
||||||
details: str = "",
|
details: str = "",
|
||||||
|
original_json_path: Optional[str] = None # Path to the JSON written by GDB
|
||||||
) -> None:
|
) -> None:
|
||||||
entry: ExecutionLogEntry = {
|
entry: ExecutionLogEntry = {
|
||||||
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
@ -107,20 +109,23 @@ class ProfileExecutor:
|
|||||||
"gdb_bp_num": str(gdb_bp_num) if gdb_bp_num is not None else "N/A",
|
"gdb_bp_num": str(gdb_bp_num) if gdb_bp_num is not None else "N/A",
|
||||||
"address": address if address else "N/A",
|
"address": address if address else "N/A",
|
||||||
"variable": variable_name,
|
"variable": variable_name,
|
||||||
"file_produced": os.path.basename(file_path) if file_path else "N/A",
|
"file_produced": os.path.basename(final_file_path) if final_file_path else "N/A",
|
||||||
"full_path": file_path if file_path else "N/A",
|
"full_path": final_file_path if final_file_path else "N/A",
|
||||||
"status": status,
|
"status": status,
|
||||||
"details": details,
|
"details": details,
|
||||||
|
"raw_json_path_by_gdb": original_json_path if original_json_path else (final_file_path if status=="Success" and final_file_path and final_file_path.endswith(".json") else "N/A")
|
||||||
}
|
}
|
||||||
self.produced_files_log.append(entry)
|
self.produced_files_log.append(entry)
|
||||||
self.execution_log_adder(entry)
|
self.execution_log_adder(entry)
|
||||||
|
|
||||||
|
|
||||||
def _get_setting(
|
def _get_setting(
|
||||||
self, category: str, key: str, default: Optional[Any] = None
|
self, category: str, key: str, default: Optional[Any] = None
|
||||||
) -> Any:
|
) -> Any:
|
||||||
return self.app_settings.get_setting(category, key, default)
|
return self.app_settings.get_setting(category, key, default)
|
||||||
|
|
||||||
def _get_dumper_options(self) -> Dict[str, Any]:
|
def _get_dumper_options(self) -> Dict[str, Any]:
|
||||||
|
# These are general options, not the per-dump file path
|
||||||
return self.app_settings.get_category_settings("dumper_options", {})
|
return self.app_settings.get_category_settings("dumper_options", {})
|
||||||
|
|
||||||
def _generate_output_filename(
|
def _generate_output_filename(
|
||||||
@ -129,7 +134,9 @@ class ProfileExecutor:
|
|||||||
profile_name: str,
|
profile_name: str,
|
||||||
bp_loc_spec: str,
|
bp_loc_spec: str,
|
||||||
var_name: str,
|
var_name: str,
|
||||||
file_format: str,
|
# file_format is the *final* desired format (json or csv)
|
||||||
|
# The dumper will always create .json, this is for the final name
|
||||||
|
file_format_extension_without_dot: str,
|
||||||
) -> str:
|
) -> str:
|
||||||
timestamp_str = datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3]
|
timestamp_str = datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3]
|
||||||
placeholders = {
|
placeholders = {
|
||||||
@ -140,14 +147,20 @@ class ProfileExecutor:
|
|||||||
"{breakpoint}": sanitize_filename_component(bp_loc_spec),
|
"{breakpoint}": sanitize_filename_component(bp_loc_spec),
|
||||||
"{variable}": sanitize_filename_component(var_name),
|
"{variable}": sanitize_filename_component(var_name),
|
||||||
"{timestamp}": timestamp_str,
|
"{timestamp}": timestamp_str,
|
||||||
"{format}": file_format.lower(),
|
# {format} will be replaced by the actual extension needed
|
||||||
|
"{format}": file_format_extension_without_dot.lower(),
|
||||||
}
|
}
|
||||||
filename = pattern
|
filename = pattern
|
||||||
for ph, val in placeholders.items():
|
for ph, val in placeholders.items():
|
||||||
filename = filename.replace(ph, val)
|
filename = filename.replace(ph, val)
|
||||||
if not filename.lower().endswith(f".{file_format.lower()}"):
|
|
||||||
filename += f".{file_format.lower()}"
|
# Ensure the final filename has the correct extension based on file_format_extension_without_dot
|
||||||
return filename
|
# Remove any existing extension and add the correct one.
|
||||||
|
name_part, _ = os.path.splitext(filename)
|
||||||
|
final_filename = f"{name_part}.{file_format_extension_without_dot.lower()}"
|
||||||
|
|
||||||
|
return final_filename
|
||||||
|
|
||||||
|
|
||||||
def _prepare_output_directory(
|
def _prepare_output_directory(
|
||||||
self, base_output_dir_from_action: str, profile_name: str
|
self, base_output_dir_from_action: str, profile_name: str
|
||||||
@ -176,69 +189,30 @@ class ProfileExecutor:
|
|||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _parse_gdb_set_breakpoint_output(
|
def _parse_gdb_set_breakpoint_output(self, gdb_output: str) -> Optional[Tuple[int, str]]:
|
||||||
self, gdb_output: str
|
if not gdb_output: return None
|
||||||
) -> Optional[Tuple[int, str]]:
|
match = re.search(r"Breakpoint\s+(\d+)\s+at\s+(0x[0-9a-fA-F]+)", gdb_output, re.IGNORECASE)
|
||||||
if not gdb_output:
|
if match: return int(match.group(1)), match.group(2).lower()
|
||||||
return None
|
match_pending = re.search(r"Breakpoint\s+(\d+)\s+pending", gdb_output, re.IGNORECASE)
|
||||||
match = re.search(
|
if match_pending: return int(match_pending.group(1)), "pending"
|
||||||
r"Breakpoint\s+(\d+)\s+at\s+(0x[0-9a-fA-F]+)", gdb_output, re.IGNORECASE
|
logger.warning(f"Could not parse GDB BP num and addr from output: '{gdb_output[:200]}'")
|
||||||
)
|
|
||||||
if match:
|
|
||||||
bp_num = int(match.group(1))
|
|
||||||
address = match.group(2).lower()
|
|
||||||
return bp_num, address
|
|
||||||
match_pending = re.search(
|
|
||||||
r"Breakpoint\s+(\d+)\s+pending", gdb_output, re.IGNORECASE
|
|
||||||
)
|
|
||||||
if match_pending:
|
|
||||||
bp_num = int(match_pending.group(1))
|
|
||||||
return bp_num, "pending"
|
|
||||||
logger.warning(
|
|
||||||
f"Could not parse GDB breakpoint number and address from set_breakpoint output: '{gdb_output[:200]}'"
|
|
||||||
)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _parse_breakpoint_hit_output(self, gdb_output: str) -> Optional[int]:
|
def _parse_breakpoint_hit_output(self, gdb_output: str) -> Optional[int]:
|
||||||
if not gdb_output:
|
if not gdb_output: return None
|
||||||
return None
|
match_thread_hit = re.search(r"Thread\s+\S+\s+hit\s+Breakpoint\s+(\d+)", gdb_output, re.IGNORECASE)
|
||||||
match = re.search(
|
if match_thread_hit: return int(match_thread_hit.group(1))
|
||||||
r"Thread\s+\S+\s+hit\s+Breakpoint\s+(\d+)", gdb_output, re.IGNORECASE
|
match_simple_hit = re.search(r"Breakpoint\s+(\d+)[,\s]", gdb_output) # Simpler match as fallback
|
||||||
)
|
if match_simple_hit: return int(match_simple_hit.group(1))
|
||||||
if match:
|
logger.debug(f"Could not parse GDB BP num from hit output: '{gdb_output[:200]}...'")
|
||||||
return int(match.group(1))
|
|
||||||
match = re.search(
|
|
||||||
r"Breakpoint\s+(\d+)[,\s]", gdb_output
|
|
||||||
)
|
|
||||||
if match:
|
|
||||||
return int(match.group(1))
|
|
||||||
logger.debug(
|
|
||||||
f"Could not parse GDB breakpoint number from hit output: '{gdb_output[:200]}...'"
|
|
||||||
)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _check_program_exited_from_output(self, gdb_output: str) -> bool:
|
def _check_program_exited_from_output(self, gdb_output: str) -> bool:
|
||||||
"""
|
|
||||||
Checks GDB output for signs that the entire program/inferior has exited.
|
|
||||||
More specific than just "exited with code" which can apply to threads.
|
|
||||||
"""
|
|
||||||
# Pattern for GDB indicating the inferior process itself exited
|
|
||||||
# Example: "[Inferior 1 (process 1234) exited normally]"
|
|
||||||
# Example: "[Inferior 1 (process 1234) exited with code 01]"
|
|
||||||
# Example: "Program exited normally." (often seen when GDB quits the debugged program)
|
|
||||||
# Example: "Program terminated with signal SIGINT, Interrupt."
|
|
||||||
# Example: "Remote communication error. Target disconnected.: Connection reset by peer." (if remote debugging)
|
|
||||||
|
|
||||||
# Regex for inferior exit messages
|
|
||||||
inferior_exit_pattern = r"\[Inferior\s+\d+\s+\(process\s+\d+\)\s+exited"
|
inferior_exit_pattern = r"\[Inferior\s+\d+\s+\(process\s+\d+\)\s+exited"
|
||||||
# General program exit messages from GDB
|
|
||||||
program_exit_patterns = [
|
program_exit_patterns = [
|
||||||
r"Program exited normally\.",
|
r"Program exited normally\.", r"Program exited with code .*\.",
|
||||||
r"Program exited with code .*\.",
|
r"Program terminated with signal .*\.", r"Remote communication error\."
|
||||||
r"Program terminated with signal .*\.",
|
|
||||||
r"Remote communication error\." # For remote debugging scenarios
|
|
||||||
]
|
]
|
||||||
|
|
||||||
if re.search(inferior_exit_pattern, gdb_output, re.IGNORECASE):
|
if re.search(inferior_exit_pattern, gdb_output, re.IGNORECASE):
|
||||||
logger.info("Detected inferior exit from GDB output.")
|
logger.info("Detected inferior exit from GDB output.")
|
||||||
return True
|
return True
|
||||||
@ -246,44 +220,24 @@ class ProfileExecutor:
|
|||||||
if re.search(pattern, gdb_output, re.IGNORECASE):
|
if re.search(pattern, gdb_output, re.IGNORECASE):
|
||||||
logger.info(f"Detected program exit via pattern: '{pattern}'")
|
logger.info(f"Detected program exit via pattern: '{pattern}'")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# If the only prompt is (gdb) and no other output indicating a stop/signal,
|
|
||||||
# and the previous command was 'run' or 'continue', it might mean the program
|
|
||||||
# finished without GDB explicitly stating "Program exited normally" before the prompt.
|
|
||||||
# This is a more subtle case and might need careful handling if common.
|
|
||||||
# For now, rely on explicit messages.
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
profile_name = self.profile.get("profile_name", "Unnamed Profile")
|
profile_name = self.profile.get("profile_name", "Unnamed Profile")
|
||||||
self._log_event(f"Starting profile: '{profile_name}'...", True)
|
self._log_event(f"Starting profile: '{profile_name}'...", True)
|
||||||
self.is_running = True
|
self.is_running = True; self._stop_requested = False
|
||||||
self._stop_requested = False
|
self.produced_files_log.clear(); self.execution_event_log.clear()
|
||||||
self.produced_files_log.clear()
|
self.gdb_bp_num_to_details_map.clear(); self.address_to_action_indices_map.clear()
|
||||||
self.execution_event_log.clear()
|
|
||||||
self.gdb_bp_num_to_details_map.clear()
|
|
||||||
self.address_to_action_indices_map.clear()
|
|
||||||
|
|
||||||
self.profile_execution_summary = {
|
self.profile_execution_summary = {
|
||||||
"profile_name": profile_name,
|
"profile_name": profile_name, "target_executable": self.profile.get("target_executable"),
|
||||||
"target_executable": self.profile.get("target_executable"),
|
"program_parameters": self.profile.get("program_parameters"), "start_time": datetime.now().isoformat(),
|
||||||
"program_parameters": self.profile.get("program_parameters"),
|
"end_time": None, "status": "Initialized",
|
||||||
"start_time": datetime.now().isoformat(),
|
"actions_summary": [{"action_index": i, "breakpoint_spec": action.get("breakpoint_location", "N/A"),
|
||||||
"end_time": None,
|
"gdb_bp_num_assigned": None, "address_resolved": None,
|
||||||
"status": "Initialized",
|
"variables_dumped_count": 0, "hit_count": 0, "status": "Pending"}
|
||||||
"actions_summary": [
|
for i, action in enumerate(self.profile.get("actions", []))],
|
||||||
{"action_index": i,
|
"execution_log": [], "files_produced_detailed": []
|
||||||
"breakpoint_spec": action.get("breakpoint_location", "N/A"),
|
|
||||||
"gdb_bp_num_assigned": None,
|
|
||||||
"address_resolved": None,
|
|
||||||
"variables_dumped_count": 0,
|
|
||||||
"hit_count": 0,
|
|
||||||
"status": "Pending"}
|
|
||||||
for i, action in enumerate(self.profile.get("actions", []))
|
|
||||||
],
|
|
||||||
"execution_log": [],
|
|
||||||
"files_produced_detailed": []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gdb_exe = self._get_setting("general", "gdb_executable_path")
|
gdb_exe = self._get_setting("general", "gdb_executable_path")
|
||||||
@ -291,48 +245,34 @@ class ProfileExecutor:
|
|||||||
gdb_script_path = self._get_setting("general", "gdb_dumper_script_path")
|
gdb_script_path = self._get_setting("general", "gdb_dumper_script_path")
|
||||||
|
|
||||||
if not target_exe or not os.path.exists(target_exe):
|
if not target_exe or not os.path.exists(target_exe):
|
||||||
msg = f"Error: Target executable '{target_exe}' not found for profile '{profile_name}'."
|
msg = f"Error: Target executable '{target_exe}' not found for profile '{profile_name}'."; self._log_event(msg, True)
|
||||||
self._log_event(msg, True)
|
self.profile_execution_summary["status"] = "Error: Target not found"; self.is_running = False
|
||||||
self.profile_execution_summary["status"] = "Error: Target not found"
|
self._finalize_summary_report(None); return
|
||||||
self.is_running = False
|
|
||||||
self._finalize_summary_report(None)
|
|
||||||
return
|
|
||||||
|
|
||||||
actions = self.profile.get("actions", [])
|
actions = self.profile.get("actions", [])
|
||||||
if not actions:
|
if not actions:
|
||||||
self._log_event(f"Profile '{profile_name}' has no actions defined. Stopping.", True)
|
self._log_event(f"Profile '{profile_name}' has no actions. Stopping.", True)
|
||||||
self.profile_execution_summary["status"] = "Error: No actions"
|
self.profile_execution_summary["status"] = "Error: No actions"; self.is_running = False
|
||||||
self.is_running = False
|
self._finalize_summary_report(None); return
|
||||||
self._finalize_summary_report(None)
|
|
||||||
return
|
|
||||||
|
|
||||||
base_output_dir = "."
|
base_output_dir = actions[0].get("output_directory", ".") if actions else "."
|
||||||
if actions and "output_directory" in actions[0]: # Use first action's output dir as base
|
|
||||||
base_output_dir = actions[0].get("output_directory", ".")
|
|
||||||
self.current_run_output_path = self._prepare_output_directory(base_output_dir, profile_name)
|
self.current_run_output_path = self._prepare_output_directory(base_output_dir, profile_name)
|
||||||
|
|
||||||
if not self.current_run_output_path:
|
if not self.current_run_output_path:
|
||||||
self.profile_execution_summary["status"] = "Error: Cannot create output directory"
|
self.profile_execution_summary["status"] = "Error: Output dir creation failed"
|
||||||
self.profile_execution_summary["end_time"] = datetime.now().isoformat()
|
self.profile_execution_summary["end_time"] = datetime.now().isoformat()
|
||||||
self._finalize_summary_report(self.current_run_output_path)
|
self._finalize_summary_report(self.current_run_output_path); self.is_running = False; return
|
||||||
self.is_running = False
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.gdb_session = GDBSession(
|
self.gdb_session = GDBSession(gdb_path=gdb_exe, executable_path=target_exe,
|
||||||
gdb_path=gdb_exe, executable_path=target_exe,
|
gdb_script_full_path=gdb_script_path,
|
||||||
gdb_script_full_path=gdb_script_path, dumper_options=self._get_dumper_options()
|
dumper_options=self._get_dumper_options())
|
||||||
)
|
|
||||||
startup_timeout = self._get_setting("timeouts", "gdb_start", 30)
|
startup_timeout = self._get_setting("timeouts", "gdb_start", 30)
|
||||||
self._log_event(f"Spawning GDB for '{os.path.basename(target_exe)}'...", True)
|
self._log_event(f"Spawning GDB for '{os.path.basename(target_exe)}'...", True)
|
||||||
self.gdb_session.start(timeout=startup_timeout)
|
self.gdb_session.start(timeout=startup_timeout)
|
||||||
|
|
||||||
if not self.gdb_session.symbols_found:
|
if not self.gdb_session.symbols_found:
|
||||||
msg = (f"Error for profile '{profile_name}': No debugging symbols found in "
|
msg = f"Error: No debugging symbols in '{os.path.basename(target_exe)}'. Profile aborted."
|
||||||
f"'{os.path.basename(target_exe)}'. Profile execution aborted.")
|
self._log_event(msg, True); self.profile_execution_summary["status"] = "Error: No Debug Symbols"; return
|
||||||
self._log_event(msg, True)
|
|
||||||
self.profile_execution_summary["status"] = "Error: No Debug Symbols"
|
|
||||||
return
|
|
||||||
|
|
||||||
self._log_event("GDB session started.", False)
|
self._log_event("GDB session started.", False)
|
||||||
if gdb_script_path and self.gdb_session.gdb_script_sourced_successfully:
|
if gdb_script_path and self.gdb_session.gdb_script_sourced_successfully:
|
||||||
@ -346,281 +286,214 @@ class ProfileExecutor:
|
|||||||
if self._stop_requested: break
|
if self._stop_requested: break
|
||||||
bp_spec = action_config.get("breakpoint_location")
|
bp_spec = action_config.get("breakpoint_location")
|
||||||
action_summary = self.profile_execution_summary["actions_summary"][action_idx]
|
action_summary = self.profile_execution_summary["actions_summary"][action_idx]
|
||||||
|
|
||||||
if not bp_spec:
|
if not bp_spec:
|
||||||
self._log_event(f"Action {action_idx + 1}: No breakpoint location. Skipping.", False)
|
self._log_event(f"Action {action_idx + 1}: No BP. Skipping.", False)
|
||||||
action_summary["status"] = "Skipped (No BP Spec)"
|
action_summary["status"] = "Skipped (No BP Spec)"; continue
|
||||||
continue
|
|
||||||
|
|
||||||
self._log_event(f"Setting BP for Action {action_idx + 1} ('{bp_spec}')...", False)
|
self._log_event(f"Setting BP for Action {action_idx + 1} ('{bp_spec}')...", False)
|
||||||
bp_set_output = self.gdb_session.set_breakpoint(bp_spec, timeout=cmd_timeout)
|
bp_set_output = self.gdb_session.set_breakpoint(bp_spec, timeout=cmd_timeout)
|
||||||
self.gdb_output_writer(bp_set_output)
|
self.gdb_output_writer(bp_set_output)
|
||||||
|
|
||||||
parsed_bp_info = self._parse_gdb_set_breakpoint_output(bp_set_output)
|
parsed_bp_info = self._parse_gdb_set_breakpoint_output(bp_set_output)
|
||||||
if parsed_bp_info:
|
if parsed_bp_info:
|
||||||
gdb_bp_num, address_str = parsed_bp_info
|
gdb_bp_num, address_str = parsed_bp_info
|
||||||
action_summary["gdb_bp_num_assigned"] = gdb_bp_num
|
action_summary["gdb_bp_num_assigned"] = gdb_bp_num; action_summary["address_resolved"] = address_str
|
||||||
action_summary["address_resolved"] = address_str
|
self.gdb_bp_num_to_details_map[gdb_bp_num] = {"address": address_str, "action_index": action_idx, "bp_spec": bp_spec }
|
||||||
|
|
||||||
self.gdb_bp_num_to_details_map[gdb_bp_num] = {
|
|
||||||
"address": address_str,
|
|
||||||
"action_index": action_idx,
|
|
||||||
"bp_spec": bp_spec
|
|
||||||
}
|
|
||||||
if address_str != "pending":
|
if address_str != "pending":
|
||||||
if address_str not in self.address_to_action_indices_map:
|
if address_str not in self.address_to_action_indices_map: self.address_to_action_indices_map[address_str] = []
|
||||||
self.address_to_action_indices_map[address_str] = []
|
if action_idx not in self.address_to_action_indices_map[address_str]: self.address_to_action_indices_map[address_str].append(action_idx)
|
||||||
if action_idx not in self.address_to_action_indices_map[address_str]:
|
self._log_event(f"Action {action_idx+1} ('{bp_spec}'): GDB BP {gdb_bp_num} at {address_str}.", False); num_successfully_mapped_breakpoints +=1
|
||||||
self.address_to_action_indices_map[address_str].append(action_idx)
|
|
||||||
self._log_event(f"Action {action_idx+1} ('{bp_spec}'): GDB BP {gdb_bp_num} at {address_str}.", False)
|
|
||||||
num_successfully_mapped_breakpoints +=1
|
|
||||||
else:
|
else:
|
||||||
self._log_event(f"Action {action_idx+1} ('{bp_spec}'): GDB BP {gdb_bp_num} is PENDING. Will not trigger until resolved.", False)
|
self._log_event(f"Action {action_idx+1} ('{bp_spec}'): GDB BP {gdb_bp_num} PENDING.", False); action_summary["status"] = "Pending in GDB"
|
||||||
action_summary["status"] = "Pending in GDB"
|
|
||||||
else:
|
else:
|
||||||
self._log_event(f"Error: Action {action_idx + 1}: Failed to parse GDB BP info for '{bp_spec}'. Output: {bp_set_output[:100]}", True)
|
self._log_event(f"Error: Action {action_idx + 1}: Failed GDB BP parse for '{bp_spec}'.", True); action_summary["status"] = "Error (BP Set/Parse)"
|
||||||
action_summary["status"] = "Error (BP Set/Parse)"
|
|
||||||
|
|
||||||
if self._stop_requested: raise InterruptedError("User requested stop during BP setup.")
|
if self._stop_requested: raise InterruptedError("User requested stop during BP setup.")
|
||||||
if num_successfully_mapped_breakpoints == 0:
|
if num_successfully_mapped_breakpoints == 0:
|
||||||
self._log_event("No non-pending breakpoints successfully mapped. Aborting profile.", True)
|
self._log_event("No non-pending BPs mapped. Aborting.", True)
|
||||||
self.profile_execution_summary["status"] = "Error: No BPs Mapped"
|
self.profile_execution_summary["status"] = "Error: No BPs Mapped"; return
|
||||||
return
|
|
||||||
|
|
||||||
program_params = self.profile.get("program_parameters", "")
|
program_params = self.profile.get("program_parameters", "")
|
||||||
self._log_event(f"Running program '{os.path.basename(target_exe)} {program_params}'...", True)
|
self._log_event(f"Running program '{os.path.basename(target_exe)} {program_params}'...", True)
|
||||||
run_timeout = self._get_setting("timeouts", "program_run_continue", 120)
|
run_timeout = self._get_setting("timeouts", "program_run_continue", 120)
|
||||||
gdb_output = self.gdb_session.run_program(program_params, timeout=run_timeout)
|
gdb_output = self.gdb_session.run_program(program_params, timeout=run_timeout)
|
||||||
self.gdb_output_writer(gdb_output)
|
self.gdb_output_writer(gdb_output)
|
||||||
|
program_has_exited = self._check_program_exited_from_output(gdb_output)
|
||||||
program_has_exited = self._check_program_exited_from_output(gdb_output) # MODIFIED
|
if program_has_exited: self._log_event(f"Program exited on initial run. Output: {gdb_output[:250]}", True)
|
||||||
if program_has_exited:
|
|
||||||
self._log_event(f"Program exited on initial run. Output: {gdb_output[:250]}", True) # Increased log length
|
|
||||||
|
|
||||||
while self.gdb_session.is_alive() and not program_has_exited and not self._stop_requested:
|
while self.gdb_session.is_alive() and not program_has_exited and not self._stop_requested:
|
||||||
hit_gdb_bp_num = self._parse_breakpoint_hit_output(gdb_output)
|
hit_gdb_bp_num = self._parse_breakpoint_hit_output(gdb_output)
|
||||||
|
|
||||||
current_pc_address: Optional[str] = None
|
current_pc_address: Optional[str] = None
|
||||||
# Only query PC if we actually hit a breakpoint or stopped for some reason
|
|
||||||
# and are not about to exit the loop due to program_has_exited.
|
|
||||||
if self.gdb_session and self.gdb_session.is_alive() and not program_has_exited and hit_gdb_bp_num:
|
if self.gdb_session and self.gdb_session.is_alive() and not program_has_exited and hit_gdb_bp_num:
|
||||||
try:
|
try:
|
||||||
pc_out = self.gdb_session.send_cmd("p/x $pc", expect_prompt=True, timeout=cmd_timeout)
|
pc_out = self.gdb_session.send_cmd("p/x $pc", expect_prompt=True, timeout=cmd_timeout)
|
||||||
self.gdb_output_writer(f"$pc query: {pc_out}\n")
|
self.gdb_output_writer(f"$pc query: {pc_out}\n")
|
||||||
pc_match = re.search(r"=\s*(0x[0-9a-fA-F]+)", pc_out)
|
pc_match = re.search(r"=\s*(0x[0-9a-fA-F]+)", pc_out)
|
||||||
if pc_match:
|
if pc_match: current_pc_address = pc_match.group(1).lower(); self._log_event(f"Current PC: {current_pc_address}", False)
|
||||||
current_pc_address = pc_match.group(1).lower()
|
except Exception as e_pc: self._log_event(f"Could not get PC: {e_pc}", False)
|
||||||
self._log_event(f"Current PC: {current_pc_address}", False)
|
|
||||||
except Exception as e_pc:
|
|
||||||
self._log_event(f"Could not get current PC: {e_pc}", False)
|
|
||||||
|
|
||||||
actions_to_process_at_this_stop: List[int] = []
|
actions_to_process_at_this_stop: List[int] = []
|
||||||
hit_bp_details_for_log = "N/A"
|
hit_bp_details_for_log = "N/A"
|
||||||
|
|
||||||
if hit_gdb_bp_num is not None and hit_gdb_bp_num in self.gdb_bp_num_to_details_map:
|
if hit_gdb_bp_num is not None and hit_gdb_bp_num in self.gdb_bp_num_to_details_map:
|
||||||
bp_details = self.gdb_bp_num_to_details_map[hit_gdb_bp_num]
|
bp_details = self.gdb_bp_num_to_details_map[hit_gdb_bp_num]
|
||||||
address_of_hit = bp_details["address"] # This should be the resolved address
|
address_of_hit = bp_details["address"]
|
||||||
hit_bp_details_for_log = f"GDB BP {hit_gdb_bp_num} ('{bp_details['bp_spec']}') at {address_of_hit}"
|
hit_bp_details_for_log = f"GDB BP {hit_gdb_bp_num} ('{bp_details['bp_spec']}') at {address_of_hit}"
|
||||||
# Ensure current_pc_address matches the breakpoint's resolved address if possible, or use resolved address
|
|
||||||
effective_address_for_action_lookup = current_pc_address if current_pc_address else address_of_hit
|
effective_address_for_action_lookup = current_pc_address if current_pc_address else address_of_hit
|
||||||
|
|
||||||
if effective_address_for_action_lookup != "pending" and \
|
if effective_address_for_action_lookup != "pending" and \
|
||||||
effective_address_for_action_lookup in self.address_to_action_indices_map:
|
effective_address_for_action_lookup in self.address_to_action_indices_map:
|
||||||
actions_to_process_at_this_stop.extend(self.address_to_action_indices_map[effective_address_for_action_lookup])
|
actions_to_process_at_this_stop.extend(self.address_to_action_indices_map[effective_address_for_action_lookup])
|
||||||
elif current_pc_address and current_pc_address in self.address_to_action_indices_map:
|
elif current_pc_address and current_pc_address in self.address_to_action_indices_map:
|
||||||
# This case handles if we stopped for a reason other than a numbered BP but PC matches a mapped BP address
|
|
||||||
actions_to_process_at_this_stop.extend(self.address_to_action_indices_map[current_pc_address])
|
actions_to_process_at_this_stop.extend(self.address_to_action_indices_map[current_pc_address])
|
||||||
hit_bp_details_for_log = f"PC {current_pc_address} (mapped to actions)"
|
hit_bp_details_for_log = f"PC {current_pc_address} (mapped actions)"
|
||||||
|
|
||||||
if actions_to_process_at_this_stop:
|
if actions_to_process_at_this_stop:
|
||||||
self._log_event(f"Processing stop at {hit_bp_details_for_log}.", True)
|
self._log_event(f"Processing stop at {hit_bp_details_for_log}.", True)
|
||||||
|
|
||||||
unique_action_indices_to_process = sorted(list(set(actions_to_process_at_this_stop)))
|
unique_action_indices_to_process = sorted(list(set(actions_to_process_at_this_stop)))
|
||||||
should_continue_after_all_these_actions = True # Default to continue
|
should_continue_after_all_these_actions = True
|
||||||
|
|
||||||
for action_idx in unique_action_indices_to_process:
|
for action_idx in unique_action_indices_to_process:
|
||||||
if self._stop_requested: break
|
if self._stop_requested: break
|
||||||
current_action_config = actions[action_idx]
|
current_action_config = actions[action_idx]
|
||||||
action_summary = self.profile_execution_summary["actions_summary"][action_idx]
|
action_summary = self.profile_execution_summary["actions_summary"][action_idx]
|
||||||
|
if action_summary["status"].startswith("Completed") and not current_action_config.get("dump_on_every_hit", True):
|
||||||
dump_on_every_hit = current_action_config.get("dump_on_every_hit", True)
|
self._log_event(f"Action {action_idx + 1} skipped (completed, !dump_on_every_hit).", False)
|
||||||
action_already_completed_once = action_summary["status"].startswith("Completed")
|
if not current_action_config.get("continue_after_dump", True): should_continue_after_all_these_actions = False
|
||||||
|
|
||||||
if action_already_completed_once and not dump_on_every_hit:
|
|
||||||
self._log_event(f"Action {action_idx + 1} ('{current_action_config.get('breakpoint_location')}') previously completed and dump_on_every_hit is False. Skipping.", False)
|
|
||||||
if not current_action_config.get("continue_after_dump", True):
|
|
||||||
should_continue_after_all_these_actions = False
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self._log_event(f"Executing Action {action_idx + 1} ('{current_action_config.get('breakpoint_location')}')...", False)
|
self._log_event(f"Executing Action {action_idx + 1} ('{current_action_config.get('breakpoint_location')}')...", False)
|
||||||
action_summary["status"] = "Processing Dumps"
|
action_summary["status"] = "Processing Dumps"; action_summary["hit_count"] += 1
|
||||||
action_summary["hit_count"] += 1
|
vars_to_dump = current_action_config.get("variables_to_dump", [])
|
||||||
|
filename_pattern_cfg = current_action_config.get("filename_pattern", "{breakpoint}_{variable}_{timestamp}.{format}")
|
||||||
vars_to_dump_for_action = current_action_config.get("variables_to_dump", [])
|
output_format_cfg = current_action_config.get("output_format", "json").lower()
|
||||||
filename_pattern = current_action_config.get("filename_pattern", "{breakpoint}_{variable}_{timestamp}.{format}")
|
|
||||||
output_format_for_action = current_action_config.get("output_format", "json").lower()
|
|
||||||
bp_spec_for_file = current_action_config.get("breakpoint_location", "unknown_bp")
|
bp_spec_for_file = current_action_config.get("breakpoint_location", "unknown_bp")
|
||||||
|
|
||||||
current_dump_success_count_for_this_hit = 0
|
current_dump_success_count = 0
|
||||||
for var_name in vars_to_dump_for_action:
|
for var_name in vars_to_dump:
|
||||||
if self._stop_requested: break
|
if self._stop_requested: break
|
||||||
dump_timeout = self._get_setting("timeouts", "dump_variable", 60)
|
dump_timeout = self._get_setting("timeouts", "dump_variable", 60)
|
||||||
dumped_data = None; file_save_path = ""; dump_status_msg = "Failed"; dump_details_msg = ""
|
|
||||||
if not self.gdb_session.gdb_script_sourced_successfully and output_format_for_action == "json":
|
# Path where GDB dumper will write the JSON file
|
||||||
msg = f"Dumper script unavailable for '{var_name}' (JSON)."
|
# GDB dumper always writes JSON, conversion to CSV is done after by ProfileExecutor
|
||||||
self._log_event(msg, False); dump_details_msg = msg
|
gdb_dumper_json_filename = self._generate_output_filename(filename_pattern_cfg, profile_name, bp_spec_for_file, var_name, "json")
|
||||||
self.json_data_handler({"_profile_executor_error": msg, "variable": var_name})
|
gdb_dumper_target_json_filepath = os.path.join(self.current_run_output_path, gdb_dumper_json_filename)
|
||||||
else:
|
|
||||||
dumped_data = self.gdb_session.dump_variable_to_json(var_name, timeout=dump_timeout)
|
dump_status_payload = self.gdb_session.dump_variable_to_json(
|
||||||
self.json_data_handler(dumped_data)
|
var_name,
|
||||||
if isinstance(dumped_data, dict) and "_gdb_tool_error" in dumped_data:
|
timeout=dump_timeout,
|
||||||
err_detail = dumped_data.get("details", dumped_data["_gdb_tool_error"])
|
target_output_filepath=gdb_dumper_target_json_filepath, # Pass path to GDB
|
||||||
self._log_event(f"Error dumping '{var_name}': {err_detail}", False); dump_details_msg = f"GDB Tool Error: {err_detail}"
|
target_output_format=output_format_cfg # Pass final desired format
|
||||||
if "raw_gdb_output" in dumped_data:
|
)
|
||||||
self.gdb_output_writer(f"--- Raw GDB output for failed dump of '{var_name}' ---\n{dumped_data['raw_gdb_output']}\n--- End ---\n")
|
self.json_data_handler(dump_status_payload) # Show status payload in GUI
|
||||||
elif dumped_data is not None:
|
|
||||||
output_filename = self._generate_output_filename(filename_pattern, profile_name, bp_spec_for_file, var_name, output_format_for_action)
|
final_file_path_for_log = ""
|
||||||
file_save_path = os.path.join(self.current_run_output_path, output_filename)
|
log_status_msg = "Failed"; log_details_msg = ""; original_json_path_for_log = None
|
||||||
|
|
||||||
|
if dump_status_payload.get("status") == "success":
|
||||||
|
original_json_path_for_log = dump_status_payload.get("filepath_written")
|
||||||
|
if not original_json_path_for_log: # Should not happen if status is success
|
||||||
|
log_status_msg = "Error"; log_details_msg = "Dumper success but no filepath in status."; self._log_event(f"Dumper reported success for '{var_name}' but no filepath_written in status.", True)
|
||||||
|
elif output_format_cfg == "json":
|
||||||
|
final_file_path_for_log = original_json_path_for_log
|
||||||
|
log_status_msg = "Success"; current_dump_success_count += 1
|
||||||
|
self._log_event(f"Saved '{var_name}' to '{os.path.basename(final_file_path_for_log)}' (JSON by GDB).", False)
|
||||||
|
elif output_format_cfg == "csv":
|
||||||
|
csv_filename = self._generate_output_filename(filename_pattern_cfg, profile_name, bp_spec_for_file, var_name, "csv")
|
||||||
|
csv_filepath = os.path.join(self.current_run_output_path, csv_filename)
|
||||||
|
final_file_path_for_log = csv_filepath
|
||||||
try:
|
try:
|
||||||
if output_format_for_action == "json": save_data_to_json_file(dumped_data, file_save_path)
|
with open(original_json_path_for_log, 'r', encoding='utf-8') as f_json_in:
|
||||||
elif output_format_for_action == "csv":
|
json_data_for_csv = json.load(f_json_in)
|
||||||
data_for_csv = dumped_data
|
|
||||||
if isinstance(data_for_csv, dict) and not isinstance(data_for_csv, list): data_for_csv = [data_for_csv]
|
|
||||||
elif not isinstance(data_for_csv, list): data_for_csv = [{"value": data_for_csv}]
|
|
||||||
elif isinstance(data_for_csv, list) and data_for_csv and not all(isinstance(item, dict) for item in data_for_csv): data_for_csv = [{"value": item} for item in data_for_csv]
|
|
||||||
save_data_to_csv_file(data_for_csv, file_save_path)
|
|
||||||
else: raise ValueError(f"Unsupported format: {output_format_for_action}")
|
|
||||||
self._log_event(f"Saved '{var_name}' to '{output_filename}'.", False); dump_status_msg = "Success"; current_dump_success_count_for_this_hit += 1
|
|
||||||
except Exception as save_e:
|
|
||||||
self._log_event(f"Error saving dump of '{var_name}': {save_e}", False); dump_details_msg = f"Save Error: {save_e}"
|
|
||||||
else:
|
|
||||||
self._log_event(f"Dump of '{var_name}' returned no data.", False); dump_details_msg = "Dump returned no data"
|
|
||||||
|
|
||||||
self._add_produced_file_entry(bp_spec_for_file, var_name, file_save_path, dump_status_msg,
|
data_for_csv_list = json_data_for_csv
|
||||||
gdb_bp_num=hit_gdb_bp_num, address=current_pc_address, details=dump_details_msg)
|
if isinstance(json_data_for_csv, dict) and not isinstance(json_data_for_csv, list): data_for_csv_list = [json_data_for_csv]
|
||||||
|
elif not isinstance(json_data_for_csv, list): data_for_csv_list = [{"value": json_data_for_csv}]
|
||||||
|
elif isinstance(json_data_for_csv, list) and json_data_for_csv and not all(isinstance(item, dict) for item in json_data_for_csv):
|
||||||
|
data_for_csv_list = [{"value": item} for item in json_data_for_csv]
|
||||||
|
|
||||||
action_summary["variables_dumped_count"] += current_dump_success_count_for_this_hit
|
save_data_to_csv_file(data_for_csv_list, csv_filepath)
|
||||||
|
log_status_msg = "Success"; current_dump_success_count += 1
|
||||||
|
self._log_event(f"Converted and saved '{var_name}' to '{os.path.basename(csv_filepath)}' (CSV).", False)
|
||||||
|
except Exception as csv_e:
|
||||||
|
log_status_msg = "CSV Conversion Failed"; log_details_msg = f"CSV Error: {csv_e}"
|
||||||
|
self._log_event(f"Error converting/saving CSV for '{var_name}': {csv_e}", True)
|
||||||
|
else: # Unknown format, should not happen with combobox
|
||||||
|
log_status_msg = "Error"; log_details_msg = f"Unsupported format '{output_format_cfg}' for '{var_name}'."
|
||||||
|
self._log_event(log_details_msg, True)
|
||||||
|
else: # Dump status was 'error'
|
||||||
|
err_detail_from_payload = dump_status_payload.get("details", dump_status_payload.get("message", "GDB dumper script reported an error."))
|
||||||
|
log_status_msg = "GDB Dump Failed"; log_details_msg = f"Dumper Error: {err_detail_from_payload}"
|
||||||
|
self._log_event(f"Error dumping '{var_name}': {err_detail_from_payload}", True)
|
||||||
|
if "raw_gdb_output" in dump_status_payload: # This key might not exist with new dumper logic
|
||||||
|
self.gdb_output_writer(f"--- Raw GDB output for failed dump of '{var_name}' ---\n{dump_status_payload['raw_gdb_output']}\n--- End ---\n")
|
||||||
|
|
||||||
if current_dump_success_count_for_this_hit == len(vars_to_dump_for_action) and vars_to_dump_for_action:
|
self._add_produced_file_entry(bp_spec_for_file, var_name, final_file_path_for_log, log_status_msg,
|
||||||
action_summary["status"] = "Completed"
|
gdb_bp_num=hit_gdb_bp_num, address=current_pc_address, details=log_details_msg,
|
||||||
elif not vars_to_dump_for_action:
|
original_json_path=original_json_path_for_log)
|
||||||
action_summary["status"] = "Completed (No Vars)"
|
|
||||||
else:
|
|
||||||
action_summary["status"] = "Completed with Errors"
|
|
||||||
|
|
||||||
if not current_action_config.get("continue_after_dump", True):
|
action_summary["variables_dumped_count"] += current_dump_success_count
|
||||||
should_continue_after_all_these_actions = False # If any action says not to continue, we stop
|
if current_dump_success_count == len(vars_to_dump) and vars_to_dump: action_summary["status"] = "Completed"
|
||||||
|
elif not vars_to_dump: action_summary["status"] = "Completed (No Vars)"
|
||||||
if self._stop_requested: break # Break from main while loop if stop requested during action processing
|
else: action_summary["status"] = "Completed with Errors"
|
||||||
|
if not current_action_config.get("continue_after_dump", True): should_continue_after_all_these_actions = False
|
||||||
|
|
||||||
|
if self._stop_requested: break
|
||||||
if should_continue_after_all_these_actions:
|
if should_continue_after_all_these_actions:
|
||||||
self._log_event(f"Continuing after processing actions at {hit_bp_details_for_log}...", True)
|
self._log_event(f"Continuing after processing actions at {hit_bp_details_for_log}...", True)
|
||||||
gdb_output = self.gdb_session.continue_execution(timeout=run_timeout)
|
gdb_output = self.gdb_session.continue_execution(timeout=run_timeout)
|
||||||
self.gdb_output_writer(gdb_output)
|
self.gdb_output_writer(gdb_output)
|
||||||
program_has_exited = self._check_program_exited_from_output(gdb_output) # MODIFIED
|
program_has_exited = self._check_program_exited_from_output(gdb_output)
|
||||||
if program_has_exited:
|
if program_has_exited: self._log_event(f"Program exited after continue. Output: {gdb_output[:250]}", True)
|
||||||
self._log_event(f"Program exited after continue. Output: {gdb_output[:250]}", True)
|
|
||||||
else:
|
else:
|
||||||
self._log_event(f"Execution halted after processing actions at {hit_bp_details_for_log} as per profile.", True)
|
self._log_event(f"Execution halted after actions at {hit_bp_details_for_log} as per profile.", True)
|
||||||
program_has_exited = True # Treat as program exit for the loop
|
program_has_exited = True
|
||||||
|
elif self._check_program_exited_from_output(gdb_output):
|
||||||
elif self._check_program_exited_from_output(gdb_output): # MODIFIED: Check if GDB indicated program exit
|
program_has_exited = True; self._log_event(f"Program exited. Output: {gdb_output[:250]}", True)
|
||||||
program_has_exited = True
|
elif "received signal" in gdb_output.lower() and "SIGINT" not in gdb_output.upper():
|
||||||
self._log_event(f"Program exited. Output: {gdb_output[:250]}", True)
|
program_has_exited = True; self._log_event(f"Program received signal. Output: {gdb_output[:250]}", True)
|
||||||
elif "received signal" in gdb_output.lower() and "SIGINT" not in gdb_output.upper(): # Ignore SIGINT from manual ctrl-c in GDB console
|
|
||||||
program_has_exited = True
|
|
||||||
self._log_event(f"Program received signal. Output: {gdb_output[:250]}", True)
|
|
||||||
self.profile_execution_summary["status"] = "Completed (Program Signalled/Crashed)"
|
self.profile_execution_summary["status"] = "Completed (Program Signalled/Crashed)"
|
||||||
elif not hit_gdb_bp_num and not self._check_program_exited_from_output(gdb_output) and not self._stop_requested:
|
elif not hit_gdb_bp_num and not self._check_program_exited_from_output(gdb_output) and not self._stop_requested:
|
||||||
# Program stopped for a reason other than a recognized breakpoint or exit.
|
logger.warning(f"GDB output after 'continue' did not indicate BP or exit. Raw: {gdb_output[:300]}")
|
||||||
# This could be a signal, an error, or an unexpected stop.
|
|
||||||
# For safety, if GDB is still alive, we might want to log this and then decide if we should try to continue or stop.
|
|
||||||
# If the program truly never stops on its own and only via breakpoints or _stop_requested,
|
|
||||||
# this branch might indicate an issue or an unexpected GDB state.
|
|
||||||
# For now, if GDB is alive and we didn't hit a BP and program didn't exit, assume we should wait or GDB is hung on continue.
|
|
||||||
# The send_cmd in continue_execution should timeout if GDB is truly hung.
|
|
||||||
# If output from 'continue' does not contain a breakpoint or exit message,
|
|
||||||
# the loop might continue if GDB sends back (gdb) prompt without stopping.
|
|
||||||
# This part of the logic might need refinement if GDB can be in a "running but not stopped at BP" state
|
|
||||||
# where we'd expect it to eventually hit another BP or exit.
|
|
||||||
# The current loop relies on `gdb_output` from `continue_execution` to update `program_has_exited` or `hit_gdb_bp_num`.
|
|
||||||
# If `continue_execution` returns without these, and `is_alive` is true, the loop continues.
|
|
||||||
# This seems okay, as we'd expect the *next* `continue` to either hit a BP, exit, or timeout.
|
|
||||||
# The `program_has_exited` check is critical.
|
|
||||||
logger.warning(f"GDB output after 'continue' did not indicate a breakpoint or program exit. Raw output: {gdb_output[:300]}")
|
|
||||||
# Let the loop continue, relying on GDB to eventually report a stop or exit, or for send_cmd to timeout.
|
|
||||||
# If no stop/exit is reported and the program is just running, `send_cmd` for `continue` should reflect that
|
|
||||||
# (e.g., not returning immediately or returning only `Continuing.`).
|
|
||||||
# The `_parse_breakpoint_hit_output` will then return None, and `_check_program_exited_from_output` will be false.
|
|
||||||
# The `while` loop condition `not program_has_exited` will keep it running.
|
|
||||||
pass # Explicitly pass if no action and no exit, let continue handle it.
|
|
||||||
|
|
||||||
|
|
||||||
if program_has_exited: break
|
if program_has_exited: break
|
||||||
|
|
||||||
final_status = "Completed"
|
final_status = "Completed"
|
||||||
if program_has_exited and not self._stop_requested:
|
if program_has_exited and not self._stop_requested:
|
||||||
if any(s["status"] == "Pending" or s["status"] == "Pending in GDB" for s in self.profile_execution_summary["actions_summary"]):
|
if any(s["status"] == "Pending" or s["status"] == "Pending in GDB" for s in self.profile_execution_summary["actions_summary"]): final_status = "Completed (Program Exited Prematurely)"
|
||||||
final_status = "Completed (Program Exited Prematurely)"
|
if self.profile_execution_summary["status"] not in ["Initialized", "Error: No BPs Mapped", "Error: No Debug Symbols"] and \
|
||||||
if self.profile_execution_summary["status"] not in ["Initialized", "Error: No BPs Mapped", "Error: No Debug Symbols"]:
|
not ("Crashed" in self.profile_execution_summary["status"] or "Signalled" in self.profile_execution_summary["status"]):
|
||||||
if "Crashed" in self.profile_execution_summary["status"] or "Signalled" in self.profile_execution_summary["status"]:
|
self.profile_execution_summary["status"] = final_status
|
||||||
pass # Keep the more specific status
|
elif self._stop_requested: self.profile_execution_summary["status"] = "Completed (User Stopped)"
|
||||||
else:
|
elif not (self.gdb_session and self.gdb_session.is_alive()) and not program_has_exited:
|
||||||
self.profile_execution_summary["status"] = final_status
|
self.profile_execution_summary["status"] = "Error: GDB Died Unexpectedly"; self._log_event("Error: GDB session died unexpectedly.", True)
|
||||||
elif self._stop_requested:
|
else:
|
||||||
self.profile_execution_summary["status"] = "Completed (User Stopped)"
|
if any(s["status"] == "Pending" for s in self.profile_execution_summary["actions_summary"]): self.profile_execution_summary["status"] = "Completed (Some Actions Pending/Not Hit)"
|
||||||
elif not (self.gdb_session and self.gdb_session.is_alive()) and not program_has_exited: # GDB died
|
else: self.profile_execution_summary["status"] = "Completed (All Triggered Actions Processed)"
|
||||||
self.profile_execution_summary["status"] = "Error: GDB Died Unexpectedly"
|
|
||||||
self._log_event("Error: GDB session died unexpectedly during execution.", True)
|
|
||||||
else: # Loop finished, GDB alive, not exited, not stopped by user -> implies all actions processed as per logic
|
|
||||||
if any(s["status"] == "Pending" for s in self.profile_execution_summary["actions_summary"]):
|
|
||||||
self.profile_execution_summary["status"] = "Completed (Some Actions Pending/Not Hit)"
|
|
||||||
else:
|
|
||||||
self.profile_execution_summary["status"] = "Completed (All Triggered Actions Processed)"
|
|
||||||
|
|
||||||
|
|
||||||
except InterruptedError as ie:
|
except InterruptedError as ie:
|
||||||
self.profile_execution_summary["status"] = "Interrupted (User Stop)"
|
self.profile_execution_summary["status"] = "Interrupted (User Stop)"; self._log_event(str(ie), True)
|
||||||
self._log_event(str(ie), True)
|
|
||||||
except FileNotFoundError as fnf_e:
|
except FileNotFoundError as fnf_e:
|
||||||
msg = f"Error running profile '{profile_name}': File not found - {fnf_e}"
|
msg = f"Error running profile '{profile_name}': File not found - {fnf_e}"; self._log_event(msg, True); self.profile_execution_summary["status"] = f"Error: {fnf_e}"
|
||||||
self._log_event(msg, True); self.profile_execution_summary["status"] = f"Error: {fnf_e}"
|
|
||||||
except (ConnectionError, TimeoutError) as session_e:
|
except (ConnectionError, TimeoutError) as session_e:
|
||||||
msg = f"Session error running profile '{profile_name}': {type(session_e).__name__} - {session_e}"
|
msg = f"Session error for profile '{profile_name}': {type(session_e).__name__} - {session_e}"; self._log_event(msg, True); self.profile_execution_summary["status"] = f"Error: {session_e}"
|
||||||
self._log_event(msg, True); self.profile_execution_summary["status"] = f"Error: {session_e}"
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
msg = f"Unexpected error running profile '{profile_name}': {type(e).__name__} - {e}"
|
msg = f"Unexpected error for profile '{profile_name}': {type(e).__name__} - {e}"; self._log_event(msg, True); logger.critical(msg, exc_info=True); self.profile_execution_summary["status"] = f"Critical Error: {e}"
|
||||||
self._log_event(msg, True); logger.critical(msg, exc_info=True)
|
|
||||||
self.profile_execution_summary["status"] = f"Critical Error: {e}"
|
|
||||||
finally:
|
finally:
|
||||||
self.profile_execution_summary["end_time"] = datetime.now().isoformat()
|
self.profile_execution_summary["end_time"] = datetime.now().isoformat()
|
||||||
self.profile_execution_summary["execution_log"] = self.execution_event_log
|
self.profile_execution_summary["execution_log"] = self.execution_event_log
|
||||||
self.profile_execution_summary["files_produced_detailed"] = self.produced_files_log
|
self.profile_execution_summary["files_produced_detailed"] = self.produced_files_log
|
||||||
self._cleanup_session()
|
self._cleanup_session()
|
||||||
|
|
||||||
summary_file_path = self._finalize_summary_report(self.current_run_output_path)
|
summary_file_path = self._finalize_summary_report(self.current_run_output_path)
|
||||||
final_gui_message = (f"Profile '{profile_name}' execution cycle finished. "
|
final_gui_message = (f"Profile '{profile_name}' cycle finished. Status: {self.profile_execution_summary.get('status', 'Unknown')}. "
|
||||||
f"Status: {self.profile_execution_summary.get('status', 'Unknown')}. "
|
f"Summary: {summary_file_path if summary_file_path else 'N/A (see logs)'}.")
|
||||||
f"Summary report attempt at: {summary_file_path if summary_file_path else 'N/A (see logs)'}.")
|
self._log_event(final_gui_message, True); self.is_running = False
|
||||||
self._log_event(final_gui_message, True)
|
|
||||||
self.is_running = False
|
|
||||||
|
|
||||||
def _finalize_summary_report(self, run_output_path: Optional[str]) -> Optional[str]:
|
def _finalize_summary_report(self, run_output_path: Optional[str]) -> Optional[str]:
|
||||||
if not run_output_path:
|
if not run_output_path:
|
||||||
logger.warning("No run output path available, cannot save summary report to specific location.")
|
logger.warning("No run output path, cannot save summary report."); logger.info(f"Exec Summary '{self.profile.get('profile_name')}':\n{json.dumps(self.profile_execution_summary, indent=2)}"); return None
|
||||||
logger.info(f"Execution Summary for '{self.profile.get('profile_name')}':\n{json.dumps(self.profile_execution_summary, indent=2)}")
|
|
||||||
return None
|
|
||||||
sane_profile_name = sanitize_filename_component(self.profile.get("profile_name", "profile_run"))
|
sane_profile_name = sanitize_filename_component(self.profile.get("profile_name", "profile_run"))
|
||||||
summary_filename = f"_{sane_profile_name}_summary_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
summary_filename = f"_{sane_profile_name}_summary_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
|
||||||
summary_filepath = os.path.join(run_output_path, summary_filename)
|
summary_filepath = os.path.join(run_output_path, summary_filename)
|
||||||
try:
|
try:
|
||||||
with open(summary_filepath, 'w', encoding='utf-8') as f_summary:
|
with open(summary_filepath, 'w', encoding='utf-8') as f_summary:
|
||||||
json.dump(self.profile_execution_summary, f_summary, indent=2, ensure_ascii=False)
|
json.dump(self.profile_execution_summary, f_summary, indent=2, ensure_ascii=False)
|
||||||
logger.info(f"Execution summary report saved to: {summary_filepath}")
|
logger.info(f"Execution summary report saved: {summary_filepath}"); return summary_filepath
|
||||||
return summary_filepath
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to save execution summary report to '{summary_filepath}': {e}")
|
logger.error(f"Failed to save summary report to '{summary_filepath}': {e}"); return None
|
||||||
return None
|
|
||||||
|
|
||||||
def request_stop(self) -> None:
|
def request_stop(self) -> None:
|
||||||
self._log_event("Stop requested for current profile execution...", True)
|
self._log_event("Stop requested for current profile execution...", True)
|
||||||
@ -631,14 +504,9 @@ class ProfileExecutor:
|
|||||||
self._log_event("Cleaning up GDB session...", False)
|
self._log_event("Cleaning up GDB session...", False)
|
||||||
quit_timeout = self._get_setting("timeouts", "gdb_quit", 10)
|
quit_timeout = self._get_setting("timeouts", "gdb_quit", 10)
|
||||||
try:
|
try:
|
||||||
# We no longer send kill_program here explicitly if we want the program to continue
|
|
||||||
# GDB quit will handle killing the inferior if it's still running and GDB exits.
|
|
||||||
self.gdb_session.quit(timeout=quit_timeout)
|
self.gdb_session.quit(timeout=quit_timeout)
|
||||||
self.gdb_output_writer("GDB session quit during cleanup.\n")
|
self.gdb_output_writer("GDB session quit during cleanup.\n")
|
||||||
except Exception as e_quit:
|
except Exception as e_quit: logger.error(f"Exception during GDB quit in cleanup: {e_quit}")
|
||||||
logger.error(f"Exception during GDB quit in cleanup: {e_quit}")
|
finally: self.gdb_session = None
|
||||||
finally:
|
elif self.gdb_session: self.gdb_session = None
|
||||||
self.gdb_session = None
|
|
||||||
elif self.gdb_session:
|
|
||||||
self.gdb_session = None
|
|
||||||
logger.info("ProfileExecutor GDB session resources attempted cleanup.")
|
logger.info("ProfileExecutor GDB session resources attempted cleanup.")
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user