# File: cpp_python_debug/core/gdb_dumper.py
import gdb
import json
import traceback
import os
import time
import re
from typing import Optional, Dict, Any, List, Tuple

# --- Initial Sourcing Diagnostic ---
try:
    gdb.write(
        "GDB_DUMPER_SCRIPT: Top of script reached by GDB (v8_path_fix).\n"
    )
    gdb.flush()
except Exception:
    pass

# --- Internal Dumper Script Logger Setup (codice invariato) ---
GDB_DUMPER_INTERNAL_LOG_FILENAME = "gdb_dumper_script_internal.log"
GDB_DUMPER_INTERNAL_LOG_PATH: Optional[str] = None
CAN_WRITE_TO_INTERNAL_LOG = False
_log_dir_from_gdb_var_str: Optional[str] = None
try:
    _log_dir_obj = gdb.parse_and_eval("$py_dumper_script_log_dir")
    if _log_dir_obj is not None:
        _log_dir_from_gdb_var_str = str(_log_dir_obj).strip('"')
        if _log_dir_from_gdb_var_str and os.path.isdir(os.path.normpath(_log_dir_from_gdb_var_str)):
            GDB_DUMPER_INTERNAL_LOG_PATH = os.path.join(os.path.normpath(_log_dir_from_gdb_var_str), GDB_DUMPER_INTERNAL_LOG_FILENAME)
except Exception: pass
if GDB_DUMPER_INTERNAL_LOG_PATH is None:
    try:
        user_home_dir = os.path.expanduser("~"); fallback_log_dir = os.path.join(user_home_dir, ".cpp_python_debug_dumper_logs")
        os.makedirs(fallback_log_dir, exist_ok=True)
        GDB_DUMPER_INTERNAL_LOG_PATH = os.path.join(fallback_log_dir, GDB_DUMPER_INTERNAL_LOG_FILENAME)
    except Exception: GDB_DUMPER_INTERNAL_LOG_PATH = None
def _dumper_log_write(message: str):
    global CAN_WRITE_TO_INTERNAL_LOG
    try:
        if GDB_DUMPER_INTERNAL_LOG_PATH:
            timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())
            pid_str = "NO_INF"
            if gdb.selected_inferior() and hasattr(gdb.selected_inferior(), "pid") and gdb.selected_inferior().pid is not None:
                pid_str = f"PID:{gdb.selected_inferior().pid}"
            with open(GDB_DUMPER_INTERNAL_LOG_PATH, "a", encoding="utf-8") as f_log: f_log.write(f"[{timestamp} {pid_str}] DUMPER: {str(message)}\n")
            if not CAN_WRITE_TO_INTERNAL_LOG: CAN_WRITE_TO_INTERNAL_LOG = True; _dumper_log_write(f"Internal dumper log active: {GDB_DUMPER_INTERNAL_LOG_PATH}")
        else:
            if CAN_WRITE_TO_INTERNAL_LOG: CAN_WRITE_TO_INTERNAL_LOG = False
            gdb.write(f"GDB_DUMPER_LOG_FALLBACK: {message}\n"); gdb.flush()
    except Exception as e: CAN_WRITE_TO_INTERNAL_LOG = False; print(f"CRITICAL GDB DUMPER LOG ERROR: {e}")
_dumper_log_write("--- GDB Dumper Script Initializing ---")
if not CAN_WRITE_TO_INTERNAL_LOG: _dumper_log_write("Using GDB console fallback for logging.")
gdb.write("GDB_DUMPER_SCRIPT: Logger initialization sequence complete.\n"); gdb.flush()


