diff --git a/.gitignore b/.gitignore index 8bf8914..9e3d98b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ /DebugVarEdit_GUI.dist /DebugVarEdit_GUI.onefile-build /parse_xml/build/ +/parse_xml/Src/__pycache__/ diff --git a/DebugVarEdit.exe b/DebugVarEdit.exe index 6f5265e..c9f99ee 100644 Binary files a/DebugVarEdit.exe and b/DebugVarEdit.exe differ diff --git a/DebugTools.rar b/DebugVarTerminal.exe similarity index 78% rename from DebugTools.rar rename to DebugVarTerminal.exe index 38ae9db..33d3c4d 100644 Binary files a/DebugTools.rar and b/DebugVarTerminal.exe differ diff --git a/Src/DebugVarEdit_GUI.py b/Src/DebugVarEdit_GUI.py index 24eb5ef..4629912 100644 --- a/Src/DebugVarEdit_GUI.py +++ b/Src/DebugVarEdit_GUI.py @@ -191,14 +191,39 @@ class VarEditor(QWidget): self.setLayout(layout) + def open_terminal(self, target): target = target.lower() if target == "tms": - self.terminal_widget = _DemoWindow() # _DemoWindow наследует QWidget - self.terminal_widget.show() + exe_name = "DebugVarTerminal.exe" + # Путь к exe в текущей директории запуска программы + exe_path = os.path.join(os.getcwd(), exe_name) + + if not os.path.isfile(exe_path): + # Файл не найден — попросим пользователя выбрать путь к exe + msg = QMessageBox() + msg.setIcon(QMessageBox.Warning) + msg.setWindowTitle("Файл не найден") + msg.setText(f"Файл {exe_name} не найден в текущей папке.\nВыберите путь к {exe_name}.") + msg.exec_() + + # Открываем диалог выбора файла + selected_path, _ = QFileDialog.getOpenFileName( + None, "Выберите файл " + exe_name, os.getcwd(), "Executable Files (*.exe)" + ) + + if not selected_path: + # Пользователь отменил выбор — ничего не делаем + return + + exe_path = selected_path + + # Запускаем exe (отдельное окно терминала) + subprocess.Popen([exe_path], creationflags=subprocess.CREATE_NEW_CONSOLE) + elif target == "modbus": - a=1 + a = 1 def on_target_selected(self, target): diff --git a/Src/allvars_xml_parser.py b/Src/allvars_xml_parser.py index 5a73000..e9081ec 100644 --- a/Src/allvars_xml_parser.py +++ b/Src/allvars_xml_parser.py @@ -1,34 +1,57 @@ +""" +VariablesXML + get_all_vars_data +--------------------------------- +Поддержка вложенных структур, указателей на структуры ("->"), +и многомерных массивов (индексация [i][j]...). + +Требования пользователя: +- size (без индекса) = общий размер массива в байтах (НЕ измерение!). +- size1..sizeN = размеры измерений массива. +- В результирующем плоском списке (flattened) должны присутствовать ВСЕ промежуточные + пути: var, var[0], var[0][0], var[0][0].field, var[0][0].field->subfield, ... +- Аналогично для членов структур. + +Пример желаемого формата: + project + project.adc + project.adc[0] + project.adc[0][0] + project.adc[0][0].bus + project.adc[0][0].bus->status + +Данный модуль реализует: +- Разбор XML (parse) с извлечением размеров размерностей в поле `dims`. +- Генерацию плоского списка словарей `flattened()`. +- Построение иерархии словарей `get_all_vars_data()`. + +""" from __future__ import annotations -import sys + import re import xml.etree.ElementTree as ET -import var_setup from dataclasses import dataclass, field -from typing import List, Dict, Optional, Tuple -from PySide2.QtWidgets import ( - QDialog, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QPushButton, - QLineEdit, QLabel, QHeaderView, QCompleter, QCheckBox, QHBoxLayout, QSizePolicy, - QTableWidget, QTableWidgetItem, QFileDialog, QWidget, QMessageBox, QApplication, QMainWindow -) -from PySide2 import QtCore, QtGui -from path_hints import PathHints -from generate_debug_vars import choose_type_map, type_map -from var_selector_window import VariableSelectorDialog -from typing import List, Tuple, Optional, Dict, Any, Set +from typing import List, Dict, Optional, Tuple, Any -DATE_FIELD_SET = {'year','month','day','hour','minute'} +import var_setup # ожидается split_path(...) +from generate_debug_vars import choose_type_map, type_map # используется для выбора карт типов + +# --------------------------- константы ---------------------------- + +DATE_FIELD_SET = {"year", "month", "day", "hour", "minute"} + +# --------------------------- dataclasses -------------------------- @dataclass class MemberNode: name: str offset: int = 0 - type_str: str = '' - size: Optional[int] = None - children: List['MemberNode'] = field(default_factory=list) - # --- новые, но необязательные (совместимость) --- - kind: Optional[str] = None # 'array', 'union', ... - count: Optional[int] = None # size1 (число элементов в массиве) + type_str: str = "" + size: Optional[int] = None # общий размер (байты), если известен + children: List["MemberNode"] = field(default_factory=list) + # --- доп.поля --- + kind: Optional[str] = None # 'array', 'union', ... + dims: Optional[List[int]] = None def is_date_struct(self) -> bool: if not self.children: @@ -44,51 +67,57 @@ class VariableNode: type_str: str size: Optional[int] members: List[MemberNode] = field(default_factory=list) - # --- новые, но необязательные --- - kind: Optional[str] = None # 'array' - count: Optional[int] = None # size1 + # --- доп.поля --- + kind: Optional[str] = None # 'array' + dims: Optional[List[int]] = None # полный список размеров [size1, size2, ...] def base_address_hex(self) -> str: return f"0x{self.address:06X}" -# --------------------------- XML Parser ---------------------------- +# --------------------------- класс парсера ----------------------- class VariablesXML: """ - Reads your XML and outputs a flat list of paths: - - Arrays -> name[i], multilevel -> name[i][j] - - Pointer to struct -> children via '->' - - Regular struct -> children via '.' + Читает XML и предоставляет методы: + - flattened(): плоский список всех путей. + - date_struct_candidates(): как раньше. + + Правила формирования путей: + * Структурные поля: '.' + * Поля через указатель на структуру: '->' + * Массивы: [index] (каждое измерение). """ - # assumed primitive sizes (for STM/MCU: int=2) + + # предполагаемые размеры примитивов (MCU: int=2) _PRIM_SIZE = { - 'char':1, 'signed char':1, 'unsigned char':1, 'uint8_t':1, 'int8_t':1, - 'short':2, 'short int':2, 'signed short':2, 'unsigned short':2, - 'uint16_t':2, 'int16_t':2, - 'int':2, 'signed int':2, 'unsigned int':2, - 'long':4, 'unsigned long':4, 'int32_t':4, 'uint32_t':4, - 'float':4, - 'long long':8, 'unsigned long long':8, 'int64_t':8, 'uint64_t':8, 'double':8, + 'char': 1, 'signed char': 1, 'unsigned char': 1, + 'uint8_t': 1, 'int8_t': 1, + 'short': 2, 'short int': 2, 'signed short': 2, 'unsigned short': 2, + 'uint16_t': 2, 'int16_t': 2, + 'int': 2, 'signed int': 2, 'unsigned int': 2, + 'long': 4, 'unsigned long': 4, 'int32_t': 4, 'uint32_t': 4, + 'float': 4, + 'long long': 8, 'unsigned long long': 8, 'int64_t': 8, 'uint64_t': 8, 'double': 8, } def __init__(self, path: str): self.path = path - self.timestamp: str = '' + self.timestamp: str = "" self.variables: List[VariableNode] = [] - choose_type_map(0) + choose_type_map(0) # инициализация карт типов (если требуется) self._parse() - # ------------------ low helpers ------------------ + # ------------------ утилиты ------------------ @staticmethod def _parse_int_guess(txt: Optional[str]) -> Optional[int]: if not txt: return None txt = txt.strip() - if txt.startswith(('0x','0X')): + if txt.startswith(('0x', '0X')): return int(txt, 16) - # если в строке есть буквы A-F → возможно hex + # если в строке есть A-F, попробуем hex if any(c in 'abcdefABCDEF' for c in txt): try: return int(txt, 16) @@ -103,7 +132,7 @@ class VariablesXML: def _is_pointer_to_struct(t: str) -> bool: if not t: return False - low = t.replace('\t',' ').replace('\n',' ') + low = t.replace('\t', ' ').replace('\n', ' ') return 'struct ' in low and '*' in low @staticmethod @@ -113,24 +142,34 @@ class VariablesXML: low = t.strip() return low.startswith('struct ') or low.startswith('union ') + @staticmethod + def _is_union(t: str) -> bool: + if not t: + return False + low = t.strip() + return low.startswith('union ') + @staticmethod def _strip_array_suffix(t: str) -> str: - return t[:-2].strip() if t.endswith('[]') else t + t = t.strip() + while t.endswith('[]'): + t = t[:-2].strip() + return t def _guess_primitive_size(self, type_str: str) -> Optional[int]: if not type_str: return None base = type_str - for tok in ('volatile','const'): + for tok in ('volatile', 'const'): base = base.replace(tok, '') - base = base.replace('*',' ') - base = base.replace('[',' ').replace(']',' ') + base = base.replace('*', ' ') + base = base.replace('[', ' ').replace(']', ' ') base = ' '.join(base.split()).strip() return self._PRIM_SIZE.get(base) # ------------------ XML read ------------------ - def _parse(self): + def _parse(self) -> None: try: tree = ET.parse(self.path) root = tree.getroot() @@ -138,204 +177,235 @@ class VariablesXML: ts = root.find('timestamp') self.timestamp = ts.text.strip() if ts is not None and ts.text else '' - def parse_member(elem) -> MemberNode: - name = elem.get('name','') - offset = int(elem.get('offset','0'),16) if elem.get('offset') else 0 - t = elem.get('type','') or '' + def parse_member(elem: ET.Element, base_offset=0) -> MemberNode: + name = elem.get('name', '') + offset = int(elem.get('offset', '0'), 16) if elem.get('offset') else 0 + t = elem.get('type', '') or '' size_attr = elem.get('size') - size = int(size_attr,16) if size_attr else None + size = int(size_attr, 16) if size_attr else None kind = elem.get('kind') - size1_attr = elem.get('size1') - count = None - if size1_attr: - count = self._parse_int_guess(size1_attr) - node = MemberNode(name=name, offset=offset, type_str=t, size=size, - kind=kind, count=count) + + abs_offset = base_offset + offset + + + # Собираем размеры, если есть + dims: List[int] = [] + i = 1 + while True: + size_key = f"size{i}" + size_val = elem.get(size_key) + if size_val is None: + break + parsed = self._parse_int_guess(size_val) # предполагается твоя функция парсинга int + if parsed is not None: + dims.append(parsed) + i += 1 + + node = MemberNode( + name=name, + offset=abs_offset, + type_str=t, + size=size, + kind=kind, + dims=dims if dims else None, + ) + + # Для детей for ch in elem.findall('member'): - node.children.append(parse_member(ch)) + if kind == 'union': + # Для union детей НЕ добавляем их offset, просто передаём abs_offset + child = parse_member(ch, base_offset=abs_offset) + child.offset = abs_offset # выравниваем offset, игнорируем offset детей + else: + # Для struct/array суммируем offset нормально + child = parse_member(ch, base_offset=abs_offset) + node.children.append(child) + + # Аналогично для pointee + pointee_elem = elem.find('pointee') + if pointee_elem is not None: + for ch in pointee_elem.findall('member'): + if kind == 'union': + child = parse_member(ch, base_offset=abs_offset) + child.offset = abs_offset + else: + child = parse_member(ch, base_offset=abs_offset) + node.children.append(child) + size_p = pointee_elem.get('size') + if size_p: + node.size = int(size_p, 16) + return node + + for var in root.findall('variable'): - addr = int(var.get('address','0'),16) - name = var.get('name','') - t = var.get('type','') or '' - size_attr = var.get('size') - size = int(size_attr,16) if size_attr else None + addr = int(var.get('address', '0'), 16) + name = var.get('name', '') + t = var.get('type', '') or '' + size_attr = var.get('size') # общий размер байт + size = int(size_attr, 16) if size_attr else None kind = var.get('kind') - size1_attr = var.get('size1') - count = None - if size1_attr: - count = self._parse_int_guess(size1_attr) + + dims: List[int] = [] + i = 1 + while True: + key = f'size{i}' + val = var.get(key) + if val is None: + break + parsed = self._parse_int_guess(val) + if parsed is not None: + dims.append(parsed) + i += 1 + members = [parse_member(m) for m in var.findall('member')] - self.variables.append( - VariableNode(name=name, address=addr, type_str=t, size=size, - members=members, kind=kind, count=count) + + v = VariableNode( + name=name, + address=addr, + type_str=t, + size=size, + members=members, + kind=kind, + dims=dims if dims else None, ) + self.variables.append(v) except FileNotFoundError: self.variables = [] except ET.ParseError: self.variables = [] - # ------------------ flatten (expanded) ------------------ + # ------------------ helpers для flattened --------------------- - def flattened(self, - max_array_elems: Optional[int] = None - ) -> List[Dict[str, Any]]: + def _elem_size_bytes(self, total_size: Optional[int], dims: List[int], base_type: str, members: List[MemberNode]) -> int: + """Оценка размера одного *листового* элемента (последнего измерения). + Если total_size и dims все известны — берём size / prod(dims). + Иначе — пробуем примитивный размер; иначе 1. + (Не учитываем выравнивание структур; при необходимости можно расширить.) """ - Returns a list of dictionaries with full data for variables and their expanded members. - Each dictionary contains: 'name', 'address', 'type', 'size', 'kind', 'count'. - max_array_elems: limit unfolding of large arrays (None = all). + if total_size is not None and dims: + prod = 1 + for d in dims: + if d is None or d == 0: + prod = None + break + prod *= d + if prod and prod > 0: + return max(1, total_size // prod) + prim = self._guess_primitive_size(base_type) + if prim: + return prim + # Если структура и у неё есть size по детям? Пока fallback=1. + return 1 + + # ------------------ flattened ------------------ + + def flattened(self, max_array_elems: Optional[int] = None) -> List[Dict[str, Any]]: + """Возвращает плоский список всех путей (каждый путь = dict). + Включает промежуточные узлы массивов (var[0], var[0][0], ...). """ out: List[Dict[str, Any]] = [] - def get_dict(name: str, address: int, type_str: str, size: Optional[int], kind: Optional[str], count: Optional[int]) -> Dict[str, Any]: - """Helper to create the output dictionary format.""" - return { + def mk(name: str, addr: Optional[int], type_str: str, size: Optional[int], kind: Optional[str], dims_for_node: Optional[List[int]]): + if 'Bender' in name: + a=1 + out.append({ 'name': name, - 'address': address, + 'address': addr, 'type': type_str, 'size': size, 'kind': kind, - 'count': count - } - - def compute_stride(size_bytes: Optional[int], - count: Optional[int], - base_type: Optional[str], - node_children: Optional[List[MemberNode]]) -> int: - """Calculates the stride (size of one element) for arrays.""" - # 1) size_bytes/count - if size_bytes and count and count > 0: - if size_bytes % count == 0: - stride = size_bytes // count - if stride <= 0: - stride = 1 - return stride - else: - # size not divisible by count → most likely size = size of one element - return max(size_bytes, 1) - - # 2) attempt by type (primitive) - if base_type: - gs = self._guess_primitive_size(base_type) - if gs: - return gs - - # 3) attempt by children (structure) - if node_children: - if not node_children: - return 1 - - min_off = min(ch.offset for ch in node_children) - max_end = min_off - for ch in node_children: - sz = ch.size - if not sz: - sz = self._guess_primitive_size(ch.type_str) or 1 - end = ch.offset + sz - if end > max_end: - max_end = end - stride = max_end - min_off - if stride > 0: - return stride - - return 1 - - - def expand_members(prefix_name: str, - base_addr: int, - members: List[MemberNode], - parent_is_ptr_struct: bool): - """ - Recursively expands members of structs/unions or pointed-to structs. - parent_is_ptr_struct: if True, connection is '->' otherwise '.' - """ + 'dims': dims_for_node[:] if dims_for_node else None, + }) + + def expand_members(prefix: str, base_addr: int, members: List[MemberNode], parent_is_ptr_struct: bool, parent_is_union: bool) -> None: + # Выбираем разделитель пути: '.' если обычный член, '->' если указатель на структуру join = '->' if parent_is_ptr_struct else '.' + for m in members: - path_m = f"{prefix_name}{join}{m.name}" if prefix_name else m.name - addr_m = base_addr + m.offset - out.append(get_dict(path_m, addr_m, m.type_str, m.size, m.kind, m.count)) + path_m = f"{prefix}{join}{m.name}" if prefix else m.name + is_union = m.kind == 'union' or parent_is_union + if is_union: + # Все поля union начинаются с одного адреса + addr_m = base_addr + else: + addr_m = base_addr + m.offset - # array? - if (m.kind == 'array') or m.type_str.endswith('[]'): - count = m.count - if count is None: - count = 0 - if count <= 0: - continue + dims = m.dims or [] + + mk(path_m, addr_m, m.type_str, m.size, m.kind, dims) + + if m.kind == 'array' and dims: base_t = self._strip_array_suffix(m.type_str) - stride = compute_stride(m.size, count, base_t, m.children if m.children else None) - limit = count if max_array_elems is None else min(count, max_array_elems) - for i in range(limit): - path_i = f"{path_m}[{i}]" - addr_i = addr_m + i*stride - # Determine kind for array element based on its base type - elem_kind = None - if self._is_struct_or_union(base_t): - elem_kind = 'struct' # or 'union' depending on `base_t` prefix - elif self._guess_primitive_size(base_t): - elem_kind = 'primitive' + elem_sz = m.size - # For array elements, 'size' is the stride (size of one element), 'count' is None. - out.append(get_dict(path_i, addr_i, base_t, stride, elem_kind, None)) + # Для массива внутри структуры: первый уровень — '.' для доступа, + # внутри массива раскрываем по обычной логике с parent_is_ptr_struct=False + expand_dims(path_m, addr_m, dims, base_t, m.children, elem_sz, parent_is_ptr_struct=False) + else: + if m.children: + # Проверяем, является ли поле указателем на структуру + is_ptr = self._is_pointer_to_struct(m.type_str) + # Рекурсивно раскрываем дочерние поля, выбирая правильный разделитель + expand_members(path_m, addr_m, m.children, is_ptr, is_union) - # array element: if structure / union → unfold fields - if m.children and self._is_struct_or_union(base_t): - expand_members(path_i, addr_i, m.children, parent_is_ptr_struct=False) - # array element: if pointer to structure - elif self._is_pointer_to_struct(base_t): - # usually no children in XML for these, but if present — use them - expand_members(path_i, addr_i, m.children, parent_is_ptr_struct=True) - continue - # not an array, but has children (e.g., struct/union) - if m.children: - is_ptr_struct = self._is_pointer_to_struct(m.type_str) - expand_members(path_m, addr_m, m.children, parent_is_ptr_struct=is_ptr_struct) + def expand_dims(name: str, base_addr: int, dims: List[int], base_type: str, children: List[MemberNode], elem_size: int, parent_is_ptr_struct: bool) -> None: + prods: List[int] = [] + acc = 1 + for d in reversed(dims[1:]): + acc *= (d if d else 1) + prods.append(acc) + prods.reverse() - # --- top-level variables --- + def rec(k: int, cur_name: str, cur_addr: int) -> None: + if k == len(dims): + # Листовой элемент массива + mk(cur_name, cur_addr, base_type, elem_size, None, None) + # Если элемент — структура или указатель на структуру, раскрываем вложения + if children and self._is_struct_or_union(base_type): + expand_members(cur_name, cur_addr, children, parent_is_ptr_struct=False, parent_is_union=self._is_union(base_type)) + elif self._is_pointer_to_struct(base_type): + expand_members(cur_name, cur_addr, children, parent_is_ptr_struct=True, parent_is_union=self._is_union(base_type)) + return + + dim_sz = dims[k] or 0 + if max_array_elems is not None: + dim_sz = min(dim_sz, max_array_elems) + + stride = elem_size * prods[k] if k < len(prods) else elem_size + if len(dims) > 2: + a=1 + for i in range(dim_sz): + child_name = f"{cur_name}[{i}]" + child_addr = (cur_addr + i * stride) if cur_addr is not None else None + remaining = dims[k+1:] + mk(child_name, child_addr, base_type + '[]' * len(remaining), stride if remaining else elem_size, 'array' if remaining else None, remaining) + rec(k + 1, child_name, child_addr) + + rec(0, name, base_addr) + + + # --- цикл по топ‑левел переменным --- for v in self.variables: - out.append(get_dict(v.name, v.address, v.type_str, v.size, v.kind, v.count)) - - # top-level array? - if (v.kind == 'array') or v.type_str.endswith('[]'): - count = v.count - if count is None: - count = 0 - if count > 0: - base_t = self._strip_array_suffix(v.type_str) - stride = compute_stride(v.size, count, base_t, v.members if v.members else None) - limit = count if max_array_elems is None else min(count, max_array_elems) - for i in range(limit): - p = f"{v.name}[{i}]" - a = v.address + i*stride - # Determine kind for array element - elem_kind = None - if self._is_struct_or_union(base_t): - elem_kind = 'struct' # or 'union' - elif self._guess_primitive_size(base_t): - elem_kind = 'primitive' - - out.append(get_dict(p, a, base_t, stride, elem_kind, None)) - - # array of structs? - if v.members and self._is_struct_or_union(base_t): - expand_members(p, a, v.members, parent_is_ptr_struct=False) - # array of pointers to structs? - elif self._is_pointer_to_struct(base_t): - expand_members(p, a, v.members, parent_is_ptr_struct=True) - continue - - # top-level not an array, but has members - if v.members: - is_ptr_struct = self._is_pointer_to_struct(v.type_str) - expand_members(v.name, v.address, v.members, parent_is_ptr_struct=is_ptr_struct) + dims = v.dims or [] + mk(v.name, v.address, v.type_str, v.size, v.kind, dims) + if (v.kind == 'array' or v.type_str.endswith('[]')) and dims: + base_t = self._strip_array_suffix(v.type_str) + elem_sz = v.size + expand_dims(v.name, v.address, dims, base_t, v.members, elem_sz, parent_is_ptr_struct=False) + else: + if v.members: + is_ptr = self._is_pointer_to_struct(v.type_str) + is_union = self._is_union(v.type_str) + expand_members(v.name, v.address, v.members, is_ptr, is_union) return out - # -------------------- date candidates (as it was) -------------------- + # -------------------- date candidates (как раньше) ------------- - def date_struct_candidates(self) -> List[Tuple[str,int]]: + def date_struct_candidates(self) -> List[Tuple[str, int]]: cands = [] for v in self.variables: # top level (if all date fields are present) @@ -349,90 +419,82 @@ class VariablesXML: return cands + # ------------------------------------------------------------------ + # Построение иерархического дерева из flattened() + # ------------------------------------------------------------------ def get_all_vars_data(self) -> List[Dict[str, Any]]: """ - Возвращает вложенную структуру словарей с полными данными для всех переменных и их развернутых членов. - Каждый словарь представляет узел в иерархии и содержит: - 'name' (полный путь), 'address', 'size', 'type', 'kind', 'count', и 'children' (если есть). - Логика определения родительского пути теперь использует `split_path` для анализа структуры пути. + Строит иерархию словарей из плоского списка переменных. + + Каждый узел = { + 'name': <полный путь>, + 'address': <адрес или None>, + 'type': <тип>, + 'size': <байты>, + 'kind': <'array' | ...>, + 'dims': [size1, size2, ...] или None, + 'children': [...список дочерних узлов] + } + + Возвращает список корневых узлов (top-level переменных). """ flat_data = self.flattened(max_array_elems=None) - root_nodes: List[Dict[str, Any]] = [] - all_nodes_map: Dict[str, Dict[str, Any]] = {} - + # Быстрое отображение имя -> узел (словарь с детьми) + all_nodes: Dict[str, Dict[str, Any]] = {} for item in flat_data: - node_dict = {**item, 'children': []} - all_nodes_map[item['name']] = node_dict + node = dict(item) + node['children'] = [] + all_nodes[item['name']] = node - # Вспомогательная функция для определения полного пути родителя с использованием split_path - def get_parent_path_using_split(full_path: str) -> Optional[str]: - # 1. Используем split_path для получения компонентов пути. - components = var_setup.split_path(full_path) - - # Если нет компонентов или только один (верхний уровень, не массивный элемент) - if not components or len(components) == 1: - # Если компонент один и это не индекс массива (например, "project" или "my_var") - # тогда у него нет родителя в этой иерархии. - # Если это был бы "my_array[0]" -> components=['my_array', '[0]'], len=2 - if len(components) == 1 and not components[0].startswith('['): - return None - elif len(components) == 2 and components[-1].startswith('['): # like "my_array[0]" - return components[0] # Return "my_array" as parent - else: # Edge cases or malformed, treat as root - return None - - - # 2. Определяем, как отрезать "хвост" из оригинальной строки `full_path`, чтобы получить родителя. - # Эта логика остаётся похожей на предыдущую, так как `split_path` не включает разделители - # и мы должны получить точную строку родительского пути. - - # Находим индекс последнего разделителя '.' или '->' - last_dot_idx = full_path.rfind('.') - last_arrow_idx = full_path.rfind('->') - - effective_last_sep_idx = -1 - if last_dot_idx > last_arrow_idx: - effective_last_sep_idx = last_dot_idx - elif last_arrow_idx != -1: - effective_last_sep_idx = last_arrow_idx - - # Находим начало последнего суффикса массива (e.g., '[0]') в оригинальной строке - array_suffix_match = re.search(r'(\[[^\]]*\])+$', full_path) - array_suffix_start_idx = -1 - if array_suffix_match: - array_suffix_start_idx = array_suffix_match.start() - - # Логика определения родителя: - # - Если есть суффикс массива, и он находится после последнего разделителя (или разделителей нет), - # то родитель - это часть до суффикса массива. (e.g., 'project.adc[0]' -> 'project.adc') - # - Иначе, если есть разделитель, родитель - это часть до последнего разделителя. (e.g., 'project.adc.bus' -> 'project.adc') - # - Иначе (ни разделителей, ни суффиксов), это корневой элемент. - if array_suffix_start_idx != -1 and (array_suffix_start_idx > effective_last_sep_idx): - return full_path[:array_suffix_start_idx] - elif effective_last_sep_idx != -1: - return full_path[:effective_last_sep_idx] + def _parent_struct_split(path: str) -> Optional[str]: + # Ищем последний '.' или '->' для определения родителя + dot_idx = path.rfind('.') + arrow_idx = path.rfind('->') + cut_idx = max(dot_idx, arrow_idx) + if cut_idx == -1: + return None + # '->' занимает 2 символа, нужно взять срез до начала '->' + if arrow_idx > dot_idx: + return path[:arrow_idx] else: - return None # Корневой элемент без явного родителя + return path[:dot_idx] - # Основная логика get_all_vars_data - - # Заполнение связей "родитель-потомок" - for item_name, node_dict in all_nodes_map.items(): - parent_name = get_parent_path_using_split(item_name) # Используем новую вспомогательную функцию - if parent_name and parent_name in all_nodes_map: - all_nodes_map[parent_name]['children'].append(node_dict) + def find_parent(path: str) -> Optional[str]: + """ + Возвращает полный путь родителя, учитывая '.', '->' и индексы [] в конце. + + Если путь заканчивается индексом [k], удаляет последний индекс и проверяет наличие родителя. + Иначе пытается найти последний сепаратор '.' или '->'. + """ + # Если есть trailing индекс в конце, убираем его + m = re.search(r'\[[0-9]+\]$', path) + if m: + base = path[:m.start()] # убираем последний [k] + # Если базовый путь есть в узлах, считаем его родителем + if base in all_nodes: + return base + # Иначе пытаемся найти родителя от базового пути + return _parent_struct_split(base) else: - root_nodes.append(node_dict) - - # Сортируем корневые узлы и их детей рекурсивно по имени - def sort_nodes(nodes_list: List[Dict[str, Any]]): - nodes_list.sort(key=lambda x: x['name']) - for node in nodes_list: - if node['children']: - sort_nodes(node['children']) - - sort_nodes(root_nodes) + # Если нет индекса, просто ищем последний разделитель + return _parent_struct_split(path) - return root_nodes + # Строим иерархию: parent -> children + roots: List[Dict[str, Any]] = [] + for full_name, node in all_nodes.items(): + parent_name = find_parent(full_name) + if parent_name and parent_name in all_nodes: + all_nodes[parent_name]['children'].append(node) + else: + roots.append(node) + # Рекурсивно сортируем детей по имени для порядка + def sort_nodes(nodes: List[Dict[str, Any]]): + nodes.sort(key=lambda n: n['name']) + for n in nodes: + if n['children']: + sort_nodes(n['children']) + + sort_nodes(roots) + return roots diff --git a/Src/build/build_and_clean.py b/Src/build/build_and_clean.py index c3ea202..5fd72ab 100644 --- a/Src/build/build_and_clean.py +++ b/Src/build/build_and_clean.py @@ -12,10 +12,12 @@ from PyInstaller.utils.hooks import collect_data_files # === Конфигурация === USE_NUITKA = True # True — сборка через Nuitka, False — через PyInstaller +MAIN_SCRIPT_NAME = "tms_debugvar_term" +OUTPUT_NAME = "DebugVarTerminal" + SRC_PATH = Path("./Src/") -SCRIPT_PATH = SRC_PATH / "DebugVarEdit_GUI.py" -OUTPUT_NAME = "DebugVarEdit" +SCRIPT_PATH = SRC_PATH / (MAIN_SCRIPT_NAME + ".py") DIST_PATH = Path("./").resolve() WORK_PATH = Path("./build_temp").resolve() @@ -26,9 +28,9 @@ ICON_ICO_PATH = SRC_PATH / "icon.ico" TEMP_FOLDERS = [ "build_temp", "__pycache__", - "DebugVarEdit_GUI.build", - "DebugVarEdit_GUI.onefile-build", - "DebugVarEdit_GUI.dist" + MAIN_SCRIPT_NAME + ".build", + MAIN_SCRIPT_NAME + ".onefile-build", + MAIN_SCRIPT_NAME + ".dist" ] # === Пути к DLL и прочим зависимостям === LIBS = { diff --git a/Src/csv_logger.py b/Src/csv_logger.py index 164bbe4..5edf7c7 100644 --- a/Src/csv_logger.py +++ b/Src/csv_logger.py @@ -221,3 +221,4 @@ class CsvLogger: return (0, float(ts)) except Exception: return (1, str(ts)) + diff --git a/Src/path_hints.py b/Src/path_hints.py index 70179d1..83ccb09 100644 --- a/Src/path_hints.py +++ b/Src/path_hints.py @@ -59,6 +59,47 @@ def split_path_tokens(path: str) -> List[str]: tokens.append(token) return tokens +def split_path_tokens_with_spans(path: str) -> List[Tuple[str, int, int]]: + """ + Возвращает список кортежей (токен, start_pos, end_pos) + Токены — так же, как в split_path_tokens, но с позициями в исходной строке. + """ + tokens = [] + i = 0 + L = len(path) + while i < L: + c = path[i] + start = i + # '->' + if c == '-' and i + 1 < L and path[i:i+2] == '->': + tokens.append(('->', start, start + 2)) + i += 2 + continue + if c == '.': + tokens.append(('.', start, start + 1)) + i += 1 + continue + if c == '[': + # захватим весь индекс с ']' + j = i + while j < L and path[j] != ']': + j += 1 + if j < L and path[j] == ']': + j += 1 + tokens.append((path[i:j], i, j)) + i = j + continue + # иначе - обычное имя (до точки, стрелки или скобок) + j = i + while j < L and path[j] not in ['.', '-', '[']: + if path[j] == '-' and j + 1 < L and path[j:j+2] == '->': + break + j += 1 + tokens.append((path[i:j], i, j)) + i = j + # фильтруем из списка токены-разделители '.' и '->' чтобы оставить только логические части + filtered = [t for t in tokens if t[0] not in ['.', '->']] + return filtered def canonical_key(path: str) -> str: """ @@ -137,28 +178,19 @@ class PathHints: self._paths.append(full_path) self._types[full_path] = type_str - toks = split_path_tokens(full_path) - if not toks: + tokens_spans = split_path_tokens_with_spans(full_path) + if not tokens_spans: return cur_dict = self._root_children cur_full = '' parent_node: Optional[PathNode] = None - for i, tok in enumerate(toks): - # Собираем ПОЛНЫЙ путь - if cur_full == '': - cur_full = tok - else: - if tok.startswith('['): - cur_full += tok - else: - cur_full += '.' + tok - - # Если узел уже есть + for i, (tok, start, end) in enumerate(tokens_spans): + cur_full = full_path[:end] # подстрока с начала до конца токена включительно + node = cur_dict.get(tok) if node is None: - # --- ВАЖНО: full_path = cur_full --- node = PathNode(name=tok, full_path=cur_full) cur_dict[tok] = node diff --git a/Src/tms_debugvar_lowlevel.py b/Src/tms_debugvar_lowlevel.py index ddb3b43..776d471 100644 --- a/Src/tms_debugvar_lowlevel.py +++ b/Src/tms_debugvar_lowlevel.py @@ -339,7 +339,7 @@ class LowLevelSelectorWidget(QWidget): name = var['ptr_type'] elif isinstance(var.get('ptr_type_name'), str): name = var['ptr_type_name'] - elif isinstance(var.get('pt_type'), str): + elif isinstance(var.get('pt_type'), str) and 'pt_' in var.get('pt_type'): name = var['pt_type'].replace('pt_','') elif isinstance(var.get('ptr_type'), int): name = PT_ENUM_NAME_FROM_VAL.get(var['ptr_type'], 'unknown') @@ -456,7 +456,7 @@ class LowLevelSelectorWidget(QWidget): return False # 2. Обновляем отображение в таблице - self.var_table.populate(self._all_available_vars, {}, self._on_var_table_changed) + #self.var_table.populate(self._all_available_vars, {}, self._on_var_table_changed) return True # --------------- Address mapping / type mapping helpers --------------- diff --git a/Src/tms_debugvar_term.py b/Src/tms_debugvar_term.py index 3b5c495..6ef6135 100644 --- a/Src/tms_debugvar_term.py +++ b/Src/tms_debugvar_term.py @@ -1,7 +1,8 @@ -from PySide2 import QtCore, QtWidgets, QtSerialPort +from PySide2 import QtCore, QtWidgets, QtSerialPort, QtGui from tms_debugvar_lowlevel import LowLevelSelectorWidget import datetime import time +import os from csv_logger import CsvLogger # ------------------------------- Константы протокола ------------------------ WATCH_SERVICE_BIT = 0x8000 @@ -44,6 +45,19 @@ def crc16_ibm(data: bytes, *, init=0xFFFF) -> int: crc >>= 1 return crc & 0xFFFF +def is_frozen(): + # Для Nuitka --onefile + return getattr(sys, 'frozen', False) + + +def get_base_path(): + if is_frozen(): + # В Nuitka onefile распаковывается в папку с самим exe во временной директории + return os.path.dirname(sys.executable) + else: + # Режим разработки + return os.path.dirname(os.path.abspath(__file__)) + def _decode_debug_status(status: int) -> str: """Преобразует код статуса прошивки в строку. Возвращает 'OK' или перечисление битов через '|'. @@ -97,9 +111,6 @@ class Spoiler(QtWidgets.QWidget): self._ani_content.setDuration(animationDuration) self._ani_content.setEasingCurve(QtCore.QEasingCurve.InOutCubic) - # Следим за шагами анимации → обновляем родителя - self._ani_content.valueChanged.connect(self._adjust_parent_size) - # --- Layout --- self.mainLayout = QtWidgets.QGridLayout(self) self.mainLayout.setVerticalSpacing(0) @@ -120,13 +131,6 @@ class Spoiler(QtWidgets.QWidget): def getState(self): return self.state - def _adjust_parent_size(self, *_): - top = self.window() - if top: - size = top.size() - size.setHeight(top.sizeHint().height()) # берём новую высоту - top.resize(size) # ширина остаётся прежней - def _on_toggled(self, checked: bool): self.state = checked self.toggleButton.setArrowType(QtCore.Qt.DownArrow if checked else QtCore.Qt.RightArrow) @@ -135,12 +139,6 @@ class Spoiler(QtWidgets.QWidget): self._ani_content.stop() self._ani_content.setStartValue(self.contentArea.maximumHeight()) self._ani_content.setEndValue(contentHeight if checked else 0) - - # --- Фиксируем ширину на время анимации --- - w = self.width() - self.setFixedWidth(w) - self._ani_content.finished.connect(lambda: self.setMaximumWidth(16777215)) # сброс фикса - self._ani_content.start() @@ -420,9 +418,8 @@ class DebugTerminalWidget(QtWidgets.QWidget): self.chk_hex_index.stateChanged.connect(self._toggle_index_base) # LowLevel (новые и переделанные) - self.ll_selector.variablePrepared.connect(self._on_ll_variable_prepared) self.ll_selector.xmlLoaded.connect(lambda p: self._log(f"[LL] XML loaded: {p}")) - self.ll_selector.btn_read_once.clicked.connect(self.request_lowlevel_once) + self.ll_selector.btn_read_once.clicked.connect(self._start_ll_cycle) self.ll_selector.btn_start_polling.clicked.connect(self._toggle_ll_polling) # --- CSV Logging --- @@ -681,7 +678,7 @@ class DebugTerminalWidget(QtWidgets.QWidget): # !!! Раньше тут было `return`, его убираем # Если идёт LL polling — переходим сразу к следующей переменной - if self._ll_polling and (self._ll_poll_index < len(self._ll_polling_variables)): + if self._ll_polling: self._process_next_ll_variable_in_cycle() return @@ -897,13 +894,20 @@ class DebugTerminalWidget(QtWidgets.QWidget): crc_lo, crc_hi = frame[crc_pos], frame[crc_pos+1] self._check_crc(payload, crc_lo, crc_hi) - + status = payload[2] addr2, addr1, addr0 = payload[3], payload[4], payload[5] addr24 = (addr2 << 16) | (addr1 << 8) | addr0 status_desc = _decode_debug_status(status) + if not success: + # Ошибка — в ответе нет ReturnType и данных, только статус + self._log(f"[LL] ERROR addr=0x{addr24:06X} status=0x{status:02X} ({status_desc})") + self.llValueRead.emit(addr24, status, None, None, None) + return None + + # Если success == True, продолжаем парсить ReturnType и данные return_type = payload[6] data_hi, data_lo = payload[7], payload[8] raw16 = (data_hi << 8) | data_lo @@ -922,8 +926,7 @@ class DebugTerminalWidget(QtWidgets.QWidget): scale = self.iq_scaling.get(frac_bits, 1.0 / (1 << frac_bits)) # 1 / 2^N scaled = float(value_int) / scale - - + self.llValueRead.emit(addr24, status, return_type, value_int, scaled) var_name = None @@ -935,7 +938,7 @@ class DebugTerminalWidget(QtWidgets.QWidget): self._log(f"[LL] OK addr=0x{addr24:06X} type=0x{return_type:02X} raw={value_int} scaled={scaled:.6g}") - current_time = time.time() # Получаем текущее время + current_time = time.time() self.csv_logger.set_value(current_time, var_name, display_val) @@ -1030,6 +1033,7 @@ class DebugTerminalWidget(QtWidgets.QWidget): self.btn_poll.setText("Stop Polling") self.set_status("Idle", "idle") self._log(f"[POLL] Started interval={interval}ms") + self._set_ui_busy(False) # Обновить доступность кнопок def _on_poll_timeout(self): @@ -1063,6 +1067,7 @@ class DebugTerminalWidget(QtWidgets.QWidget): # Immediately kick off the first variable read of the first cycle self._start_ll_cycle() + self._set_ui_busy(False) # Обновить доступность кнопок def _on_ll_poll_timeout(self): @@ -1096,14 +1101,19 @@ class DebugTerminalWidget(QtWidgets.QWidget): self._ll_poll_index += 1 frame = self._build_lowlevel_request(var_info) # --- НОВОЕ: Передаем var_info в метаданные транзакции для LL polling --- - meta = {'lowlevel': True, 'll_polling': True, 'll_var_info': var_info} - self.set_status(f"Polling LL: {var_info.get('name')}", "values") + meta = {'lowlevel': True, 'll_polling': True, 'll_var_info': var_info} + # Получаем адрес переменной, предполагаем что ключ называется 'addr' или 'address' + addr = var_info.get('addr') or var_info.get('address') + if addr is not None: + addr_str = f"0x{addr:06X}" + else: + addr_str = "addr unknown" + + self.set_status(f"Polling LL: {addr_str} {var_info.get('name')}", "values") self._enqueue_raw(frame, meta) else: # Цикл завершен, перезапускаем таймер для следующего полного цикла - self._ll_poll_index = 0 - self._ll_poll_timer.start(self.ll_selector.spin_interval.value()) - self.set_status("LL polling cycle done, waiting...", "idle") + self.ll_selector._populate_var_table() # ------------------------------ HELPERS -------------------------------- def _toggle_index_base(self, st): # ... (код без изменений) @@ -1180,7 +1190,7 @@ class DebugTerminalWidget(QtWidgets.QWidget): self._csv_logging_active = True self.btn_start_csv_logging.setEnabled(False) self.btn_stop_csv_logging.setEnabled(True) - self.set_status("CSV Logging ACTIVE", "values") + self.set_status("CSV Logging ACTIVATED", "service") self._log("[CSV] Запись данных в CSV началась.") @@ -1189,7 +1199,7 @@ class DebugTerminalWidget(QtWidgets.QWidget): self._csv_logging_active = False self.btn_start_csv_logging.setEnabled(True) self.btn_stop_csv_logging.setEnabled(False) - self.set_status("CSV Logging STOPPED", "idle") + self.set_status("CSV Logging STOPPED", "service") self._log("[CSV] Запись данных в CSV остановлена.") def _save_csv_data(self): @@ -1199,7 +1209,7 @@ class DebugTerminalWidget(QtWidgets.QWidget): self.set_status("Stop logging first", "error") return self.csv_logger.write_to_csv() - self.set_status("CSV data saved", "idle") + self.set_status("CSV data saved", "service") def _log(self, msg: str): # ... (код без изменений) @@ -1235,6 +1245,10 @@ class DebugTerminalWidget(QtWidgets.QWidget): class _DemoWindow(QtWidgets.QMainWindow): def __init__(self): super().__init__() + base_path = get_base_path() + icon_path = os.path.join(base_path, "icon.ico") + if os.path.exists(icon_path): + self.setWindowIcon(QtGui.QIcon(icon_path)) self.setWindowTitle("DebugVar Terminal") self.term = DebugTerminalWidget(self) self.setCentralWidget(self.term) diff --git a/Src/var_selector_window.py b/Src/var_selector_window.py index a4073db..f201c4d 100644 --- a/Src/var_selector_window.py +++ b/Src/var_selector_window.py @@ -206,7 +206,7 @@ class VariableSelectorDialog(QDialog): 'enable': 'true', 'shortname': name, 'pt_type': '', - 'iq_type': '', + 'iq_type': 't_iq_none', 'return_type': 't_iq_none', 'file': file_val, 'extern': str(extern_val).lower() if extern_val else 'false', diff --git a/Src/var_table.py b/Src/var_table.py index f8fb2c5..e8fa676 100644 --- a/Src/var_table.py +++ b/Src/var_table.py @@ -64,7 +64,8 @@ class SetSizeDialog(QDialog): """ Диалоговое окно для выбора числового значения (размера). """ - def __init__(self, parent=None, initial_value=10, min_value=1, max_value=50, title="Укажите размер короткого имени"): + def __init__(self, parent=None, initial_value=10, min_value=1, max_value=50, title="Укажите размер короткого имени", + label_text="Количество символов:"): super().__init__(parent) self.setWindowTitle(title) self.setFixedSize(320, 120) # Задаем фиксированный размер для аккуратного вида @@ -74,7 +75,7 @@ class SetSizeDialog(QDialog): # Макет для ввода значения input_layout = QHBoxLayout() - label = QLabel("Количество символов:", self) + label = QLabel(label_text, self) self.spin_box = QSpinBox(self) self.spin_box.setRange(min_value, max_value) # Устанавливаем диапазон допустимых значений @@ -155,6 +156,9 @@ class VariableTableWidget(QTableWidget): shortsize = self.settings.value("shortname_size", True, type=int) self._shortname_size = shortsize + if(self._show_value): + self._shortname_size = 3 + self.type_options = list(dict.fromkeys(type_map.values())) self.pt_types_all = [t.replace('pt_', '') for t in self.type_options] self.iq_types_all = ['iq_none', 'iq'] + [f'iq{i}' for i in range(1, 31)] @@ -263,13 +267,27 @@ class VariableTableWidget(QTableWidget): # Последний столбец if self._show_value: - val = var.get('value', '') - if val is None: - val = '' - val_edit = QLineEdit(str(val)) - val_edit.textChanged.connect(on_change_callback) - val_edit.setStyleSheet(style_with_padding) - self.setCellWidget(row, rows.short_name, val_edit) + if self._show_value: + val = var.get('value', '') + if val is None: + val = '' + else: + try: + f_val = float(val) + # Форматируем число с учетом self._shortname_size + if f_val.is_integer(): + val = str(int(f_val)) + else: + precision = getattr(self, "_shortname_size", 3) # по умолчанию 3 + val = f"{f_val:.{precision}f}" + except ValueError: + # Если значение не число (строка и т.п.), оставляем как есть + val = str(val) + + val_edit = QLineEdit(val) + val_edit.textChanged.connect(on_change_callback) + val_edit.setStyleSheet(style_with_padding) + self.setCellWidget(row, rows.short_name, val_edit) else: short_name_val = var.get('shortname', var['name']) short_name_edit = QLineEdit(short_name_val) @@ -309,9 +327,10 @@ class VariableTableWidget(QTableWidget): if not found: color = error_color tooltip = tooltip_missing - elif long_shortname: - color = warning_color - tooltip = tooltip_shortname + elif long_shortname: + if not self._show_value: + color = warning_color + tooltip = tooltip_shortname self.highlight_row(row, color, tooltip) t4 = time.time() @@ -365,10 +384,15 @@ class VariableTableWidget(QTableWidget): self.update_comboboxes({rows.ret_type: self._ret_type_filter}) elif logicalIndex == rows.short_name: - dlg = SetSizeDialog(self) + if self._show_value: + dlg = SetSizeDialog(self, title="Укажите точность", label_text="Кол-во знаков после запятой", initial_value=3) + else: + dlg = SetSizeDialog(self) + if dlg.exec_(): self._shortname_size = dlg.get_selected_size() - self.settings.setValue("shortname_size", self._shortname_size) + if not self._show_value: + self.settings.setValue("shortname_size", self._shortname_size) self.check() diff --git a/parse_xml/Src/parse_xml.py b/parse_xml/Src/parse_xml.py index 13c52b8..832a32f 100644 --- a/parse_xml/Src/parse_xml.py +++ b/parse_xml/Src/parse_xml.py @@ -1,12 +1,14 @@ -# pyinstaller --onefile --distpath ./parse_xml --workpath ./build --specpath ./build parse_xml/Src/parse_xml.py -# python -m nuitka --standalone --onefile --output-dir=./build parse_xml/Src/parse_xml.py +# pyinstaller --onefile --distpath ./parse_xml --workpath ./parse_xml/build --specpath ./build parse_xml/Src/parse_xml.py +# python -m nuitka --standalone --onefile --output-dir=./parse_xml parse_xml/Src/parse_xml.py import xml.etree.ElementTree as ET import xml.dom.minidom import sys import os + + if len(sys.argv) < 3: - print("Usage: python simplify_dwarf.py [output.xml]") + print("Usage: python parse_xml.exe [output.xml]") sys.exit(1) input_path = sys.argv[1] @@ -159,75 +161,6 @@ def parse_offset(offset_text): -def get_array_dimensions(array_die): - """ - Собрать размеры всех измерений массива. - Возвращает список размеров [dim0, dim1, ...] во внешне-внутреннем порядке. - """ - dims = [] - - # Ищем DW_TAG_subrange_type — для каждого измерения - for child in array_die.findall("die"): - if child.findtext("tag") != "DW_TAG_subrange_type": - continue - - dim_size = None - - # Попытка получить upper_bound - ub_attr = get_attr(child, "DW_AT_upper_bound") - if ub_attr is not None: - val = ub_attr.find("value/const") - if val is not None: - try: - # В DWARF верхняя граница включительно, значит размер = upper_bound + 1 - dim_size = int(val.text, 0) + 1 - except Exception: - pass - - # Если не получилось — попытаться из count - if dim_size is None: - ct_attr = get_attr(child, "DW_AT_count") - if ct_attr is not None: - val = ct_attr.find("value/const") - if val is not None: - try: - dim_size = int(val.text, 0) - except Exception: - pass - - # Если ничего не найдено — ставим 0 - if dim_size is None: - dim_size = 0 - - dims.append(dim_size) - - # Если subrange не нашли (например, в случае typedef), попробуем через размер типа - if not dims: - arr_size = get_die_size(array_die) - elem_size = None - element_type_ref = get_attr(array_die, "DW_AT_type") - if element_type_ref is not None and element_type_ref.find("ref") is not None: - element_type_id = element_type_ref.find("ref").attrib.get("idref") - elem_die = resolve_type_die(element_type_id) - if elem_die is not None: - elem_size = get_die_size(elem_die) - - if arr_size is not None and elem_size: - dims.append(arr_size // elem_size) - else: - dims.append(0) - - # Рекурсия — если элементный тип массива тоже массив, добавляем размеры вложенного - element_type_ref = get_attr(array_die, "DW_AT_type") - if element_type_ref is not None and element_type_ref.find("ref") is not None: - element_type_id = element_type_ref.find("ref").attrib.get("idref") - element_type_die = resolve_type_die(element_type_id) - if element_type_die is not None and element_type_die.findtext("tag") == "DW_TAG_array_type": - dims.extend(get_array_dimensions(element_type_die)) - - return dims - - def get_base_type_die(array_die): """Спускаемся по цепочке DW_AT_type, пока не дойдем до не-массива (базового типа).""" current_die = array_die @@ -244,11 +177,98 @@ def get_base_type_die(array_die): return next_die return current_die +def get_array_dimensions(array_die): + dims = [] + # Итерируем по всем DIE с тегом DW_TAG_subrange_type, потомки текущего массива + for child in array_die.findall("die"): + if child.findtext("tag") != "DW_TAG_subrange_type": + continue + + dim_size = None + ub_attr = get_attr(child, "DW_AT_upper_bound") + if ub_attr is not None: + # Попробуем разные варианты получить значение upper_bound + # 1) value/const + val_const = ub_attr.find("const") + if val_const is not None: + try: + dim_size = int(val_const.text, 0) + 1 + #print(f"[DEBUG] Found DW_AT_upper_bound const: {val_const.text}, size={dim_size}") + except Exception as e: + a=1#print(f"[WARN] Error parsing upper_bound const: {e}") + else: + # 2) value/block (DW_OP_constu / DW_OP_plus_uconst, etc.) + val_block = ub_attr.find("block") + if val_block is not None: + block_text = val_block.text + # Можно попытаться парсить DWARF expr (например DW_OP_plus_uconst 7) + if block_text and "DW_OP_plus_uconst" in block_text: + try: + parts = block_text.split() + val = int(parts[-1], 0) + dim_size = val + 1 + #print(f"[DEBUG] Parsed upper_bound block: {val} + 1 = {dim_size}") + except Exception as e: + a=1#print(f"[WARN] Error parsing upper_bound block: {e}") + else: + a=1#print(f"[WARN] Unexpected DW_AT_upper_bound block content: {block_text}") + else: + a=1#print(f"[WARN] DW_AT_upper_bound has no const or block value") + + if dim_size is None: + # fallback по DW_AT_count — редко встречается + ct_attr = get_attr(child, "DW_AT_count") + if ct_attr is not None: + val_const = ct_attr.find("value/const") + if val_const is not None: + try: + dim_size = int(val_const.text, 0) + #print(f"[DEBUG] Found DW_AT_count: {dim_size}") + except Exception as e: + a=1#print(f"[WARN] Error parsing DW_AT_count const: {e}") + + if dim_size is None: + print("[DEBUG] No dimension size found for this subrange, defaulting to 0") + dim_size = 0 + + dims.append(dim_size) + + # Если не нашли измерений — пытаемся вычислить размер массива по общему размеру + if not dims: + arr_size = get_die_size(array_die) + elem_size = None + element_type_ref = get_attr(array_die, "DW_AT_type") + if element_type_ref is not None and element_type_ref.find("ref") is not None: + element_type_id = element_type_ref.find("ref").attrib.get("idref") + elem_die = resolve_type_die(element_type_id) + if elem_die is not None: + elem_size = get_die_size(elem_die) + #print(f"[DEBUG] Fallback: arr_size={arr_size}, elem_size={elem_size}") + + if arr_size is not None and elem_size: + dim_calc = arr_size // elem_size + dims.append(dim_calc) + #print(f"[DEBUG] Calculated dimension size from total size: {dim_calc}") + else: + dims.append(0) + print("[DEBUG] Could not calculate dimension size, set 0") + + # Рекурсивно обрабатываем вложенные массивы + element_type_ref = get_attr(array_die, "DW_AT_type") + if element_type_ref is not None and element_type_ref.find("ref") is not None: + element_type_id = element_type_ref.find("ref").attrib.get("idref") + element_type_die = resolve_type_die(element_type_id) + if element_type_die is not None and element_type_die.findtext("tag") == "DW_TAG_array_type": + dims.extend(get_array_dimensions(element_type_die)) + + #print(f"[DEBUG] Array dimensions: {dims}") + return dims + + def handle_array_type(member_elem, resolved_type, offset=0): dims = get_array_dimensions(resolved_type) - # Определяем базовый тип (не массив) base_die = get_base_type_die(resolved_type) base_name = "unknown" base_size = None @@ -259,10 +279,10 @@ def handle_array_type(member_elem, resolved_type, offset=0): base_size = get_die_size(base_die) else: base_name = get_type_name(base_die.attrib.get("id", "")) + #print(f"[DEBUG] Base type name: {base_name}, base size: {base_size}") member_elem.set("type", base_name + "[]" * len(dims)) - # Вычисляем общий размер массива — произведение размеров * размер базового элемента if base_size is None: base_size = 0 @@ -270,31 +290,34 @@ def handle_array_type(member_elem, resolved_type, offset=0): for d in dims: if d == 0: total_elements = 0 + print(f"[WARN] Dimension size is zero, setting total elements to 0") break total_elements *= d total_size = total_elements * base_size if base_size is not None else 0 if total_size: - member_elem.set("size", str(total_size)) + member_elem.set("size", str(base_size if base_size is not None else 1)) else: - # fallback: если не удалось, можно попробовать get_die_size arr_size = get_die_size(resolved_type) if arr_size: member_elem.set("size", str(arr_size)) + #print(f"[DEBUG] Used fallback size from resolved_type: {arr_size}") + else: + print(f"[WARN] Could not determine total size for array") - # Записываем размеры измерений size1, size2 ... for i, dim in enumerate(dims, 1): member_elem.set(f"size{i}", str(dim)) + #print(f"[DEBUG] Setting size{i} = {dim}") member_elem.set("kind", "array") - # Если элемент базового типа — структура, разворачиваем её поля if base_die is not None and base_die.findtext("tag") == "DW_TAG_structure_type": add_members_recursive(member_elem, base_die, offset) + def add_members_recursive(parent_elem, struct_die, base_offset=0): is_union = struct_die.findtext("tag") == "DW_TAG_union_type" size = get_die_size(struct_die) @@ -330,6 +353,20 @@ def add_members_recursive(parent_elem, struct_die, base_offset=0): elif tag in ("DW_TAG_structure_type", "DW_TAG_union_type"): member_elem.set("type", type_name) add_members_recursive(member_elem, resolved_type, offset) + elif tag == "DW_TAG_pointer_type": + # Проверяем тип, на который указывает указатель + pointee_ref = get_attr(resolved_type, "DW_AT_type") + if pointee_ref is not None and pointee_ref.find("ref") is not None: + pointee_id = pointee_ref.find("ref").attrib.get("idref") + pointee_die = resolve_type_die(pointee_id) + if pointee_die is not None: + pointee_tag = pointee_die.findtext("tag") + if pointee_tag in ("DW_TAG_structure_type", "DW_TAG_union_type"): + # Добавляем подэлементы для структуры, на которую указывает указатель + pointer_elem = ET.SubElement(member_elem, "pointee", type=get_type_name(pointee_id)) + add_members_recursive(pointer_elem, pointee_die, 0) + + output_root = ET.Element("variables") for die in root.iter("die"): diff --git a/parse_xml/parse_xml.exe b/parse_xml/parse_xml.exe index 649285a..055de8b 100644 Binary files a/parse_xml/parse_xml.exe and b/parse_xml/parse_xml.exe differ