опять кууууча всего:

базово доделаны терминалки до более менее итогового состояния
This commit is contained in:
Razvalyaev 2025-07-23 17:13:28 +03:00
parent e99de603e6
commit 502046091c
14 changed files with 631 additions and 433 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@
/DebugVarEdit_GUI.dist
/DebugVarEdit_GUI.onefile-build
/parse_xml/build/
/parse_xml/Src/__pycache__/

Binary file not shown.

View File

@ -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):

View File

@ -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

View File

@ -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 = {

View File

@ -221,3 +221,4 @@ class CsvLogger:
return (0, float(ts))
except Exception:
return (1, str(ts))

View File

@ -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

View File

@ -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 ---------------

View File

@ -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)

View File

@ -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',

View File

@ -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()

View File

@ -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 <input.xml> <info.txt> [output.xml]")
print("Usage: python parse_xml.exe <input.xml> <info.txt> [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"):

Binary file not shown.