# --- INIZIO MODIFICA CHIAVE ---
def _get_gdb_variable_as_string(var_name_no_dollar: str, default_value: str) -> str:
    """
    Reads a GDB convenience variable and returns its value as a clean Python string.
    This function is designed to robustly handle strings passed from the outside,
    stripping any quoting added by GDB.
    """
    full_var_name = f"${var_name_no_dollar}"
    try:
        gdb_val_obj = gdb.parse_and_eval(full_var_name)
        if gdb_val_obj is None:
            _dumper_log_write(f"GDB var '{full_var_name}' is None. Using default: '{default_value}'")
            return default_value

        # Convert the gdb.Value to a string. This might include quotes and escaped chars.
        raw_str = str(gdb_val_obj)
        _dumper_log_write(f"Read GDB var '{full_var_name}' raw value: {raw_str}")

        # Aggressively clean the string. GDB can represent a string like "C:\path"
        # as "\"C:\\\\path\"". We need to handle this.
        # 1. Strip leading/trailing whitespace.
        cleaned_str = raw_str.strip()
        # 2. Strip a single layer of quotes if they exist at both ends.
        if len(cleaned_str) >= 2 and cleaned_str.startswith('"') and cleaned_str.endswith('"'):
            cleaned_str = cleaned_str[1:-1]
        
        # 3. GDB might escape backslashes. We can try to un-escape them, but for paths,
        # it's often safer to just replace them with forward slashes, which Python handles well.
        # This step is often not necessary if the path is already clean, but good for robustness.
        # final_path = cleaned_str.replace('\\\\', '\\') # Be careful with un-escaping

        _dumper_log_write(f"  Cleaned string value for '{full_var_name}': '{cleaned_str}'")
        return cleaned_str if cleaned_str else default_value

    except Exception as e:
        _dumper_log_write(f"Error reading GDB var '{full_var_name}': {e}. Using default: '{default_value}'")
        return default_value
# --- FINE MODIFICA CHIAVE ---

# Il resto delle funzioni di lettura variabili e delle costanti è invariato...
def _get_gdb_variable_as_int(var_name_no_dollar: str, default_value: int) -> int:
    # (codice invariato)
    full_var_name = f"${var_name_no_dollar}"
    try:
        gdb_val = gdb.parse_and_eval(full_var_name)
        if gdb_val is not None: return int(gdb_val)
        return default_value
    except: return default_value

def _get_gdb_variable_as_bool(var_name_no_dollar: str, default_value: bool) -> bool:
    # (codice invariato)
    full_var_name = f"${var_name_no_dollar}"
    try:
        gdb_val = gdb.parse_and_eval(full_var_name)
        if gdb_val is not None:
            val_str = str(gdb_val).lower().strip().strip('"')
            if val_str in ("1", "true", "yes", "on"): return True
            if val_str in ("0", "false", "no", "off"): return False
        return default_value
    except: return default_value

# Dumper Configuration Constants
DEFAULT_MAX_ARRAY_ELEMENTS_SCRIPT = 100
DEFAULT_MAX_RECURSION_DEPTH_SCRIPT = 10
DEFAULT_MAX_STRING_LENGTH_SCRIPT = 2048
DEFAULT_DUMP_DIAGNOSTIC_JSON_TO_FILE_SCRIPT = False
DEFAULT_DIAGNOSTIC_JSON_OUTPUT_DIR_SCRIPT = ""
DEFAULT_TARGET_OUTPUT_FILEPATH_FALLBACK_SCRIPT = ""
DEFAULT_TARGET_OUTPUT_FORMAT_FALLBACK_SCRIPT = "json"

MAX_ARRAY_ELEMENTS = _get_gdb_variable_as_int("py_dumper_max_array_elements", DEFAULT_MAX_ARRAY_ELEMENTS_SCRIPT)
MAX_RECURSION_DEPTH = _get_gdb_variable_as_int("py_dumper_max_recursion_depth", DEFAULT_MAX_RECURSION_DEPTH_SCRIPT)
MAX_STRING_LENGTH = _get_gdb_variable_as_int("py_dumper_max_string_length", DEFAULT_MAX_STRING_LENGTH_SCRIPT)
DUMP_THIS_SCRIPT_DIAGNOSTIC_JSON = _get_gdb_variable_as_bool("py_dumper_dump_diagnostic_json_to_file", DEFAULT_DUMP_DIAGNOSTIC_JSON_TO_FILE_SCRIPT)
THIS_SCRIPT_DIAGNOSTIC_JSON_DIR = _get_gdb_variable_as_string("py_dumper_diagnostic_json_output_dir", DEFAULT_DIAGNOSTIC_JSON_OUTPUT_DIR_SCRIPT)

