3 Commits

Author SHA1 Message Date
Razvalyaev
910bf0a585 Обновлены readme 2025-07-23 18:18:19 +03:00
Razvalyaev
502046091c опять кууууча всего:
базово доделаны терминалки до более менее итогового состояния
2025-07-23 17:13:28 +03:00
Razvalyaev
e99de603e6 попытка сделать в parse_xml парсинг вложенных массивов [][]
определяет вложенные массивы но не определяет их размерности (нули)
2025-07-22 18:57:59 +03:00
16 changed files with 842 additions and 499 deletions

1
.gitignore vendored
View File

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

Binary file not shown.

116
README.md
View File

@@ -1,4 +1,11 @@
# DebugTools - Просмотр переменных по указателям # DebugTools - Просмотр переменных по указателям
## Содержание
1. [Описание модуля](#программный-модуль-debugtools)
2. [Описание приложения для настройки](#debugvaredit---настройка-переменных)
3. [Описание терминалки для считывания](#debugvarterminal---считывание-переменных-для-tms)
4. [Для разработчиков](#для-разработчиков)
# Программный модуль DebugTools
Модуль состоит из трех файлов: Модуль состоит из трех файлов:
- **debug_tools.c** - реализация считывания переменных - **debug_tools.c** - реализация считывания переменных
- **debug_tools.h** - объявление всякого для считывания переменных - **debug_tools.h** - объявление всякого для считывания переменных
@@ -9,11 +16,26 @@
Для чтения переменных можно использовать функции: Для чтения переменных можно использовать функции:
```c ```c
/* Читает значение переменной по индексу */
int Debug_ReadVar(int var_ind, int32_t *return_long); int Debug_ReadVar(int var_ind, int32_t *return_long);
int Debug_ReadVarName(int var_ind, DebugVarName_t name_ptr); /* Читает имя переменной по индексу */
``` int Debug_ReadVarName(int var_ind, DebugVarName_t name_ptr, int *length);
/* Читает возвращаемый тип (IQ) переменной по индексу */
int Debug_ReadVarReturnType(int var_ind, int *vartype);
/* Читает тип переменной по индексу */
int Debug_ReadVarType(int var_ind, int *vartype);
/* Читает значение переменной с нижнего уровня */
int Debug_LowLevel_ReadVar(int32_t *return_long);
/* Инициализирует отладку нижнего уровня */
int Debug_LowLevel_Initialize(DateTime_t *external_date);
/* Читает возвращаемый тип (IQ) низкоуровнено заданной переменной */
int Debug_LowLevel_ReadVarReturnType(int *vartype);
/* Читает тип низкоуровнено заданной переменной.*/
int Debug_LowLevel_ReadVarType(int *vartype);
```
Переменные доступные для чтения определяются в **debug_vars.c** (их можно прописывать вручную или генерировать через **DebugVarEdit**): Переменные доступные для чтения определяются в **debug_vars.c** (их можно прописывать вручную или генерировать через **DebugVarEdit**):
```c ```c
@@ -32,6 +54,26 @@ DebugVar_t dbg_vars[] = {\
}; };
``` ```
## LowLevel - Просмотр абсолютно любых переменных
Также присутствует утилита `parse_xml.exe`, которая генерирует `.xml` файл с всеми переменными в программе и их адрессами
Для её подключения:
- В Pre-Build Steps добавить следующую команду. Это удаление `debug_tools.obj` файла для его перекомпиялции с актуальной датой компиляции
```
cmd /c del /Q "${CWD}\Src\DebugTools\debug_tools.obj"
```
- В Post-Build Steps добавить следующую команду. Это:
- формирование с помощью утилиты компилятора `ofd2000` `.xml` файла с полной отладочной информацией.
- формирование отладочной информации в текстовом файле (для получения таймштампа)
- запуск утилиты `parse_xml.exe` для формирования итогового `<projname>_allVars.xml` с информацией о переменных и адресах
```
"${CG_TOOL_ROOT}/bin/ofd2000" --obj_display=symbols --dwarf --dwarf_display=all --xml --xml_indent=1 --output="${BuildArtifactFileBaseName}_ofd_dump.xml" "${BuildArtifactFilePath}"
"${CG_TOOL_ROOT}/bin/ofd2000" "${BuildArtifactFilePath}" > ${CCS_PROJECT_DIR}/bin/temp.txt
"${CCS_PROJECT_DIR}/Src/DebugTools/parse_xml/parse_xml.exe" ${BuildArtifactFileBaseName}_ofd_dump.xml ${CCS_PROJECT_DIR}/bin/temp.txt ${BuildArtifactFileBaseName}_allVars.xml"
```
После, с использованием терминалки можно прочитать любые переменные по адресам. (должен совпадать таймштапм в прошивке и `.xml` файла, иначе контроллер ответит ошибкой)
# DebugVarEdit - Настройка переменных # DebugVarEdit - Настройка переменных
**DebugVarEdit** — графическое приложение для Windows, предназначенное для настройки и генерации отладочных переменных (`debug_vars.c`) на основе исходного C-проекта. Работает с `makefile` проекта, сохраняет изменения в XML и позволяет удобно редактировать переменные и их типы через интерфейс. **DebugVarEdit** — графическое приложение для Windows, предназначенное для настройки и генерации отладочных переменных (`debug_vars.c`) на основе исходного C-проекта. Работает с `makefile` проекта, сохраняет изменения в XML и позволяет удобно редактировать переменные и их типы через интерфейс.
@@ -113,6 +155,68 @@ DebugVar_t dbg_vars[] = {\
``` ```
--- ---
# DebugVarTerminal - Считывание переменных (для TMS)
**DebugVarTerminal** — терминалка для считывания переменных по RS-232 протоколу RS_Functions.
Программа — один исполняемый файл `DebugVarTerminal.exe`, не требующий установки и дополнительных зависимостей.
> Требуется Windows 7 или новее.
---
## Как использовать приложение
1. Запустите **DebugVarTerminal.exe.**
2. Выберите COM Port и скорость Baud. Нажмиите **Open**
3. Для считывания переменных заданных в `*debug_vars.c`:
- Откройте вкладку **Watch**.
- Выберите стартовый индекс переменной и количество переменных для считывания.
- Нажмите одну из кнопок:
- **Update Service** - считывает информацию об именах и возвращаемых IQ типах переемнных
- **Read Value(s)** - считывает и форматирует значения переменных в соответствии с IQ типами
- **Start Polling** - начать опрос выбранных переменных с заданным интервалом. При старте опроса, имя и тип переменных считываются автоматически
4. Для считывания переменных по адресам:
- Откройте вкладку **LowLevel**.
- Выберите `projname_allVars.xml` в папке bin рядом с бинарником. Из него подгрузятся все доступные для считывания переменные
- Выберите переменные кнопкной **Выбрать переменные**
- Задайте IQ тип и возвращаемый IQ тип если требуется
- Нажмите одну из кнопок:
- **Read Once** - считывает выбранные переменные один раз
- **Start Polling** - начать опрос выбранных переменных с заданным интервалом
5. Запись в CSV:
- Можно записывавать считываемые переменные в CSV файл, с разделителем `;`
- Нажмите кнопку **Начать запись в CSV**
- Когда нужная выборка будет накоплена нажмите **Остаовить запись в CSV**
- Нажмите **Выбрать файл CSV** для выбора пути для сохранения файла и нажмите **Сохранить данные в CSV** чтобы сохранить
- Генерируется совместимый с LogView `.csv` файл
Все параметры выбираются из выпадающих списков, которые можно настроить, чтобы отображались только нужные опции.
---
## Возможности
Режим "Watch":
- Быстрое и удобное чтение одной или нескольких переменных по их индексу.
- Автоматическое получение имен, типов и параметров масштабирования (IQ) переменных.
- Запуск постоянного опроса для отслеживания изменений значений в реальном времени.
- Наглядное представление данных в таблице с отображением как сырых, так и масштабированных значений.
Режим "LowLevel":
- Прямое чтение данных из памяти по заданным адресам.
- Возможность указания типов указателей, IQ-масштабирования и форматов возвращаемых данных.
- Аналогично режиму "Watch", поддерживается постоянный опрос выбранных низкоуровневых переменных.
- Умное автодополнение имён переменных и полей структур.
Логирование в CSV
- Записывайте все полученные значения в файл формата CSV для последующего анализа.
- Легкое управление записью: запуск, остановка и сохранение данных в любой момент.
---
--- ---
# Для разработчиков # Для разработчиков
@@ -124,17 +228,23 @@ Src
├── build/ ├── build/
│ └── build_and_clean.py # Билдинг проекта в .exe (через nuitka или pyinstaller) │ └── build_and_clean.py # Билдинг проекта в .exe (через nuitka или pyinstaller)
├── DebugVarEdit_GUI.py # Главное окно ├── DebugVarEdit_GUI.py # Главное окно
├── tms_debugvar_term.py # Терминал DebugVarTerminal
├── tms_debugvar_lowlevel.py # Виджет для выбора переменных для LowLevel Watch
├── var_table.py # Таблица выбранных переменных ├── var_table.py # Таблица выбранных переменных
├── var_selector_window.py # Окно выбора переменных ├── var_selector_window.py # Окно выбора переменных
├── var_selector_table.py # Таблица переменных в окне выбора переменных ├── var_selector_table.py # Таблица переменных в окне выбора переменных
├── scan_progress_gui.py # Отображение процесса сканирования переменных ├── scan_progress_gui.py # Отображение процесса сканирования переменных
├── scan_vars.py # Сканирование переменных среди .c/.h файлов ├── scan_vars.py # Сканирование переменных среди .c/.h файлов
├── generate_debug_vars.py # Генерация debug_vars.c ├── generate_debug_vars.py # Генерация debug_vars.c
├── allvars_xml_parser.py # Парсинг XML со всеми переменными и структурами
├── csv_logger.py # Логирование переменных в CSV
├── myXML.py # Утилиты для XML ├── myXML.py # Утилиты для XML
├── makefile_parser.py # Парсинг makefile на .c/.h файлы ├── makefile_parser.py # Парсинг makefile на .c/.h файлы
├── var_setup.py # Подготовка переменных для окна выбора переменных ├── var_setup.py # Подготовка переменных для окна выбора переменных
├── path_hints.py # Подсказки для автодополнения путей переменных
├── libclang.dll # Бибилиотека clang ├── libclang.dll # Бибилиотека clang
├── icon.ico # Иконка ├── icon.ico # Иконка
``` ```
### Зависимости ### Зависимости
@@ -161,7 +271,7 @@ Src
- Очищает временные папки после сборки: - Очищает временные папки после сборки:
- `build_temp` - `build_temp`
- `__pycache__` - `__pycache__`
- `DebugVarEdit_GUI.*` - `<MAIN_SCRIPT_NAME>.*`
> Все пути, имена файлов, временные папки и выбор между Nuitka и PyInstaller можно настроить в начале файла `build_and_clean.py`. > Все пути, имена файлов, временные папки и выбор между Nuitka и PyInstaller можно настроить в начале файла `build_and_clean.py`.

View File

@@ -191,14 +191,39 @@ class VarEditor(QWidget):
self.setLayout(layout) self.setLayout(layout)
def open_terminal(self, target): def open_terminal(self, target):
target = target.lower() target = target.lower()
if target == "tms": if target == "tms":
self.terminal_widget = _DemoWindow() # _DemoWindow наследует QWidget exe_name = "DebugVarTerminal.exe"
self.terminal_widget.show() # Путь к 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": elif target == "modbus":
a=1 a = 1
def on_target_selected(self, target): def on_target_selected(self, target):

View File

@@ -1,3 +1,4 @@
# Для разработчиков # Для разработчиков
### Структура проекта: ### Структура проекта:
@@ -7,31 +8,37 @@ Src
├── build/ ├── build/
│ └── build_and_clean.py # Билдинг проекта в .exe (через nuitka или pyinstaller) │ └── build_and_clean.py # Билдинг проекта в .exe (через nuitka или pyinstaller)
├── DebugVarEdit_GUI.py # Главное окно ├── DebugVarEdit_GUI.py # Главное окно
├── tms_debugvar_term.py # Терминал DebugVarTerminal
├── tms_debugvar_lowlevel.py # Виджет для выбора переменных для LowLevel Watch
├── var_table.py # Таблица выбранных переменных ├── var_table.py # Таблица выбранных переменных
├── var_selector_window.py # Окно выбора переменных ├── var_selector_window.py # Окно выбора переменных
├── var_selector_table.py # Таблица переменных в окне выбора переменных ├── var_selector_table.py # Таблица переменных в окне выбора переменных
├── scan_progress_gui.py # Отображение процесса сканирования переменных ├── scan_progress_gui.py # Отображение процесса сканирования переменных
├── scan_vars.py # Сканирование переменных среди .c/.h файлов ├── scan_vars.py # Сканирование переменных среди .c/.h файлов
├── generate_debug_vars.py # Генерация debug_vars.c ├── generate_debug_vars.py # Генерация debug_vars.c
├── allvars_xml_parser.py # Парсинг XML со всеми переменными и структурами
├── csv_logger.py # Логирование переменных в CSV
├── myXML.py # Утилиты для XML ├── myXML.py # Утилиты для XML
├── makefile_parser.py # Парсинг makefile на .c/.h файлы ├── makefile_parser.py # Парсинг makefile на .c/.h файлы
├── var_setup.py # Подготовка переменных для окна выбора переменных ├── var_setup.py # Подготовка переменных для окна выбора переменных
├── path_hints.py # Подсказки для автодополнения путей переменных
├── libclang.dll # Бибилиотека clang ├── libclang.dll # Бибилиотека clang
├── icon.ico # Иконка ├── icon.ico # Иконка
``` ```
### Зависимости ### Зависимости
Для запуска приложения: Для запуска приложения:
- **Python 3.7+** - **Python 3.7+**
- **clang** — используется для парсинга C-кода (требуется `libclang.dll`) - **clang** — используется для парсинга C-кода (требуется `libclang.dll`, лежит в папке Src)
- **PySide2** — GUI-фреймворк - **PySide2** — GUI-фреймворк
- **lxml** — работа с XML - **lxml** — работа с XML
> Python 3.7 и PySide2 рекомендуется для совместимости с Windows 7 > Python 3.7 и PySide2 рекомендуется для совместимости с Windows 7
Для сборки `.exe`: Для сборки `.exe`:
- **Nuitka** — создает полностью автономный `.exe` без внешних зависимостей - **Nuitka** — создает полностью автономный `.exe` без внешних зависимостей
- **PyInstaller** — создает `.exe` с зависимостью от `pythonXX.dll` (Python должен быть установлен) - **PyInstaller** — создает `.exe` с зависимостью от `pythonXX.dll` (Python должен быть установлен для запуска `.exe`)
### Сборка: ### Сборка:
@@ -44,12 +51,12 @@ Src
- Очищает временные папки после сборки: - Очищает временные папки после сборки:
- `build_temp` - `build_temp`
- `__pycache__` - `__pycache__`
- `DebugVarEdit_GUI.*` - `<MAIN_SCRIPT_NAME>.*`
> Все пути, имена файлов, временные папки и выбор между Nuitka и PyInstaller можно настроить в начале файла `build_and_clean.py`. > Все пути, имена файлов, временные папки и выбор между Nuitka и PyInstaller можно настроить в начале файла `build_and_clean.py`.
### Установка зависимостей ### Установка зависимостей
```bash ```bash
pip install PySide2 lxml nuitka pyinstaller pip install clang PySide2 lxml nuitka pyinstaller
``` ```

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 from __future__ import annotations
import sys
import re import re
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
import var_setup
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import List, Dict, Optional, Tuple from typing import List, Dict, Optional, Tuple, Any
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
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 @dataclass
class MemberNode: class MemberNode:
name: str name: str
offset: int = 0 offset: int = 0
type_str: str = '' type_str: str = ""
size: Optional[int] = None size: Optional[int] = None # общий размер (байты), если известен
children: List['MemberNode'] = field(default_factory=list) children: List["MemberNode"] = field(default_factory=list)
# --- новые, но необязательные (совместимость) --- # --- доп.поля ---
kind: Optional[str] = None # 'array', 'union', ... kind: Optional[str] = None # 'array', 'union', ...
count: Optional[int] = None # size1 (число элементов в массиве) dims: Optional[List[int]] = None
def is_date_struct(self) -> bool: def is_date_struct(self) -> bool:
if not self.children: if not self.children:
@@ -44,51 +67,57 @@ class VariableNode:
type_str: str type_str: str
size: Optional[int] size: Optional[int]
members: List[MemberNode] = field(default_factory=list) members: List[MemberNode] = field(default_factory=list)
# --- новые, но необязательные --- # --- доп.поля ---
kind: Optional[str] = None # 'array' kind: Optional[str] = None # 'array'
count: Optional[int] = None # size1 dims: Optional[List[int]] = None # полный список размеров [size1, size2, ...]
def base_address_hex(self) -> str: def base_address_hex(self) -> str:
return f"0x{self.address:06X}" return f"0x{self.address:06X}"
# --------------------------- XML Parser ---------------------------- # --------------------------- класс парсера -----------------------
class VariablesXML: class VariablesXML:
""" """
Reads your XML and outputs a flat list of paths: Читает XML и предоставляет методы:
- Arrays -> name[i], multilevel -> name[i][j] - flattened(): плоский список всех путей.
- Pointer to struct -> children via '->' - date_struct_candidates(): как раньше.
- Regular struct -> children via '.'
Правила формирования путей:
* Структурные поля: '.'
* Поля через указатель на структуру: '->'
* Массивы: [index] (каждое измерение).
""" """
# assumed primitive sizes (for STM/MCU: int=2)
# предполагаемые размеры примитивов (MCU: int=2)
_PRIM_SIZE = { _PRIM_SIZE = {
'char':1, 'signed char':1, 'unsigned char':1, 'uint8_t':1, 'int8_t':1, 'char': 1, 'signed char': 1, 'unsigned char': 1,
'short':2, 'short int':2, 'signed short':2, 'unsigned short':2, 'uint8_t': 1, 'int8_t': 1,
'uint16_t':2, 'int16_t':2, 'short': 2, 'short int': 2, 'signed short': 2, 'unsigned short': 2,
'int':2, 'signed int':2, 'unsigned int':2, 'uint16_t': 2, 'int16_t': 2,
'long':4, 'unsigned long':4, 'int32_t':4, 'uint32_t':4, 'int': 2, 'signed int': 2, 'unsigned int': 2,
'float':4, 'long': 4, 'unsigned long': 4, 'int32_t': 4, 'uint32_t': 4,
'long long':8, 'unsigned long long':8, 'int64_t':8, 'uint64_t':8, 'double':8, 'float': 4,
'long long': 8, 'unsigned long long': 8, 'int64_t': 8, 'uint64_t': 8, 'double': 8,
} }
def __init__(self, path: str): def __init__(self, path: str):
self.path = path self.path = path
self.timestamp: str = '' self.timestamp: str = ""
self.variables: List[VariableNode] = [] self.variables: List[VariableNode] = []
choose_type_map(0) choose_type_map(0) # инициализация карт типов (если требуется)
self._parse() self._parse()
# ------------------ low helpers ------------------ # ------------------ утилиты ------------------
@staticmethod @staticmethod
def _parse_int_guess(txt: Optional[str]) -> Optional[int]: def _parse_int_guess(txt: Optional[str]) -> Optional[int]:
if not txt: if not txt:
return None return None
txt = txt.strip() txt = txt.strip()
if txt.startswith(('0x','0X')): if txt.startswith(('0x', '0X')):
return int(txt, 16) return int(txt, 16)
# если в строке есть буквы A-F → возможно hex # если в строке есть A-F, попробуем hex
if any(c in 'abcdefABCDEF' for c in txt): if any(c in 'abcdefABCDEF' for c in txt):
try: try:
return int(txt, 16) return int(txt, 16)
@@ -103,7 +132,7 @@ class VariablesXML:
def _is_pointer_to_struct(t: str) -> bool: def _is_pointer_to_struct(t: str) -> bool:
if not t: if not t:
return False return False
low = t.replace('\t',' ').replace('\n',' ') low = t.replace('\t', ' ').replace('\n', ' ')
return 'struct ' in low and '*' in low return 'struct ' in low and '*' in low
@staticmethod @staticmethod
@@ -113,24 +142,34 @@ class VariablesXML:
low = t.strip() low = t.strip()
return low.startswith('struct ') or low.startswith('union ') 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 @staticmethod
def _strip_array_suffix(t: str) -> str: 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]: def _guess_primitive_size(self, type_str: str) -> Optional[int]:
if not type_str: if not type_str:
return None return None
base = type_str base = type_str
for tok in ('volatile','const'): for tok in ('volatile', 'const'):
base = base.replace(tok, '') base = base.replace(tok, '')
base = base.replace('*',' ') base = base.replace('*', ' ')
base = base.replace('[',' ').replace(']',' ') base = base.replace('[', ' ').replace(']', ' ')
base = ' '.join(base.split()).strip() base = ' '.join(base.split()).strip()
return self._PRIM_SIZE.get(base) return self._PRIM_SIZE.get(base)
# ------------------ XML read ------------------ # ------------------ XML read ------------------
def _parse(self): def _parse(self) -> None:
try: try:
tree = ET.parse(self.path) tree = ET.parse(self.path)
root = tree.getroot() root = tree.getroot()
@@ -138,204 +177,235 @@ class VariablesXML:
ts = root.find('timestamp') ts = root.find('timestamp')
self.timestamp = ts.text.strip() if ts is not None and ts.text else '' self.timestamp = ts.text.strip() if ts is not None and ts.text else ''
def parse_member(elem) -> MemberNode: def parse_member(elem: ET.Element, base_offset=0) -> MemberNode:
name = elem.get('name','') name = elem.get('name', '')
offset = int(elem.get('offset','0'),16) if elem.get('offset') else 0 offset = int(elem.get('offset', '0'), 16) if elem.get('offset') else 0
t = elem.get('type','') or '' t = elem.get('type', '') or ''
size_attr = elem.get('size') 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') kind = elem.get('kind')
size1_attr = elem.get('size1')
count = None abs_offset = base_offset + offset
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) 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'): 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 return node
for var in root.findall('variable'): for var in root.findall('variable'):
addr = int(var.get('address','0'),16) addr = int(var.get('address', '0'), 16)
name = var.get('name','') name = var.get('name', '')
t = var.get('type','') or '' t = var.get('type', '') or ''
size_attr = var.get('size') size_attr = var.get('size') # общий размер байт
size = int(size_attr,16) if size_attr else None size = int(size_attr, 16) if size_attr else None
kind = var.get('kind') kind = var.get('kind')
size1_attr = var.get('size1')
count = None dims: List[int] = []
if size1_attr: i = 1
count = self._parse_int_guess(size1_attr) 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')] members = [parse_member(m) for m in var.findall('member')]
self.variables.append(
VariableNode(name=name, address=addr, type_str=t, size=size, v = VariableNode(
members=members, kind=kind, count=count) 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: except FileNotFoundError:
self.variables = [] self.variables = []
except ET.ParseError: except ET.ParseError:
self.variables = [] self.variables = []
# ------------------ flatten (expanded) ------------------ # ------------------ helpers для flattened ---------------------
def flattened(self, def _elem_size_bytes(self, total_size: Optional[int], dims: List[int], base_type: str, members: List[MemberNode]) -> int:
max_array_elems: Optional[int] = None """Оценка размера одного *листового* элемента (последнего измерения).
) -> List[Dict[str, Any]]: Если total_size и dims все известны — берём size / prod(dims).
Иначе — пробуем примитивный размер; иначе 1.
(Не учитываем выравнивание структур; при необходимости можно расширить.)
""" """
Returns a list of dictionaries with full data for variables and their expanded members. if total_size is not None and dims:
Each dictionary contains: 'name', 'address', 'type', 'size', 'kind', 'count'. prod = 1
max_array_elems: limit unfolding of large arrays (None = all). 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]] = [] 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]: def mk(name: str, addr: Optional[int], type_str: str, size: Optional[int], kind: Optional[str], dims_for_node: Optional[List[int]]):
"""Helper to create the output dictionary format.""" if 'Bender' in name:
return { a=1
out.append({
'name': name, 'name': name,
'address': address, 'address': addr,
'type': type_str, 'type': type_str,
'size': size, 'size': size,
'kind': kind, 'kind': kind,
'count': count 'dims': dims_for_node[:] if dims_for_node else None,
} })
def compute_stride(size_bytes: Optional[int], def expand_members(prefix: str, base_addr: int, members: List[MemberNode], parent_is_ptr_struct: bool, parent_is_union: bool) -> None:
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 '.'
"""
join = '->' if parent_is_ptr_struct else '.' join = '->' if parent_is_ptr_struct else '.'
for m in members: for m in members:
path_m = f"{prefix_name}{join}{m.name}" if prefix_name else m.name 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 addr_m = base_addr + m.offset
out.append(get_dict(path_m, addr_m, m.type_str, m.size, m.kind, m.count))
# array? dims = m.dims or []
if (m.kind == 'array') or m.type_str.endswith('[]'):
count = m.count mk(path_m, addr_m, m.type_str, m.size, m.kind, dims)
if count is None:
count = 0 if m.kind == 'array' and dims:
if count <= 0:
continue
base_t = self._strip_array_suffix(m.type_str) base_t = self._strip_array_suffix(m.type_str)
stride = compute_stride(m.size, count, base_t, m.children if m.children else None) elem_sz = m.size
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'
# 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)
# array element: if structure / union → unfold fields else:
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: 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) is_ptr = self._is_pointer_to_struct(m.type_str)
# Рекурсивно раскрываем дочерние поля, выбирая правильный разделитель
expand_members(path_m, addr_m, m.children, is_ptr, is_union)
# --- top-level variables ---
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()
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: for v in self.variables:
out.append(get_dict(v.name, v.address, v.type_str, v.size, v.kind, v.count)) dims = v.dims or []
mk(v.name, v.address, v.type_str, v.size, v.kind, dims)
# top-level array? if (v.kind == 'array' or v.type_str.endswith('[]')) and dims:
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) base_t = self._strip_array_suffix(v.type_str)
stride = compute_stride(v.size, count, base_t, v.members if v.members else None) elem_sz = v.size
limit = count if max_array_elems is None else min(count, max_array_elems) expand_dims(v.name, v.address, dims, base_t, v.members, elem_sz, parent_is_ptr_struct=False)
for i in range(limit): else:
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: if v.members:
is_ptr_struct = self._is_pointer_to_struct(v.type_str) is_ptr = self._is_pointer_to_struct(v.type_str)
expand_members(v.name, v.address, v.members, parent_is_ptr_struct=is_ptr_struct) is_union = self._is_union(v.type_str)
expand_members(v.name, v.address, v.members, is_ptr, is_union)
return out 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 = [] cands = []
for v in self.variables: for v in self.variables:
# top level (if all date fields are present) # top level (if all date fields are present)
@@ -349,90 +419,82 @@ class VariablesXML:
return cands return cands
# ------------------------------------------------------------------
# Построение иерархического дерева из flattened()
# ------------------------------------------------------------------
def get_all_vars_data(self) -> List[Dict[str, Any]]: 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) 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: for item in flat_data:
node_dict = {**item, 'children': []} node = dict(item)
all_nodes_map[item['name']] = node_dict node['children'] = []
all_nodes[item['name']] = node
# Вспомогательная функция для определения полного пути родителя с использованием split_path def _parent_struct_split(path: str) -> Optional[str]:
def get_parent_path_using_split(full_path: str) -> Optional[str]: # Ищем последний '.' или '->' для определения родителя
# 1. Используем split_path для получения компонентов пути. dot_idx = path.rfind('.')
components = var_setup.split_path(full_path) arrow_idx = path.rfind('->')
cut_idx = max(dot_idx, arrow_idx)
# Если нет компонентов или только один (верхний уровень, не массивный элемент) if cut_idx == -1:
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 return None
elif len(components) == 2 and components[-1].startswith('['): # like "my_array[0]" # '->' занимает 2 символа, нужно взять срез до начала '->'
return components[0] # Return "my_array" as parent if arrow_idx > dot_idx:
else: # Edge cases or malformed, treat as root return path[:arrow_idx]
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]
else: else:
return None # Корневой элемент без явного родителя return path[:dot_idx]
# Основная логика get_all_vars_data def find_parent(path: str) -> Optional[str]:
"""
Возвращает полный путь родителя, учитывая '.', '->' и индексы [] в конце.
# Заполнение связей "родитель-потомок" Если путь заканчивается индексом [k], удаляет последний индекс и проверяет наличие родителя.
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: # Если есть trailing индекс в конце, убираем его
all_nodes_map[parent_name]['children'].append(node_dict) 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: else:
root_nodes.append(node_dict) # Если нет индекса, просто ищем последний разделитель
return _parent_struct_split(path)
# Сортируем корневые узлы и их детей рекурсивно по имени # Строим иерархию: parent -> children
def sort_nodes(nodes_list: List[Dict[str, Any]]): roots: List[Dict[str, Any]] = []
nodes_list.sort(key=lambda x: x['name']) for full_name, node in all_nodes.items():
for node in nodes_list: parent_name = find_parent(full_name)
if node['children']: if parent_name and parent_name in all_nodes:
sort_nodes(node['children']) all_nodes[parent_name]['children'].append(node)
else:
roots.append(node)
sort_nodes(root_nodes) # Рекурсивно сортируем детей по имени для порядка
def sort_nodes(nodes: List[Dict[str, Any]]):
return root_nodes 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 USE_NUITKA = True # True — сборка через Nuitka, False — через PyInstaller
MAIN_SCRIPT_NAME = "tms_debugvar_term"
OUTPUT_NAME = "DebugVarTerminal"
SRC_PATH = Path("./Src/") SRC_PATH = Path("./Src/")
SCRIPT_PATH = SRC_PATH / "DebugVarEdit_GUI.py" SCRIPT_PATH = SRC_PATH / (MAIN_SCRIPT_NAME + ".py")
OUTPUT_NAME = "DebugVarEdit"
DIST_PATH = Path("./").resolve() DIST_PATH = Path("./").resolve()
WORK_PATH = Path("./build_temp").resolve() WORK_PATH = Path("./build_temp").resolve()
@@ -26,9 +28,9 @@ ICON_ICO_PATH = SRC_PATH / "icon.ico"
TEMP_FOLDERS = [ TEMP_FOLDERS = [
"build_temp", "build_temp",
"__pycache__", "__pycache__",
"DebugVarEdit_GUI.build", MAIN_SCRIPT_NAME + ".build",
"DebugVarEdit_GUI.onefile-build", MAIN_SCRIPT_NAME + ".onefile-build",
"DebugVarEdit_GUI.dist" MAIN_SCRIPT_NAME + ".dist"
] ]
# === Пути к DLL и прочим зависимостям === # === Пути к DLL и прочим зависимостям ===
LIBS = { LIBS = {

View File

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

View File

@@ -59,6 +59,47 @@ def split_path_tokens(path: str) -> List[str]:
tokens.append(token) tokens.append(token)
return tokens 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: def canonical_key(path: str) -> str:
""" """
@@ -137,28 +178,19 @@ class PathHints:
self._paths.append(full_path) self._paths.append(full_path)
self._types[full_path] = type_str self._types[full_path] = type_str
toks = split_path_tokens(full_path) tokens_spans = split_path_tokens_with_spans(full_path)
if not toks: if not tokens_spans:
return return
cur_dict = self._root_children cur_dict = self._root_children
cur_full = '' cur_full = ''
parent_node: Optional[PathNode] = None parent_node: Optional[PathNode] = None
for i, tok in enumerate(toks): for i, (tok, start, end) in enumerate(tokens_spans):
# Собираем ПОЛНЫЙ путь cur_full = full_path[:end] # подстрока с начала до конца токена включительно
if cur_full == '':
cur_full = tok
else:
if tok.startswith('['):
cur_full += tok
else:
cur_full += '.' + tok
# Если узел уже есть
node = cur_dict.get(tok) node = cur_dict.get(tok)
if node is None: if node is None:
# --- ВАЖНО: full_path = cur_full ---
node = PathNode(name=tok, full_path=cur_full) node = PathNode(name=tok, full_path=cur_full)
cur_dict[tok] = node cur_dict[tok] = node

View File

@@ -339,7 +339,7 @@ class LowLevelSelectorWidget(QWidget):
name = var['ptr_type'] name = var['ptr_type']
elif isinstance(var.get('ptr_type_name'), str): elif isinstance(var.get('ptr_type_name'), str):
name = var['ptr_type_name'] 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_','') name = var['pt_type'].replace('pt_','')
elif isinstance(var.get('ptr_type'), int): elif isinstance(var.get('ptr_type'), int):
name = PT_ENUM_NAME_FROM_VAL.get(var['ptr_type'], 'unknown') name = PT_ENUM_NAME_FROM_VAL.get(var['ptr_type'], 'unknown')
@@ -456,7 +456,7 @@ class LowLevelSelectorWidget(QWidget):
return False return False
# 2. Обновляем отображение в таблице # 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 return True
# --------------- Address mapping / type mapping helpers --------------- # --------------- 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 from tms_debugvar_lowlevel import LowLevelSelectorWidget
import datetime import datetime
import time import time
import os
from csv_logger import CsvLogger from csv_logger import CsvLogger
# ------------------------------- Константы протокола ------------------------ # ------------------------------- Константы протокола ------------------------
WATCH_SERVICE_BIT = 0x8000 WATCH_SERVICE_BIT = 0x8000
@@ -44,6 +45,19 @@ def crc16_ibm(data: bytes, *, init=0xFFFF) -> int:
crc >>= 1 crc >>= 1
return crc & 0xFFFF 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: def _decode_debug_status(status: int) -> str:
"""Преобразует код статуса прошивки в строку. """Преобразует код статуса прошивки в строку.
Возвращает 'OK' или перечисление битов через '|'. Возвращает 'OK' или перечисление битов через '|'.
@@ -97,9 +111,6 @@ class Spoiler(QtWidgets.QWidget):
self._ani_content.setDuration(animationDuration) self._ani_content.setDuration(animationDuration)
self._ani_content.setEasingCurve(QtCore.QEasingCurve.InOutCubic) self._ani_content.setEasingCurve(QtCore.QEasingCurve.InOutCubic)
# Следим за шагами анимации → обновляем родителя
self._ani_content.valueChanged.connect(self._adjust_parent_size)
# --- Layout --- # --- Layout ---
self.mainLayout = QtWidgets.QGridLayout(self) self.mainLayout = QtWidgets.QGridLayout(self)
self.mainLayout.setVerticalSpacing(0) self.mainLayout.setVerticalSpacing(0)
@@ -120,13 +131,6 @@ class Spoiler(QtWidgets.QWidget):
def getState(self): def getState(self):
return self.state 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): def _on_toggled(self, checked: bool):
self.state = checked self.state = checked
self.toggleButton.setArrowType(QtCore.Qt.DownArrow if checked else QtCore.Qt.RightArrow) 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.stop()
self._ani_content.setStartValue(self.contentArea.maximumHeight()) self._ani_content.setStartValue(self.contentArea.maximumHeight())
self._ani_content.setEndValue(contentHeight if checked else 0) 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() self._ani_content.start()
@@ -420,9 +418,8 @@ class DebugTerminalWidget(QtWidgets.QWidget):
self.chk_hex_index.stateChanged.connect(self._toggle_index_base) self.chk_hex_index.stateChanged.connect(self._toggle_index_base)
# LowLevel (новые и переделанные) # 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.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) self.ll_selector.btn_start_polling.clicked.connect(self._toggle_ll_polling)
# --- CSV Logging --- # --- CSV Logging ---
@@ -681,7 +678,7 @@ class DebugTerminalWidget(QtWidgets.QWidget):
# !!! Раньше тут было `return`, его убираем # !!! Раньше тут было `return`, его убираем
# Если идёт LL polling — переходим сразу к следующей переменной # Если идёт 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() self._process_next_ll_variable_in_cycle()
return return
@@ -904,6 +901,13 @@ class DebugTerminalWidget(QtWidgets.QWidget):
status_desc = _decode_debug_status(status) 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] return_type = payload[6]
data_hi, data_lo = payload[7], payload[8] data_hi, data_lo = payload[7], payload[8]
raw16 = (data_hi << 8) | data_lo raw16 = (data_hi << 8) | data_lo
@@ -923,7 +927,6 @@ class DebugTerminalWidget(QtWidgets.QWidget):
scaled = float(value_int) / scale scaled = float(value_int) / scale
self.llValueRead.emit(addr24, status, return_type, value_int, scaled) self.llValueRead.emit(addr24, status, return_type, value_int, scaled)
var_name = None 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}") 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) 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.btn_poll.setText("Stop Polling")
self.set_status("Idle", "idle") self.set_status("Idle", "idle")
self._log(f"[POLL] Started interval={interval}ms") self._log(f"[POLL] Started interval={interval}ms")
self._set_ui_busy(False) # Обновить доступность кнопок self._set_ui_busy(False) # Обновить доступность кнопок
def _on_poll_timeout(self): def _on_poll_timeout(self):
@@ -1063,6 +1067,7 @@ class DebugTerminalWidget(QtWidgets.QWidget):
# Immediately kick off the first variable read of the first cycle # Immediately kick off the first variable read of the first cycle
self._start_ll_cycle() self._start_ll_cycle()
self._set_ui_busy(False) # Обновить доступность кнопок
def _on_ll_poll_timeout(self): def _on_ll_poll_timeout(self):
@@ -1097,13 +1102,18 @@ class DebugTerminalWidget(QtWidgets.QWidget):
frame = self._build_lowlevel_request(var_info) frame = self._build_lowlevel_request(var_info)
# --- НОВОЕ: Передаем var_info в метаданные транзакции для LL polling --- # --- НОВОЕ: Передаем var_info в метаданные транзакции для LL polling ---
meta = {'lowlevel': True, 'll_polling': True, 'll_var_info': var_info} meta = {'lowlevel': True, 'll_polling': True, 'll_var_info': var_info}
self.set_status(f"Polling LL: {var_info.get('name')}", "values") # Получаем адрес переменной, предполагаем что ключ называется '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) self._enqueue_raw(frame, meta)
else: else:
# Цикл завершен, перезапускаем таймер для следующего полного цикла # Цикл завершен, перезапускаем таймер для следующего полного цикла
self._ll_poll_index = 0 self.ll_selector._populate_var_table()
self._ll_poll_timer.start(self.ll_selector.spin_interval.value())
self.set_status("LL polling cycle done, waiting...", "idle")
# ------------------------------ HELPERS -------------------------------- # ------------------------------ HELPERS --------------------------------
def _toggle_index_base(self, st): def _toggle_index_base(self, st):
# ... (код без изменений) # ... (код без изменений)
@@ -1180,7 +1190,7 @@ class DebugTerminalWidget(QtWidgets.QWidget):
self._csv_logging_active = True self._csv_logging_active = True
self.btn_start_csv_logging.setEnabled(False) self.btn_start_csv_logging.setEnabled(False)
self.btn_stop_csv_logging.setEnabled(True) 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 началась.") self._log("[CSV] Запись данных в CSV началась.")
@@ -1189,7 +1199,7 @@ class DebugTerminalWidget(QtWidgets.QWidget):
self._csv_logging_active = False self._csv_logging_active = False
self.btn_start_csv_logging.setEnabled(True) self.btn_start_csv_logging.setEnabled(True)
self.btn_stop_csv_logging.setEnabled(False) 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 остановлена.") self._log("[CSV] Запись данных в CSV остановлена.")
def _save_csv_data(self): def _save_csv_data(self):
@@ -1199,7 +1209,7 @@ class DebugTerminalWidget(QtWidgets.QWidget):
self.set_status("Stop logging first", "error") self.set_status("Stop logging first", "error")
return return
self.csv_logger.write_to_csv() 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): def _log(self, msg: str):
# ... (код без изменений) # ... (код без изменений)
@@ -1235,6 +1245,10 @@ class DebugTerminalWidget(QtWidgets.QWidget):
class _DemoWindow(QtWidgets.QMainWindow): class _DemoWindow(QtWidgets.QMainWindow):
def __init__(self): def __init__(self):
super().__init__() 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.setWindowTitle("DebugVar Terminal")
self.term = DebugTerminalWidget(self) self.term = DebugTerminalWidget(self)
self.setCentralWidget(self.term) self.setCentralWidget(self.term)

View File

@@ -206,7 +206,7 @@ class VariableSelectorDialog(QDialog):
'enable': 'true', 'enable': 'true',
'shortname': name, 'shortname': name,
'pt_type': '', 'pt_type': '',
'iq_type': '', 'iq_type': 't_iq_none',
'return_type': 't_iq_none', 'return_type': 't_iq_none',
'file': file_val, 'file': file_val,
'extern': str(extern_val).lower() if extern_val else 'false', '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) super().__init__(parent)
self.setWindowTitle(title) self.setWindowTitle(title)
self.setFixedSize(320, 120) # Задаем фиксированный размер для аккуратного вида self.setFixedSize(320, 120) # Задаем фиксированный размер для аккуратного вида
@@ -74,7 +75,7 @@ class SetSizeDialog(QDialog):
# Макет для ввода значения # Макет для ввода значения
input_layout = QHBoxLayout() input_layout = QHBoxLayout()
label = QLabel("Количество символов:", self) label = QLabel(label_text, self)
self.spin_box = QSpinBox(self) self.spin_box = QSpinBox(self)
self.spin_box.setRange(min_value, max_value) # Устанавливаем диапазон допустимых значений self.spin_box.setRange(min_value, max_value) # Устанавливаем диапазон допустимых значений
@@ -155,6 +156,9 @@ class VariableTableWidget(QTableWidget):
shortsize = self.settings.value("shortname_size", True, type=int) shortsize = self.settings.value("shortname_size", True, type=int)
self._shortname_size = shortsize self._shortname_size = shortsize
if(self._show_value):
self._shortname_size = 3
self.type_options = list(dict.fromkeys(type_map.values())) self.type_options = list(dict.fromkeys(type_map.values()))
self.pt_types_all = [t.replace('pt_', '') for t in self.type_options] 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)] self.iq_types_all = ['iq_none', 'iq'] + [f'iq{i}' for i in range(1, 31)]
@@ -262,11 +266,25 @@ class VariableTableWidget(QTableWidget):
self.setCellWidget(row, rows.ret_type, ret_combo) self.setCellWidget(row, rows.ret_type, ret_combo)
# Последний столбец # Последний столбец
if self._show_value:
if self._show_value: if self._show_value:
val = var.get('value', '') val = var.get('value', '')
if val is None: if val is None:
val = '' val = ''
val_edit = QLineEdit(str(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.textChanged.connect(on_change_callback)
val_edit.setStyleSheet(style_with_padding) val_edit.setStyleSheet(style_with_padding)
self.setCellWidget(row, rows.short_name, val_edit) self.setCellWidget(row, rows.short_name, val_edit)
@@ -310,6 +328,7 @@ class VariableTableWidget(QTableWidget):
color = error_color color = error_color
tooltip = tooltip_missing tooltip = tooltip_missing
elif long_shortname: elif long_shortname:
if not self._show_value:
color = warning_color color = warning_color
tooltip = tooltip_shortname tooltip = tooltip_shortname
@@ -365,9 +384,14 @@ class VariableTableWidget(QTableWidget):
self.update_comboboxes({rows.ret_type: self._ret_type_filter}) self.update_comboboxes({rows.ret_type: self._ret_type_filter})
elif logicalIndex == rows.short_name: elif logicalIndex == rows.short_name:
if self._show_value:
dlg = SetSizeDialog(self, title="Укажите точность", label_text="Кол-во знаков после запятой", initial_value=3)
else:
dlg = SetSizeDialog(self) dlg = SetSizeDialog(self)
if dlg.exec_(): if dlg.exec_():
self._shortname_size = dlg.get_selected_size() self._shortname_size = dlg.get_selected_size()
if not self._show_value:
self.settings.setValue("shortname_size", self._shortname_size) self.settings.setValue("shortname_size", self._shortname_size)
self.check() self.check()

View File

@@ -1,17 +1,29 @@
# pyinstaller --onefile --distpath . --workpath ./build --specpath ./build 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=./build 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.etree.ElementTree as ET
import xml.dom.minidom import xml.dom.minidom
import sys import sys
import os import os
if len(sys.argv) < 3: 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) sys.exit(1)
input_path = sys.argv[1] input_path = sys.argv[1]
info_path = sys.argv[2] info_path = sys.argv[2]
base_type_sizes = {
"char": 2,
"short": 2,
"int": 2,
"long": 4,
"long long": 8,
"float": 4,
"double": 8,
}
if len(sys.argv) >= 4: if len(sys.argv) >= 4:
output_path = sys.argv[3] output_path = sys.argv[3]
else: else:
@@ -42,13 +54,25 @@ def get_attr(die, attr_type):
return None return None
def get_die_size(die): def get_die_size(die):
"""Вернуть размер DIE в байтах из атрибута DW_AT_byte_size.""" """Вернуть размер DIE в байтах из атрибута DW_AT_byte_size или по ключевым словам имени типа."""
# Сначала пытаемся получить размер из DW_AT_byte_size
for attr in die.findall("attribute"): for attr in die.findall("attribute"):
type_elem = attr.find("type") type_elem = attr.find("type")
if type_elem is not None and type_elem.text == "DW_AT_byte_size": if type_elem is not None and type_elem.text == "DW_AT_byte_size":
const_elem = attr.find("value/const") const_elem = attr.find("value/const")
if const_elem is not None: if const_elem is not None:
return int(const_elem.text, 0) return int(const_elem.text, 0)
# Если не нашли, пробуем определить размер по ключевым словам в имени типа
name_elem = die.find("attribute[@name='DW_AT_name']/value/const")
if name_elem is not None:
type_name = name_elem.text.lower()
for key, size in base_type_sizes.items():
if key in type_name:
return size
return None return None
def resolve_type_die(type_id): def resolve_type_die(type_id):
@@ -136,48 +160,100 @@ def parse_offset(offset_text):
return 0 return 0
def get_array_dimensions(array_die):
"""Рекурсивно получить размеры всех измерений массива из DIE с тегом DW_TAG_array_type."""
dims = []
# Ищем размер текущего измерения def get_base_type_die(array_die):
# Размер может быть в DW_AT_upper_bound, либо вычисляться из DW_AT_byte_size и типа элемента """Спускаемся по цепочке DW_AT_type, пока не дойдем до не-массива (базового типа)."""
# Но часто в DWARF размер указывается через дочерние die с тегом DW_TAG_subrange_type current_die = array_die
while True:
subrange = None ref = get_attr(current_die, "DW_AT_type")
for child in array_die.findall("die"): if ref is None or ref.find("ref") is None:
if child.findtext("tag") == "DW_TAG_subrange_type":
subrange = child
break break
next_die = resolve_type_die(ref.find("ref").attrib.get("idref"))
if next_die is None:
break
if next_die.findtext("tag") == "DW_TAG_array_type":
current_die = next_die
else:
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 dim_size = None
if subrange is not None: ub_attr = get_attr(child, "DW_AT_upper_bound")
# Ищем атрибут DW_AT_upper_bound
ub_attr = get_attr(subrange, "DW_AT_upper_bound")
if ub_attr is not None: if ub_attr is not None:
val = ub_attr.find("value/const") # Попробуем разные варианты получить значение upper_bound
if val is not None: # 1) value/const
# Размер измерения равен верхней границе + 1 (т.к. верхняя граница индексируется с 0) val_const = ub_attr.find("const")
dim_size = int(val.text, 0) + 1 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: if dim_size is None:
# Если размер не нашли, попробуем вычислить через общий размер / размер элемента # fallback по DW_AT_count — редко встречается
arr_size = get_die_size(array_die) ct_attr = get_attr(child, "DW_AT_count")
element_type_ref = get_attr(array_die, "DW_AT_type") if ct_attr is not None:
if element_type_ref is not None and element_type_ref.find("ref") is not None: val_const = ct_attr.find("value/const")
element_type_id = element_type_ref.find("ref").attrib.get("idref") if val_const is not None:
element_type_die = resolve_type_die(element_type_id) try:
elem_size = get_die_size(element_type_die) if element_type_die is not None else None dim_size = int(val_const.text, 0)
#print(f"[DEBUG] Found DW_AT_count: {dim_size}")
if arr_size is not None and elem_size: except Exception as e:
dim_size = arr_size // elem_size a=1#print(f"[WARN] Error parsing DW_AT_count const: {e}")
if dim_size is None: if dim_size is None:
dim_size = 0 # Неизвестно print("[DEBUG] No dimension size found for this subrange, defaulting to 0")
dim_size = 0
dims.append(dim_size) 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") 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: 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_id = element_type_ref.find("ref").attrib.get("idref")
@@ -185,63 +261,69 @@ def get_array_dimensions(array_die):
if element_type_die is not None and element_type_die.findtext("tag") == "DW_TAG_array_type": 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)) dims.extend(get_array_dimensions(element_type_die))
#print(f"[DEBUG] Array dimensions: {dims}")
return dims return dims
def handle_array_type(member_elem, resolved_type, offset=0): def handle_array_type(member_elem, resolved_type, offset=0):
dims = get_array_dimensions(resolved_type) dims = get_array_dimensions(resolved_type)
# Получаем элементарный тип массива (наибольший элемент в цепочке массивов) base_die = get_base_type_die(resolved_type)
def get_base_element_type(die): base_name = "unknown"
ref = get_attr(die, "DW_AT_type") base_size = None
if ref is not None and ref.find("ref") is not None: if base_die is not None:
type_id = ref.find("ref").attrib.get("idref") base_id = base_die.attrib.get("id")
type_die = resolve_type_die(type_id) if base_id:
if type_die is not None and type_die.findtext("tag") == "DW_TAG_array_type": base_name = get_type_name(base_id)
return get_base_element_type(type_die) base_size = get_die_size(base_die)
else: else:
return type_die base_name = get_type_name(base_die.attrib.get("id", ""))
return None #print(f"[DEBUG] Base type name: {base_name}, base size: {base_size}")
element_type_die = get_base_element_type(resolved_type) member_elem.set("type", base_name + "[]" * len(dims))
element_type_name = get_type_name(element_type_die.attrib.get("id")) if element_type_die is not None else "unknown"
# Формируем строку типа с нужным количеством [] if base_size is None:
type_with_array = element_type_name + "[]" * len(dims) base_size = 0
member_elem.set("type", type_with_array)
# Размер всего массива total_elements = 1
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(base_size if base_size is not None else 1))
else:
arr_size = get_die_size(resolved_type) arr_size = get_die_size(resolved_type)
if arr_size is not None: if arr_size:
member_elem.set("size", str(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): for i, dim in enumerate(dims, 1):
member_elem.set(f"size{i}", str(dim)) member_elem.set(f"size{i}", str(dim))
#print(f"[DEBUG] Setting size{i} = {dim}")
member_elem.set("kind", "array") member_elem.set("kind", "array")
# Если базовый элемент - структура, рекурсивно добавляем её члены if base_die is not None and base_die.findtext("tag") == "DW_TAG_structure_type":
if element_type_die is not None and element_type_die.findtext("tag") == "DW_TAG_structure_type": add_members_recursive(member_elem, base_die, offset)
add_members_recursive(member_elem, element_type_die, offset)
def add_members_recursive(parent_elem, struct_die, base_offset=0): def add_members_recursive(parent_elem, struct_die, base_offset=0):
tag = struct_die.findtext("tag") is_union = struct_die.findtext("tag") == "DW_TAG_union_type"
is_union = tag == "DW_TAG_union_type"
# Получаем размер структуры/объединения
size = get_die_size(struct_die) size = get_die_size(struct_die)
if size is not None: if size is not None:
parent_elem.set("size", hex(size)) parent_elem.set("size", hex(size))
for member in struct_die.findall("die"): for member in struct_die.findall("die"):
if member.findtext("tag") != "DW_TAG_member": if member.findtext("tag") != "DW_TAG_member":
continue continue
@@ -249,39 +331,41 @@ def add_members_recursive(parent_elem, struct_die, base_offset=0):
name_attr = get_attr(member, "DW_AT_name") name_attr = get_attr(member, "DW_AT_name")
offset_attr = get_attr(member, "DW_AT_data_member_location") offset_attr = get_attr(member, "DW_AT_data_member_location")
type_attr = get_attr(member, "DW_AT_type") type_attr = get_attr(member, "DW_AT_type")
if name_attr is None or offset_attr is None or type_attr is None: if name_attr is None or offset_attr is None or type_attr is None:
continue continue
name = name_attr.findtext("string") name = name_attr.findtext("string")
offset_text = offset_attr.findtext("block") offset = parse_offset(offset_attr.findtext("block")) + base_offset
offset = parse_offset(offset_text) + base_offset
type_id = type_attr.find("ref").attrib.get("idref") type_id = type_attr.find("ref").attrib.get("idref")
resolved_type = resolve_type_die(type_id) resolved_type = resolve_type_die(type_id)
type_name = get_type_name(type_id) type_name = get_type_name(type_id)
if type_name == "unknown": if type_name == "unknown":
continue continue
member_elem = ET.SubElement( member_elem = ET.SubElement(parent_elem, "member", name=name, offset=hex(offset), type=type_name)
parent_elem, "member", name=name, offset=hex(offset), type=type_name
)
if is_union: if is_union:
member_elem.set("kind", "union") member_elem.set("kind", "union")
if resolved_type is not None: if resolved_type is not None:
subtag = resolved_type.findtext("tag") tag = resolved_type.findtext("tag")
if tag == "DW_TAG_array_type":
# Обработка массива
if subtag == "DW_TAG_array_type":
handle_array_type(member_elem, resolved_type, offset) handle_array_type(member_elem, resolved_type, offset)
# Обработка структур и объединений elif tag in ("DW_TAG_structure_type", "DW_TAG_union_type"):
elif subtag in ("DW_TAG_structure_type", "DW_TAG_union_type"):
member_elem.set("type", type_name) member_elem.set("type", type_name)
add_members_recursive(member_elem, resolved_type, offset) add_members_recursive(member_elem, resolved_type, offset)
else: elif tag == "DW_TAG_pointer_type":
member_elem.set("type", type_name) # Проверяем тип, на который указывает указатель
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") output_root = ET.Element("variables")
@@ -292,13 +376,10 @@ for die in root.iter("die"):
name_attr = get_attr(die, "DW_AT_name") name_attr = get_attr(die, "DW_AT_name")
addr_attr = get_attr(die, "DW_AT_location") addr_attr = get_attr(die, "DW_AT_location")
type_attr = get_attr(die, "DW_AT_type") type_attr = get_attr(die, "DW_AT_type")
if name_attr is None or addr_attr is None or type_attr is None: if name_attr is None or addr_attr is None or type_attr is None:
continue continue
name = name_attr.findtext("string") name = name_attr.findtext("string")
# Пропускаем переменные с '$' в имени
if "$" in name: if "$" in name:
continue continue
@@ -310,40 +391,24 @@ for die in root.iter("die"):
type_id = type_attr.find("ref").attrib.get("idref") type_id = type_attr.find("ref").attrib.get("idref")
resolved_type = resolve_type_die(type_id) resolved_type = resolve_type_die(type_id)
type_name = get_type_name(type_id) type_name = get_type_name(type_id)
# Пропускаем переменные, находящиеся в памяти периферии if 0x800 <= addr < 0x8000 or type_name == "unknown":
if 0x800 <= addr < 0x8000:
continue
# Проверка на DW_TAG_subroutine_type - пропускаем такие переменные
if type_name == "unknown":
continue continue
var_elem = ET.SubElement(output_root, "variable", name=name, address=hex(addr), type=type_name) var_elem = ET.SubElement(output_root, "variable", name=name, address=hex(addr), type=type_name)
if resolved_type is not None: if resolved_type is not None:
tag = resolved_type.findtext("tag") tag = resolved_type.findtext("tag")
if tag == "DW_TAG_array_type": if tag == "DW_TAG_array_type":
handle_array_type(var_elem, resolved_type) handle_array_type(var_elem, resolved_type)
elif tag in ("DW_TAG_structure_type", "DW_TAG_union_type"): elif tag in ("DW_TAG_structure_type", "DW_TAG_union_type"):
add_members_recursive(var_elem, resolved_type) add_members_recursive(var_elem, resolved_type)
timestamp = extract_timestamp(info_path) timestamp = extract_timestamp(info_path)
# Создаём новый элемент <timestamp> с текстом timestamp
timestamp_elem = ET.Element("timestamp") timestamp_elem = ET.Element("timestamp")
timestamp_elem.text = timestamp timestamp_elem.text = timestamp
output_root.insert(0, timestamp_elem)
# Вставляем тег timestamp в начало (или куда хочешь)
output_root.insert(0, timestamp_elem) # В начало списка дочерних элементов
# Красивый вывод
rough_string = ET.tostring(output_root, encoding="utf-8") rough_string = ET.tostring(output_root, encoding="utf-8")
reparsed = xml.dom.minidom.parseString(rough_string) pretty_xml = xml.dom.minidom.parseString(rough_string).toprettyxml(indent=" ")
pretty_xml = reparsed.toprettyxml(indent=" ")
with open(output_path, "w", encoding="utf-8") as f: with open(output_path, "w", encoding="utf-8") as f:
f.write(pretty_xml) f.write(pretty_xml)

Binary file not shown.