if DUMP_THIS_SCRIPT_DIAGNOSTIC_JSON and THIS_SCRIPT_DIAGNOSTIC_JSON_DIR:
    THIS_SCRIPT_DIAGNOSTIC_JSON_DIR = os.path.normpath(THIS_SCRIPT_DIAGNOSTIC_JSON_DIR)
    if not os.path.exists(THIS_SCRIPT_DIAGNOSTIC_JSON_DIR):
        try: os.makedirs(THIS_SCRIPT_DIAGNOSTIC_JSON_DIR, exist_ok=True)
        except OSError as e: DUMP_THIS_SCRIPT_DIAGNOSTIC_JSON = False

# Il resto del file (EnhancedJsonEncoder, GDBDumpJsonCommand) è identico alla versione precedente
# che ti ho fornito con il supporto alle matrici. La modifica chiave è solo nella funzione
# _get_gdb_variable_as_string. Lo includo comunque per completezza.

def _sanitize_filename_component_dumper(component: str) -> str:
    if not component: return "unknown_comp"
    s = re.sub(r'[\\/*?:"<>|]', "_", component); s = s.replace(" ", "_"); s = re.sub(r"_+", "_", s); s = s.strip("_")
    return s[:64] if len(s) > 64 else (s if s else "sanitized_empty")

class EnhancedJsonEncoder(json.JSONEncoder):
    # (codice invariato)
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.current_depth = 0
        self.visited_values = {}
    def _get_value_unique_key(self, gdb_val):
        if not gdb_val: return None
        try:
            addr = "no_addr"
            if hasattr(gdb_val, "address") and gdb_val.address is not None:
                try: addr = str(gdb_val.address)
                except gdb.error: addr = "err_addr"
            t_str = str(gdb_val.type) if hasattr(gdb_val, "type") and gdb_val.type is not None else "no_type"
            return f"{t_str}@{addr}"
        except Exception as e: _dumper_log_write(f"KeyErr:{e}"); return f"key_err_{id(gdb_val)}"
    def _is_visitable_value(self, gdb_val):
        if not gdb_val or not hasattr(gdb_val, "type") or gdb_val.type is None: return False
        try:
            return gdb_val.type.strip_typedefs().code in [
                gdb.TYPE_CODE_PTR, gdb.TYPE_CODE_ARRAY, gdb.TYPE_CODE_STRUCT,
                gdb.TYPE_CODE_UNION, gdb.TYPE_CODE_REF,
            ]
        except gdb.error: return False
    def _serialize_value(self, gdb_val_orig):
        gdb_val = gdb_val_orig
        try:
            if hasattr(gdb_val, "type") and gdb_val.type and gdb_val.type.code == gdb.TYPE_CODE_REF:
                gdb_val = gdb_val.referenced_value()
        except gdb.error: pass

        if self.current_depth > MAX_RECURSION_DEPTH: return f"<max_depth_reached:{MAX_RECURSION_DEPTH}>"
        unique_key = None
        is_visitable = self._is_visitable_value(gdb_val)
        if is_visitable:
            unique_key = self._get_value_unique_key(gdb_val)
            if unique_key and unique_key in self.visited_values and self.current_depth >= self.visited_values[unique_key]:
                return f"<cyclic_ref_to:{unique_key.split('@',1)[0]}>"

        self.current_depth += 1
        if is_visitable and unique_key: self.visited_values[unique_key] = self.current_depth
        serialized_val = None
        try:
            if not hasattr(gdb_val, "type") or gdb_val.type is None: serialized_val = "<no_type>"
            elif gdb_val.is_optimized_out: serialized_val = "<optimized_out>"
            else:
                val_type = gdb_val.type.strip_typedefs(); type_code = val_type.code
                original_type_str = str(gdb_val.type)
                std_string_patterns = ["std::basic_string", "std::string", "std::__cxx11::string", "std::__1::string"]
                is_std_string_type = any(p in original_type_str for p in std_string_patterns)
                is_c_style_char_ptr = (type_code == gdb.TYPE_CODE_PTR and val_type.target().strip_typedefs().code == gdb.TYPE_CODE_INT and "char" in str(val_type.target().strip_typedefs().name or "").lower())
                if is_std_string_type or is_c_style_char_ptr: serialized_val = self._handle_string_types(gdb_val, is_std_string_type, original_type_str)
                elif type_code == gdb.TYPE_CODE_INT: serialized_val = int(gdb_val)
                elif type_code == gdb.TYPE_CODE_FLT: serialized_val = float(gdb_val)
                elif type_code == gdb.TYPE_CODE_BOOL: serialized_val = bool(gdb_val)
                elif type_code == gdb.TYPE_CODE_ENUM: serialized_val = str(gdb_val)
                else:
                    iterator_children = self._get_iterator_children(gdb_val)
                    if iterator_children is not None: serialized_val = iterator_children
                    elif type_code == gdb.TYPE_CODE_PTR: serialized_val = self._handle_pointer(gdb_val, val_type)
                    elif type_code == gdb.TYPE_CODE_ARRAY: serialized_val = self._handle_c_array(gdb_val, val_type)
                    elif type_code in [gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION]: serialized_val = self._handle_struct_or_class(gdb_val, val_type, original_type_str)
                    else: serialized_val = str(gdb_val)[:MAX_STRING_LENGTH]
        except Exception as e: serialized_val = f"<serialization_error:{e}>"
        finally:
            self.current_depth -= 1
            if is_visitable and unique_key and unique_key in self.visited_values and self.visited_values[unique_key] == self.current_depth + 1:
                del self.visited_values[unique_key]
        return serialized_val
    def default(self, o):
        if isinstance(o, gdb.Value):
            is_top = self.current_depth == 0
            if is_top: self.visited_values.clear()
            try: return self._serialize_value(o)
            finally:
                if is_top: self.current_depth = 0
        return json.JSONEncoder.default(self, o)
    def _handle_string_types(self, gdb_val, is_std, original_type_str):
        # (metodo invariato)
        str_val = None
        try:
            read_len = MAX_STRING_LENGTH + 1 if MAX_STRING_LENGTH > 0 else 0
            str_val = gdb_val.string("utf-8", "replace", length=read_len)
        except Exception as e:
            if is_std: str_val = self._handle_std_string_fallback(gdb_val, original_type_str)
            else: str_val = f"<err_read_char_ptr>"
        if isinstance(str_val, str) and MAX_STRING_LENGTH > 0 and len(str_val) > MAX_STRING_LENGTH:
            return str_val[:MAX_STRING_LENGTH] + "<str_trunc>"
        return str_val
    def _handle_std_string_fallback(self, gdb_val, original_type_str):
        # (metodo invariato)
        try:
            ptr_field = None
            if "_M_dataplus" in (f.name for f in gdb_val.type.fields()): ptr_field = gdb_val["_M_dataplus"]["_M_p"]
            elif "_M_p" in (f.name for f in gdb_val.type.fields()): ptr_field = gdb_val["_M_p"]
            if ptr_field: return ptr_field.string("utf-8", "replace", length=MAX_STRING_LENGTH)
        except: pass
        return f"<std_str_fallback_failed:{original_type_str}>"
    def _get_iterator_children(self, gdb_val):
        # (metodo invariato)
        type_name = str(gdb_val.type.strip_typedefs().name or "")
        if "std::basic_string" in type_name: return None
        try:
            elements, count = [], 0
            for child in gdb_val.children():
                if MAX_ARRAY_ELEMENTS > 0 and count >= MAX_ARRAY_ELEMENTS: elements.append("<truncated>"); break
                key, val = child if isinstance(child, tuple) else (None, child)
                elements.append({"key": str(key), "value": self._serialize_value(val)} if key and "map" in type_name else self._serialize_value(val))
                count += 1
            return elements
        except: return None
    def _handle_pointer(self, gdb_val, val_type):
        # (metodo invariato)
        if not gdb_val: return None
        try: return self._serialize_value(gdb_val.dereference())
        except: return f"<err_deref_ptr:0x{gdb_val:x}>"
    def _handle_c_array(self, gdb_val, val_type):
        # (metodo invariato, la versione ricorsiva precedente era un errore, meglio gestire solo 1D qui)
        arr = []
        try:
            bounds = val_type.range()
            limit = min(bounds[1] - bounds[0] + 1, MAX_ARRAY_ELEMENTS) if MAX_ARRAY_ELEMENTS > 0 else (bounds[1] - bounds[0] + 1)
            for i in range(limit): arr.append(self._serialize_value(gdb_val[bounds[0] + i]))
            if MAX_ARRAY_ELEMENTS > 0 and (bounds[1] - bounds[0] + 1) > MAX_ARRAY_ELEMENTS: arr.append("<c_arr_trunc>")
            return arr
        except: return "<err_c_arr>"
    def _handle_struct_or_class(self, gdb_val, val_type, original_type_str=""):
        # (metodo invariato)
        obj_dict = {"_type": original_type_str or str(val_type.name or val_type)}
        try:
            for field in val_type.fields():
                if field.name is None or (field.artificial and not field.is_base_class): continue
                if field.is_base_class:
                    try:
                        base_data = self._serialize_value(gdb_val.cast(field.type))
                        if isinstance(base_data, dict): obj_dict.update({f"{str(field.type.name)}::{k}": v for k, v in base_data.items() if k != "_type"})
                    except: pass
                else:
                    try: obj_dict[field.name] = self._serialize_value(gdb_val[field.name])
                    except: obj_dict[field.name] = "<err_field>"
            return obj_dict
        except: return "<err_struct>"

class GDBDumpJsonCommand(gdb.Command):
    # (metodo __init__ invariato)
    def __init__(self):
        super(GDBDumpJsonCommand, self).__init__("dump_json", gdb.COMMAND_DATA, gdb.COMPLETE_SYMBOL)
        self.output_start_delimiter = "START_JSON_OUTPUT_DELIMITER_SEQUENCE_v1"
        self.output_end_delimiter = "END_JSON_OUTPUT_DELIMITER_SEQUENCE_v1"
        _dumper_log_write("GDBDumpJsonCommand class initialized.")
    def invoke(self, arg_string_raw: str, from_tty: bool):
        current_target_filepath = _get_gdb_variable_as_string("py_dumper_target_output_filepath", DEFAULT_TARGET_OUTPUT_FILEPATH_FALLBACK_SCRIPT)
        current_target_format = _get_gdb_variable_as_string("py_dumper_target_output_format", DEFAULT_TARGET_OUTPUT_FORMAT_FALLBACK_SCRIPT).lower()

        _dumper_log_write(f"--- dump_json command invoked for expression: '{arg_string_raw}' ---")
        _dumper_log_write(f"  Target Output Filepath: '{current_target_filepath}'")

        status_payload: Dict[str, Any] = {
            "status": "error",
            "message": "Initialization error",
            "variable_dumped": arg_string_raw.strip(),
            "filepath_written": None,
            "details": "An error occurred before GDB evaluation."
        }

        try:
            expression_to_eval_full = arg_string_raw.strip()
            if not expression_to_eval_full:
                raise ValueError("No expression provided to dump_json.")

            parts = expression_to_eval_full.split('@', 1)
            base_expr_str = parts[0].strip()
            
            data_to_encode: Any
            metadata: Dict[str, Any] = {} # Inizializza il dizionario dei metadati

            # Controlla se è stata fornita una dimensione (sintassi @)
            if len(parts) > 1 and parts[1].strip():
                dim_expr_str = parts[1].strip()
                dim_parts = [p.strip() for p in dim_expr_str.split(',') if p.strip()]

                if len(dim_parts) == 1:  # Vettore: var@size
                    data_to_encode, metadata = self._handle_dynamic_vector(base_expr_str, dim_parts[0])
                elif len(dim_parts) == 2:  # Matrice: var@rows,cols
                    data_to_encode, metadata = self._handle_dynamic_matrix(base_expr_str, dim_parts[0], dim_parts[1])
                else:
                    raise ValueError(f"Invalid dimension format '{dim_expr_str}'. Use '@size' or '@rows,cols'.")
            else:
                # Nessuna dimensione fornita, esegui il dump del singolo valore/oggetto
                gdb_val_base = gdb.parse_and_eval(base_expr_str)
                data_to_encode = gdb_val_base
                # Aggiungi metadati di base anche per i non-matrici/vettori
                metadata = {"is_matrix": False, "is_vector": False}

            # Costruisci l'oggetto JSON finale che include dati e metadati
            final_json_object = {
                "metadata": metadata,
                "data": data_to_encode
            }

            # Serializza l'intero oggetto in una stringa JSON
            encoder = EnhancedJsonEncoder(indent=None, separators=(",", ":"), ensure_ascii=False)
            dumped_data_json_string = encoder.encode(final_json_object)

            _dumper_log_write(f"JSON data with metadata generated, length {len(dumped_data_json_string)}.")

            json_to_gdb_console = ""
            if current_target_filepath:
                try:
                    target_dir = os.path.dirname(current_target_filepath)
                    if target_dir:
                        os.makedirs(target_dir, exist_ok=True)
                    with open(current_target_filepath, "w", encoding="utf-8") as f_out:
                        f_out.write(dumped_data_json_string)
                    
                    status_payload.update({
                        "status": "success",
                        "filepath_written": current_target_filepath,
                        "message": "Structured JSON data (with metadata) saved to file.",
                        "details": None
                    })
                    json_to_gdb_console = json.dumps(status_payload)
                except Exception as e_save:
                    status_payload.update({
                        "message": f"Failed to save JSON to file '{current_target_filepath}'.",
                        "details": str(e_save)
                    })
                    json_to_gdb_console = json.dumps(status_payload)
            else:
                # Se non c'è un file di destinazione, invia l'intero JSON strutturato alla console
                json_to_gdb_console = dumped_data_json_string
                status_payload.update({
                    "status": "success_to_console",
                    "message": "Structured JSON data prepared for console output.",
                    "details": None
                })
            
            # Scrivi il risultato (o lo stato) sulla console di GDB
            gdb.write(f"\n{self.output_start_delimiter}\n{json_to_gdb_console}\n{self.output_end_delimiter}\n")
            gdb.flush()

        except Exception as e_invoke:
            # Cattura qualsiasi errore (parsing, valutazione GDB, ecc.)
            status_payload.update({"details": str(e_invoke)})
            _dumper_log_write(f"FATAL ERROR in dump_json: {e_invoke}\n{traceback.format_exc(limit=5)}")
            _try_write_error_to_gdb(status_payload, self.output_start_delimiter, self.output_end_delimiter)

        _dumper_log_write(f"--- dump_json finished for expression: '{arg_string_raw}'. Final status: '{status_payload.get('status')}' ---")
    # (metodi _handle_dynamic_vector e _handle_dynamic_matrix invariati)
    def _handle_dynamic_vector(self, base_expr: str, size_expr: str) -> List[Any]:
        try: length = int(gdb.parse_and_eval(size_expr))
        except Exception as e: raise ValueError(f"Cannot evaluate vector dimension '{size_expr}': {e}")
        elements = []
        limit = min(length, MAX_ARRAY_ELEMENTS) if MAX_ARRAY_ELEMENTS > 0 else length
        for i in range(limit):
            try: elements.append(gdb.parse_and_eval(f"({base_expr})[{i}]"))
            except Exception as e: elements.append(f"<err_idx_{i}: {e}>"); break
        if MAX_ARRAY_ELEMENTS > 0 and length > limit: elements.append(f"<array_truncated_to_{limit}_of_{length}>")
        metadata = {
            "is_matrix": False,
            "original_size": length
        }
        return elements, metadata
    
    def _get_struct_unpack_format(self, gdb_type) -> Tuple[str, List[str]]:
        """
        Determines the Python `struct` unpack format string for a given GDB type.
        Returns the format string and a list of field names.
        """
        _dumper_log_write(f"[_get_struct_unpack_format] Analyzing type: {gdb_type.name}")
        type_code_map = {
            'char': 'c', 'signed char': 'b', 'unsigned char': 'B', 'bool': '?',
            'short': 'h', 'unsigned short': 'H', 'int': 'i', 'unsigned int': 'I',
            'long': 'l', 'unsigned long': 'L', 'long long': 'q', 'unsigned long long': 'Q',
            'float': 'f', 'double': 'd'
        }
        
        endian_char = '<'
        try:
            endian_str = gdb.execute("show endian", to_string=True)
            if "big endian" in endian_str:
                endian_char = '>'
            _dumper_log_write(f"  Target endianness detected: {'big' if endian_char == '>' else 'little'}")
        except gdb.error:
            _dumper_log_write("  Could not determine target endianness, assuming little-endian.")

        format_str = endian_char
        field_names = []
        
        if gdb_type.code != gdb.TYPE_CODE_STRUCT:
             raise TypeError(f"Cannot generate unpack format for non-struct type: {gdb_type.name}")

        for field in gdb_type.fields():
            if field.is_base_class or field.name is None:
                continue
            
            field_type_str = str(field.type.strip_typedefs())
            fmt_char = type_code_map.get(field_type_str)
            
            if fmt_char:
                format_str += fmt_char
                field_names.append(field.name)
                _dumper_log_write(f"  - Field '{field.name}' (type: {field_type_str}) mapped to format char '{fmt_char}'")
            else:
                _dumper_log_write(f"  [!] Unsupported field type '{field_type_str}' for struct unpacking.")
                raise TypeError(f"Unsupported field type in struct: {field_type_str}")
        
        _dumper_log_write(f"  Successfully generated unpack format: '{format_str}' with fields: {field_names}")
        return format_str, field_names

    def _handle_dynamic_matrix(self, base_expr: str, rows_expr: str, cols_expr: str) -> List[List[Any]]:
        try:
            num_rows = int(gdb.parse_and_eval(rows_expr))
            num_cols = int(gdb.parse_and_eval(cols_expr))
            if num_rows < 0 or num_cols < 0:
                raise ValueError("Matrix dimensions cannot be negative.")
            _dumper_log_write(f"[_handle_dynamic_matrix] Dimensions evaluated: rows={num_rows}, cols={num_cols}")
        except Exception as e:
            raise ValueError(f"Cannot evaluate matrix dimensions '{rows_expr},{cols_expr}': {e}")
            
        base_ptr_val = gdb.parse_and_eval(base_expr)
        element_type = base_ptr_val.type.target().strip_typedefs()
        sizeof_element = element_type.sizeof
        _dumper_log_write(f"  Element type: '{element_type.name}', Sizeof(element): {sizeof_element} bytes.")

        if sizeof_element == 0:
            raise ValueError("Cannot dump matrix of zero-sized elements.")
            
        # --- Attempting Optimized Path ---
        try:
            unpack_format, field_names = self._get_struct_unpack_format(element_type)
            _dumper_log_write(f"  Attempting OPTIMIZED path with format '{unpack_format}'.")

            start_address = int(base_ptr_val)
            total_elements = num_rows * num_cols
            total_bytes = total_elements * sizeof_element
            _dumper_log_write(f"  Calculated memory block: address=0x{start_address:x}, total_bytes={total_bytes}")

            if total_bytes > 2_000_000_000: # 2GB sanity check
                 raise ValueError(f"Memory block size ({total_bytes} bytes) is excessively large. Aborting.")

            _dumper_log_write("  Calling inferior.read_memory...")
            inferior = gdb.selected_inferior()
            memory_block = inferior.read_memory(start_address, total_bytes)
            _dumper_log_write(f"  Memory block of size {len(memory_block)} read successfully.")

            matrix_data = []
            import struct
            
            rows_limit = min(num_rows, MAX_ARRAY_ELEMENTS) if MAX_ARRAY_ELEMENTS > 0 else num_rows
            
            # Log progress every 10% of rows or every 1000 rows, whichever is less frequent
            log_interval = max(1, min(rows_limit // 10, 1000))

            for r in range(rows_limit):
                if r % log_interval == 0:
                    _dumper_log_write(f"    ...processing row {r} of {rows_limit}...")

                row_data = []
                cols_limit = min(num_cols, MAX_ARRAY_ELEMENTS) if MAX_ARRAY_ELEMENTS > 0 else num_cols
                for c in range(cols_limit):
                    offset = (r * num_cols + c) * sizeof_element
                    element_bytes = memory_block[offset : offset + sizeof_element]
                    unpacked_values = struct.unpack(unpack_format, element_bytes)
                    cell_dict = dict(zip(field_names, unpacked_values))
                    row_data.append(cell_dict)

                if MAX_ARRAY_ELEMENTS > 0 and num_cols > cols_limit:
                    row_data.append(f"<row_trunc_to_{cols_limit}>")
                matrix_data.append(row_data)

            if MAX_ARRAY_ELEMENTS > 0 and num_rows > rows_limit:
                matrix_data.append([f"<matrix_rows_trunc_to_{rows_limit}>"])
            
            _dumper_log_write("  Optimized path finished successfully.")
            metadata = {
                "is_matrix": True,
                "original_rows": num_rows,
                "original_cols": num_cols
            }
            return matrix_data, metadata

        except Exception as e_opt:
            # --- Fallback Path ---
            _dumper_log_write(f"  [!] Optimization failed: {e_opt}. Falling back to SLOW, element-by-element parsing.")
            
            matrix_data = []
            rows_limit = min(num_rows, MAX_ARRAY_ELEMENTS) if MAX_ARRAY_ELEMENTS > 0 else num_rows
            is_pointer_to_pointer = base_ptr_val.type.strip_typedefs().code == gdb.TYPE_CODE_PTR and base_ptr_val.type.target().strip_typedefs().code == gdb.TYPE_CODE_PTR

            log_interval = max(1, min(rows_limit // 10, 1000))

            for r in range(rows_limit):
                if r % log_interval == 0:
                     _dumper_log_write(f"    [Fallback] ...processing row {r} of {rows_limit}...")

                row_data = []
                cols_limit = min(num_cols, MAX_ARRAY_ELEMENTS) if MAX_ARRAY_ELEMENTS > 0 else num_cols
                for c in range(cols_limit):
                    try:
                        if is_pointer_to_pointer:
                            expression_to_eval = f"({base_expr})[{r}][{c}]"
                        else:
                            offset = r * num_cols + c
                            expression_to_eval = f"*({base_expr} + {offset})"
                        row_data.append(gdb.parse_and_eval(expression_to_eval))
                    except Exception as e:
                        row_data.append(f"<err_({r},{c}): {e}>")
                        break
                if MAX_ARRAY_ELEMENTS > 0 and num_cols > cols_limit:
                    row_data.append(f"<row_trunc_to_{cols_limit}>")
                matrix_data.append(row_data)

            if MAX_ARRAY_ELEMENTS > 0 and num_rows > rows_limit:
                matrix_data.append([f"<matrix_rows_trunc_to_{rows_limit}>"])
            
            _dumper_log_write("  Fallback path finished.")
            return matrix_data

def _try_write_error_to_gdb(status_payload: Dict[str, Any], start_delim: str, end_delim: str):
    # (codice invariato)
    try: gdb.write(f"\n{start_delim}\n{json.dumps(status_payload)}\n{end_delim}\n"); gdb.flush()
    except Exception as e: _dumper_log_write(f"CRITICAL: Could not write final error status to GDB: {e}")

try: GDBDumpJsonCommand()
except Exception as e: _dumper_log_write(f"CRITICAL: Failed to register GDBDumpJsonCommand: {e}")

_dumper_log_write("--- GDB Dumper Script Fully Parsed ---")
gdb.write("GDB_DUMPER_SCRIPT: End of script reached and processed by GDB.\n")
gdb.flush()