Files
Sprinter-SDCC/docs/mame-source-debug.md
T
2026-09-16 10:01:43 +03:00

820 lines
72 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Отладка C-приложений Sprinter в MAME и VS Code
Редакция: **2026-09-14**, после технического ревью плана от 2026-09-13.
Статус: **ЧАСТИЧНАЯ РЕАЛИЗАЦИЯ**. Сборка/оффлайновая карта, транспорт,
live C-сессия, постоянный server, DAP и VS Code launch MVP реализованы; результаты —
[mame-source-debug-status.md](mame-source-debug-status.md).
Полный lifecycle/restart и расширенная отладка ещё не реализованы.
Основной маршрут включает CLI/MCP и VS Code через DAP;
GDB — самостоятельное расширение по отдельной потребности.
### Статус платформ
- **macOS** — полный текущий цикл build → DSS → MAME → DAP → VS Code
проверен живыми запусками. Доступны backend `sdbg` и совместный режим
VS Code + штатное окно debugger через `debugger: "osx"`.
- **Linux** — архитектура и используемые host-механизмы совместимы; для
штатного окна MAME выбирается `qt` либо `imgui`. Полный живой прогон на
Linux ещё требуется, поэтому статус остаётся экспериментальным.
- **Windows** — полный функционал пока **не поддерживается**. Значение
`debugger: "windows"` выбирает только штатное окно debugger MAME, но
текущие owner lock (`fcntl`), Unix domain socket, shell-команды запуска и
тесты рассчитаны на macOS/Linux. Нужны Windows-реализации блокировки и
локального RPC, переносимый launcher и отдельные end-to-end тесты. До
этого launch/attach из VS Code на native Windows не считаются рабочими.
Прежние фазы 0–5A/5B заменены этапами §10. IDE учитывается в архитектуре
с самого начала; прежняя «опциональная фаза 4» теперь распределена между
этапами IDE и расширенной диагностики. Это изменение состава плана,
а не разрешение выполнять ранее отложенную реализацию.
## 1. Цель и критерий доверия
Целевой цикл: редактирование C → сборка sprinter-cc → упаковка приложения
и данных → запуск MAME/DSS → остановка на main → отладка из VS Code,
CLI или MCP.
Планируемые возможности:
- Точки по функции/строке C, условия, счётчики попаданий, логпоинты без
пересборки, watchpoint на поддержанные объекты.
- Текущий исходник, сгенерированный `.asm`, реальные инструкции, регистры.
- Чтение/изменение глобалов и статиков с учётом типов, банков и окон.
- Шаг по инструкции и исходнику; затем next/stepOut, проверенные локальные
переменные и восстановление стека в поддержанных случаях.
- Структурированные логи для автотестов/IDE, комментарии в родном
дизассемблере; позднее покрытие и профилирование.
- Общая семантика адресов и одна сессия для всех интерфейсов.
**Критерий доверия:** неизвестная строка, неподтверждённая точка или
недоступная переменная лучше правдоподобного неверного результата.
Карта оптимизированного кода не обещает отдельную инструкцию для каждого
оператора C и наличие всех переменных в любой момент исполнения.
Связанные файлы: [mame-autotest.md](mame-autotest.md),
[sprinter-cc](../bin/sprinter-cc), [app.mk](../app.mk),
[mame_interactive.py](../toolchain/mame_interactive.py),
[bank.s](../runtime/bank.s), [crt0_banked.s](../runtime/crt0_banked.s).
MAME fork теперь самостоятельный проект: `MAME/plugins/mamebridge/init.lua`,
`MAME/src/mame_mcp.py`; среда запуска задаётся `MAME_HOME`, а исходники
fork приложению не нужны. Расширение VS Code находится в отдельном
`VSCode-Sprinter`; Python DAP backend остаётся в SDK.
## 2. Доказательства и пределы выводов
### 2.1 Результаты первоначальной разведки
Ниже сохранены результаты редакции 2026-09-13. При обновлении плана
сборочные эксперименты не повторялись. Перед реализацией их требуется
воспроизвести с сохранением репро, команд и версий.
| Эксперимент | Зафиксированный результат | Предел вывода |
|---|---|---|
| `sdcc --debug`, probe | `.exe` идентичен; `_CODE` 3354 → 3354 | Один пример, не гарантия для всех программ |
| `tests/banked`, huge, 3 TU, 2 банка | `.ihx` идентичен сборке без debug | rc=1 из-за C$; корректность карты не доказана |
| `.globl _dbg_x` и `_dbg_x:` в inline asm | Ошибка локальных меток `NNNNN$` в проверенных циклах/ветвлениях | Такую форму якоря не используем |
| `.globl _dbg_x` и `_dbg_x = .` | Релоцируемый символ; в probe байты идентичны, якорь у `ret` | Отдельно проверить влияние inline asm на оптимизацию |
| `;;SDBG x` | Код идентичен, адреса в `.map` нет | Для адреса нужен листинг/дополнительная привязка |
| SDCC 4.5.0 z80, `--out-fmt-elf` | Неизвестная опция | Готового ELF/DWARF этого target ожидать нельзя |
SDCC сам использует `sym = .`. Это подтверждает пригодность ассемблерной
формы, но не доказывает отсутствие влияния пользовательского inline asm
на оптимизатор C и peephole. Раздельно проверяем идентичность бинарника,
точность карты и скорость программы под debugger.
Примеры `.cdb` из первоначальной разведки:
```text
L:C$probe.c$18$3_0$48:8236 C-строка → адрес линковщика
L:A$probe$111:8236 строка .asm → адрес
L:G$main$0$0:8222 начало функции
L:Fprobe$counter$0_0$0:8E40 статик модуля
L:G$total$0_0$0:8E42 глобал
S:G$total$0_0$0({2}SI:S),E,0,0 описание типа
S:Lprobe.add$s$1_0$44({2}SI:S),R,0,0,[e,d] описание регистровой локальной
```
Парсер разбирает семейства `L:`, `S:`, `F:` и диагностирует неизвестные
записи. Построчная форма не делает семантику типов, scope, инлайнинга
и границ функций тривиальной. Семантику конечных адресов установить репро,
внутренние диапазоны нормализовать к `[start, end)`.
### 2.2 Проверено чтением текущих локальных исходников
- MAME поддерживает breakpoint с condition/action, printf/logerror/tracelog,
source/debugscript, comadd и debugger без окна (`-debugger none`).
`none` автоматически вызывает go() при остановке: он пригоден для
автономных logpoint actions, но не для ожидания интерактивных команд.
Подтверждено исходниками и живым репро; прототип использует osx debugger.
- `device_debug::compute_opcode_crc32` в `src/emu/debug/debugcpu.cpp`
считает CRC **одной инструкции**. Одинаковый `ret` по одному адресу
в двух банках имеет одинаковый ключ комментария. CRC не определяет банк.
- `execute_trace` в `src/emu/debug/debugcmd.cpp` включает трассировку CPU,
а не просто открывает файл для сообщений.
- Мост содержит `clog` и обслуживает `register_periodic` при stopped.
Комментарий у `do_step` прямо указывает: инструкция выполняется после
возврата callback, поэтому чтение PC сразу после step даст старый PC.
- `emu.symbol_table` в `luaengine_debug.cpp` создаёт отдельную таблицу;
готового symadd для консоли нет. C-имена разрешаем на своей стороне.
- В штатных debug views нет окна C-исходника. Для родного UI используем
комментарии, для полноценного исходника — IDE.
- sprinter-cc размещает банки по `(bank << 16) | 0xC000` в huge и
`(bank << 16) | 0x4000` в big. Есть `--bank-data`: банки содержат и данные.
- `_bank_pages[1..N]` заполняется crt0 при загрузке банков, а не до entry.
Драйвер экспортирует PG0..PG3 и другие состояния отображения.
- `bootstrap_r/w` в sprinter.cpp при обычном исполнении перенаправляют
логический адрес в `0x10000 | addr`; загрузочный режим отличается.
Адрес CPU и адрес пространства MAME нельзя смешивать.
### 2.3 Пакет доказательств этапа 0
Сохранить небольшие исходники-репро, команды, полные диагностики,
версии SDCC/ассемблера/линкера, commit MAME и патчи, хеши бинарников,
выбранные фрагменты `.asm/.map/.cdb`, протокол живой проверки MAME.
Большие артефакты допускается хранить вне Git с командой воспроизведения.
Ссылки на меняющиеся номера строк MAME заменять именами функций и
закреплённой ревизией в отчёте эксперимента.
## 3. Реестр решений: что заменено и почему
| ID | Риск / прежнее предложение | Решение и обоснование |
|---|---|---|
| R01 | Игнорировать rc=1 при C$, если есть `.ihx` | Устранить конфликт debug-имён; исправность кода не доказывает карту, старый `.ihx` может пережить ошибку |
| R02 | «Полная карта, ноль влияния» | Раздельные регрессии бинарника, карты и overhead; гарантия ограничена проверенными конфигурациями |
| R03 | Адрес — одно число, банки только у кода | Типизированные адреса и snapshot отображения, включая bank-data |
| R04 | Первый адрес строки / следующая строка вместо якоря | Все доказанные позиции, фактическое разрешение и unverified; другой путь исполнения не заменяет удалённую точку |
| R05 | Арминг по совпадению PC с entry | Проверка образа, готовности runtime/банков и выхода; DSS использует те же адреса |
| R06 | Цикл step внутри Lua | Асинхронный автомат с лимитами/отменой: шаг исполняется после callback |
| R07 | Разные ID файлов достаточно для нескольких клиентов | Общая сессия и арбитраж; ID не устраняют гонки run/stop |
| R08 | Безусловный `g` в логпоинте, clear-all | Реестр владельцев и диспетчер попаданий; логи не отменяют остановку |
| R09 | `trace` по умолчанию для логов | Отдельный журнал; instruction trace имеет другое назначение и стоимость |
| R10 | Формат C printf можно передать MAME | Ограниченная грамматика, типы, знак, длина строк и CP866; форматы различаются |
| R11 | CRC скрывает чужой банк | Сначала резидентные комментарии; затем обновление при remap или расширение ключа |
| R12 | Каталог сборки достаточен для attach | Manifest, хеши и фиксированный пакет сессии; исключить смешение сборок |
| R13 | Все DAP-запросы сразу, стек эвристикой | Честные capabilities и один достоверный frame в MVP; сложные функции отдельно |
| R14 | GDB не имеет логов/банков | Учесть dprintf/overlays; DAP выбран за модель Sprinter и общую сессию |
| R15 | Сокет автоматически ускорит шаг | Сначала измерить RTT и execution latency; callback сокетом не исправляется |
| R16 | Ручные правки в игнорируемом mame/ | Версионируемые исходники/патчи и установщик с проверкой расхождений |
| R17 | Библиотеки/локальные автоматически следуют из CDB | Эксперимент архивной линковки и доказанные location ranges |
| R18 | Один launch.json завершает интеграцию | Сборка, диагностики, язык, упаковка данных, запуск DSS и повторный F5 |
| R19 | debugger none держит stopped для IDE | В none wait_for_debugger вызывает go(); использовать родной debugger, для режима без окон реализовать отдельный backend ожидания |
| R20 | `debugger: "windows"` означает поддержку всей цепочки на Windows | Разделить MAME provider и host-инструменты: Windows provider существует, но Python/DAP launch/attach остаются неподдержанными до замены `fcntl`/Unix sockets, переноса launcher и живых тестов |
| R21 | Короткий sleep в `sdbg` достаточно для окна MAME | При stopped CPU обычный frame loop не обрабатывает события GUI, и macOS помечает приложение «не отвечает». В `wait_for_debugger` периодически вызывать event pump выбранного OSD (SDL3: `input_update` + `process_events`, native macOS: штатный poll), ограничив частоту; проверить Cmd-Tab/Dock при длительной остановке и сохранение DAP/клавиатурного ввода |
| R22 | CDB pointee type достаточен для выбора строки или скаляра | SDCC 4.5 кодирует проверенные `char *` и `uint8_t *` одинаково (`DG,SC:U`). В финальном debug-пакете сохранять исходный declared type/typedef chain, сверять его с CDB и размером; при неопределённости явно `unavailable` либо запросить типовую аннотацию. Отдельный fixture должен доказать, что `char *` читается как строка, а `int8_t *`/`uint8_t *` — как один 8-битный объект |
## 4. Архитектура, сборка и пакет
```text
sprinter-cc → .exe + отладочный пакет + manifest
sdbg: карта и типы
CLI ────────┐ │
MCP ────────┼──→ общая сессия отладки ──→ мост MAME ──→ CPU/debugger
VS Code/DAP ┘ состояние, точки,
события, загрузка
```
### 4.1 Ответственность компонентов
- `toolchain/sdbg.py` — публичный CLI; реализацию разделить на модули
пакета/парсера, адресов, типов, точек, сессии и транспорта по мере роста.
- Карта — чистые преобразования артефактов без команд MAME.
- Общая сессия — процесс для подключения CLI/MCP/DAP, хранит build ID,
generation, состояние, владельца управления и реестр точек.
- Мост — низкоуровневые действия, согласованные снимки, асинхронные
операции и события; второго парсера CDB в Lua нет.
- DAP/MCP — тонкие адаптеры; существующие input/screenshots сохраняются,
команды изменения исполнения проходят арбитраж.
- `.dbgs` — ограниченный автономный экспорт, не второй менеджер сессии.
Неподдержанную семантику экспорт отклоняет с объяснением.
### 4.2 Флаги и manifest
`--src-debug` включает debug для всех пользовательских TU;
повторяемый `--src-debug-file FILE` — для выбранных. Режимы взаимоисключающие,
неизвестный FILE — ошибка. Необязательный аргумент прежнего флага убран,
поскольку он неоднозначен рядом с позиционными `.c`.
`--debug` остаётся DEBUG_RT. В app.mk: `SRC_DEBUG := 1` либо
`SRC_DEBUG_FILES := a.c b.c`. Обычные сборки прежние; профиль IDE явно
включает карту, а не меняет defaults всего проекта.
Пакет в `.sprinter-cc-<name>/`: `.cdb/.map/.noi/.ihx/.asm`, нужные листинги,
manifest и индекс `.sdbg.json`. Конфигурация и содержимое входов входят
в ключ пересборки: смена debug/fast/safe/memory не оставляет stale artifacts.
Параллельные сборки одного output сериализуются или используют разные
каталоги. Пакет публикуется только после успешных проверок, атомарно.
Manifest: schema version, build ID, хеш `.exe` и артефактов, версии
инструментов, команды/флаги, библиотеки, TU и исходники/заголовки с хешами,
соответствующие `.asm`, entry, секции, окна, банки и полнота debug-покрытия.
Адрес `>=0x10000` не объявляется банком без проверки секции.
Неподдержанное размещение диагностируется, не угадывается по имени режима.
Пути относительно корня сборки, уникальный TU ID, поддержка одинаковых
basename и source path mapping при переносе проекта. IDE проверяет хеши;
при расхождении показывает stale source или сохранённый снимок через DAP
source. Сессия фиксирует пакет: новая сборка не меняет карту старого образа.
### 4.3 Ошибки CDB
Основное решение R01: проверить уникализацию отладочных имён по TU до
линковки в `.asm` с согласованным преобразованием всех связанных CDB-записей.
Если формат не позволяет надёжный внешний проход, подготовить патч SDCC.
Не изменять публичные C/asm-символы и инструкции. Выбор способа — результат
репро этапа 0, а не утверждение, что простое переименование уже достаточно.
До исправления допустим пер-модульный режим только при успешной линковке;
manifest перечисляет отсутствующие TU. Неполный выбранный набор отличается
от повреждённой карты. Повреждённая карта не публикуется.
Автоматического превращения rc=1 в успех по наличию `.ihx` не будет.
Неуспешные артефакты можно сохранять для исследования отдельно, но обычный
attach их не принимает. Полные диагностики сохраняются, включая pipefail.
### 4.4 Библиотеки
Проверить извлечение `S/F/L` и путей из архивов libc/libbgi. После успеха
добавить явный режим debug-артефактов библиотек с отдельным ключом кэша,
сохранив fast/safe и «одна функция — один модуль». Проверить DCE, типы,
строки, отсутствие коллизий и идентичность кода. До этого библиотечный
код доступен как asm/символы без выдуманных C-строк.
## 5. Адреса, исходники, переменные и выражения
### 5.1 Адресная модель
`CodeLocation/DataLocation`: build ID, секция, адрес линковщика, logical
CPU address, bank ID приложения при наличии, окно и offset.
Физическая страница — свойство сессии, не константа пакета.
`MappingSnapshot`: generation остановки, PC/SP, регистры, PG0..PG3,
остальные нужные биты отображения, готовность `_bank_pages`.
```text
line_locations(source_id, line, function_id=None) → список CodeLocation
resolve_pc(pc, mapping_snapshot) → SourceLocation | Unknown
asm_at(code_location) → номер/текст asm и происхождение
function_at(code_location) → функция | Unknown
resolve_symbol(name, module_id=None) → Symbol | Ambiguous | Unknown
read_variable(symbol, snapshot) → TypedValue | Unavailable
```
Backend различает logical CPU memory, пространство MAME и physical RAM.
Преобразование `0x10000 | addr` инкапсулировано в драйверном backend.
Bootstrap/configuration имеет отдельную семантику и не допускает обычный
attach приложения.
Банковая точка проверяет страницу окна против `_bank_pages[N]` после
подтверждения таблицы и резидентного контекста приложения. huge — W3,
big — W1. Проверить достаточность PGn с учётом CNF и других битов драйвера;
простое равенство — базовый случай, не доказанный полный контракт.
Manual-размещения допускаются по фактической карте и поддержке backend.
### 5.2 Разрешение строк
Хранить все позиции строки и диапазоны инструкции/функции/секции.
Breakpoint строки по умолчанию покрывает все доказанные позиции исполнения;
дополнительно можно выбрать функцию/экземпляр. Resolver возвращает реальные
адреса, фактическую строку и причины неоднозначности/переноса.
Для пустой или удалённой строки можно предложить ближайшую позицию в том
же контексте, но не подтверждать её молча как точное совпадение.
Без допустимого соответствия — unverified с объяснением.
addr2line не распространяет предыдущую метку через конец функции, дыру,
секцию или банк. Inline-экземпляры и неоднозначности сохраняются, вместо
выбора первого TU. Привязка точного макроса рассматривается отдельно (§7.2).
### 5.3 Типы, память и watchpoint
MVP: доказанные целочисленные типы, указатели, глобалы/статики. Одинаковые
имена требуют квалификации модулем. Затем массивы, структуры, битовые поля
и другие типы с fixtures. Размеры, знак, byte order и ABI задаёт target
SDCC, не хост. Неподдержанный тип отображается raw bytes с диагностикой.
Регистры/память/mapping читаются в одной остановке, пакетные запросы
привязаны к generation. После resume старые handles и snapshots недействительны.
Запись требует stopped, актуальную generation, проверку диапазона/типа
и известного отображения. Порты показывать по экспортированному состоянию;
отладочные чтения отключают side effects там, где backend это поддерживает.
Неотображённый банк читается через проверенный доступ к physical RAM без
переключения страниц приложения; до реализации — Unavailable. Один
16-битный указатель не содержит достаточного bank ID: его нельзя выдумывать.
Чтение/запись через границу окна обрабатывается явно.
Для watchpoint экспериментом установить пространство/alias реального пути
CPU и момент остановки до/после записи. PC-источник, старое и новое значение
показывать лишь при достоверном получении: текущий PC может отличаться от
адреса записавшей инструкции. Банковый watchpoint учитывает mapping;
до доказательства не объявлять его поддержанным.
### 5.4 Выражения
Один парсер для condition/logMessage/evaluate/watches: имена, integer
literals, `$`-регистры, ограниченные арифметические/битовые/сравнительные
операции. Документировать грамматику, приоритеты, знаковость и ошибки.
Поля/индексы/разыменование добавлять по поддержке типов. По умолчанию
evaluate без присваиваний, вызовов C и других побочных эффектов.
Raw MAME expressions/commands — отдельный явно обозначенный режим;
это не C. Изменяющие команды также требуют управления и синхронизации.
Команды генерировать из проверенного AST с экранированием строк/путей,
не конкатенацией произвольного текста в action.
## 6. Общая сессия и управление исполнением
### 6.1 Жизненный цикл
```text
disconnected → waiting_load → verifying_image → runtime_initializing
→ stopped ↔ running
→ exited
reset / state load / потеря связи → invalidated → повторная проверка
```
Совпадение PC с entry — только кандидат загрузки. Подтверждение сочетает
build ID выбранного файла, протокол запуска и сравнение неизменяемых
участков RAM по карте. Хеш `.exe` на хосте сам по себе RAM не проверяет.
Изменяемые crt0 данные/таблицы исключаются; сигнатуры и точки проверки
определяются для конкретного runtime.
На entry активируются только доказанные резидентные точки. Банковые —
после загрузки и проверки `_bank_pages`. На main runtime должен быть готов.
Отладка crt0 — отдельный режим с asm и постепенным появлением областей.
Частичная ошибка загрузки не переводит сессию в ready.
Выход через runtime/ESTEX и возврат в DSS деактивируют точки приложения.
Проверить обычный/аварийный выход и обход штатного exit. Если контекст
невозможно уверенно распознать — invalidated, а не продолжение со старой картой.
Reset/state load/restart сбрасывают mapping, temporary points, handles,
generation. Attach к работающей программе сначала останавливает CPU,
проверяет образ и runtime; неизвестную сборку не принимает молча.
### 6.2 Управление и реестр точек
Один клиент владеет run/step/write/input; остальные наблюдают согласованные
данные. Передача управления явная. Потеря клиента снимает lease и отменяет
его незавершённые операции согласно политике сессии. Screenshots доступны
наблюдателям; ввод влияет на приложение и арбитрируется. Изменения run/stop
из родного UI MAME отражаются событиями.
Реестр: logical breakpoint ID, MAME IDs, owner, build ID/generation,
вид (user/log/temporary/service), адреса/условия. Clear-all ограничен owner.
Повторная загрузка файла точек заменяет его набор, не дублирует его.
Совпавшие точки обслуживает диспетчер: вычислить условия, записать логи,
собрать причины остановки, продолжить только если их нет. User breakpoint,
pause и ошибка шага имеют приоритет над автоматическим `g`.
Изменения точек в родном UI требуют сверки; если backend не может
гарантировать совместное управление, сообщить конфликт.
### 6.3 Протокол и транспорт
Protocol version/capabilities, session ID, request ID, generation,
ответы и упорядоченные события stop/run/output/reset/exit/error.
Ответ на установку шага значит «принят», не «CPU уже шагнул».
Нужны timeout/cancel, snapshot/varbatch, EOF/disconnect и защита от повторного
исполнения: write/continue после timeout не повторяется вслепую.
Сначала измерить файловый IPC: RTT, pause→snapshot, step→stopped,
periodic при running/stopped. Файловый backend: отдельный каталог сессии,
один писатель backend, атомарная публикация запросов/ответов, очистка stale
и журнал событий с курсором. Разделение диапазонов ID независимых MCP
заменяется одним владельцем backend: оно не решало гонки состояния.
Сокет реализует тот же протокол при подтверждённом выигрыше. MAME имеет
пример `emu.file` socket в plugins/gdbstub, но неблокирующий ввод/вывод
при stop проверяется отдельно. Framing, частичные сообщения, лимит очереди,
reconnect, loopback по умолчанию обязательны. Callback не ждёт клиента
блокирующим чтением. Сокет не исправляет задержку исполнения инструкции.
### 6.4 Шаги
Сначала надёжный instruction step. Source-step — асинхронный автомат:
задать действие, вернуть управление MAME, дождаться фактической остановки,
получить snapshot, решить следующий шаг. Для скорости применить temporary
breakpoints на доказанных границах либо C++ hook, если Lua periodic
недостаточен по измерениям.
StepIn идёт к следующей доступной позиции исходника с заходом в вызов.
Next обходит вызов только при распознанной семантике; stepOut требует
достоверного контекста возврата. Сравнивать source/TU/function/bank и
исполняемую позицию, не один номер строки. Повтор строки в цикле не должен
вечно ждать смены номера.
Составной шаг имеет предел инструкций/wall time, отмену и итоговую причину.
Код без исходников, HALT, ISR, рекурсия, tail call, bank trampolines,
BIOS rst 08 и ESTEX rst 10 имеют явную политику. Для неизвестного случая
допустим отказ или переход к asm с объяснением; stepIn не выдаётся за next.
Системный код пропускается только при безопасном способе дождаться возврата.
## 7. Логи, макросы и расширенная диагностика
### 7.1 Внешние точки и журнал
Основной путь — версионируемый внешний файл: ID, source/function/location,
condition, hit condition, сообщение и выражения. Тот же resolver и реестр,
что у VS Code; пересборка не требуется. Адреса пересчитываются для новой
сборки из сохранённой исходной привязки. DAP logMessage переводится в эту
модель, не образует отдельный движок.
Журнал: sequence, build/session ID, host time, доступное emulated time,
PC, bank/page, location, tag и типизированные значения. JSONL — автотестам,
текст — CLI, DAP output — IDE, курсор — MCP. Ограничить объём/частоту,
предусмотреть ротацию или bounded buffer, счётчик потерь, flush на stop/exit.
clog остаётся для сообщений MAME, но хвост консоли не считается полным
надёжным журналом приложения.
Форматирование — задача финального этапа §10, а не свойство текущего
`{name}`. Целевой ограниченный набор включает decimal/hex, ширину,
символ и строку; MAME `%d/%x/%X/%c/%s/%%` может быть форматом совместимого
экспорта, но не передаётся напрямую как C-макрос. Принято это разделение,
потому что formatter обязан проверять тип/знак, размер, pointer против
массива, границы и кодировку, а CDB не отличает `char *` от `uint8_t *`
(R22). Для строк нужны bounded read до NUL и проверка CP866→UTF-8;
raw bytes имеют отдельное представление. Значения собираются до resume.
Горячие логи могут исполняться в мосте по скомпилированному описанию;
стоимость измеряется отдельно.
Console/logerror допустимы для совместимого экспорта. Trace/tracelog —
явный режим instruction tracing, не default для логов. Экспорт `.dbgs`
указывает ограничения и не ставит безусловный resume в общей сессии.
### 7.2 SDBG_LOG и SDBG_LOGIF
Практический синтаксис, типы, ограничения и примеры описаны в отдельном
[руководстве по SDBG_LOG](sdbg-log-macros.md). Этот документ — источник
истины для пользовательского контракта макросов: при изменении грамматики,
типов, регистров, чтения указателей или вывода в MAME/DAP обновлять его
вместе с кодом и проверками, затем синхронизировать краткие описания здесь.
Первый поддержанный вариант реализован в `<sdbg.h>`: `SDBG_LOG(tag,
"total={total}")` и `SDBG_LOGIF(tag, flag, "total={total}")`.
Обычная сборка получает `((void)0)`; выбранный `--src-debug` TU —
символ `sym = .` без инструкции. `tag` обязан быть уникальным в TU,
условие пока только имя поддержанной global/static переменной, а
`{name}` соответствует ограниченной грамматике DAP logMessage.
Сборщик извлекает только активные вызовы из отдельного препроцессорного
metadata-прохода, проверяет единственный asm-якорь и linked address.
Автоматически установленный logpoint пишет в журнал debugger MAME и DAP
`output`, затем продолжает CPU; совпавшая обычная точка сохраняет остановку.
Событийный буфер DAP ограничен 1024 записями и сообщает разрыв курсора с
числом пропусков; overhead горячих macro-logpoints ещё требуется измерить.
Десятичный/hex formatter и чтение типизированного значения по указателю
отложены до финального этапа §10: сначала нужны доказанные тип, контекст
банка, границы и безопасное чтение памяти, иначе лог может показать неверный
объект. Текущий `{name}` выводит десятичное число и никогда не разыменовывает
указатель.
Если якорь не совпал с началом доказанной инструкции, он помечен unverified
и не активируется. В проверенном fixture EXE совпал побайтово с обычной
сборкой; это доказательство конкретного случая, не гарантия для всех
оптимизаций SDCC и inline asm.
Макросы остаются расширением для авторских устойчивых точек. Без
anchors — `((void)0)`, с anchors — символ `sym = .` без инструкции.
Аргументы логирования не исполняются приложением и не имеют C-побочных
эффектов; это явно документируется, чтобы counter++ не считался кодом C.
Точная точка требует найденного и проверенного якоря. Без него — missing/
unverified; приблизительная привязка выбирается отдельно с показом
фактической позиции. Якорь задаёт машинную границу перед инструкцией,
но не гарантирует материализацию локальной или порядок всех вычислений C.
ID включает TU и tag. Повторные/inline экземпляры имеют отдельные ID либо
отклоняются до линковки; уникальный tag в тексте программы не предотвращает
повторное разворачивание inline.
Реализованный препроцессорный проход учитывает активные #if, include,
wrappers, многострочные вызовы, комментарии и склейку литералов; связывает
SDBG-описания с единственным реально собранным якорем. Текущая грамматика
намеренно отклоняет вычисляемый tag/condition, нестроковое сообщение и
повторные tag; поддержку сложных C-выражений и локальных добавлять только
после доказанного location range и ограниченного парсера.
Приёмка сравнивает полные бинарники с/без anchors и без макроса на циклах,
ветках, inline и multi-TU. Если оптимизация меняется, внешние логпоинты
остаются путём без пересборки, anchors обозначаются инструментированным
режимом с измеренной дельтой. Без доказательства «ноль влияния» не обещать.
При добавлении sdbg.h обновить публичный справочник API.
### 7.3 Комментарии, стек, локальные, покрытие
Резидентные comadd ставятся после проверки образа и обновляются при смене
сборки. Банковые включаются после поддержки remap-обновления с удалением
старых либо патча ключа `(address, page/context, opcode CRC)`.
Оффлайновый cmt допустим для проверенного резидентного образа; CRC из ihx
не решает банковые коллизии. Учитывать владение пользовательскими комментариями.
Backtrace сначала даёт текущий достоверный frame. Затем поддержать
распознанные прологи/эпилоги, SDCC __sdcccall(1), callee-pops, IX,
trampolines и ISR. Сканирование стека на похожие адреса — отдельная
маркированная эвристика, не основание для stepOut/локальных. Возможная
альтернатива — история call/return, но attach посреди исполнения не знает
прошлого, а нестандартные переходы требуют инвалидирования истории.
Локальные доступны лишь при доказанном location range (регистр/стек/память).
Лексический scope не равен live-range. Неизвестные/оптимизированные
значения — unavailable/optimized out. CFI/location lists из asm — отдельное
исследование, а не автоматически доступная возможность CDB.
Покрытие различает посещение адреса, число исполнений и время. Trackpc
сам по себе не даёт времени и требует проверки банковых коллизий.
Ключ покрытия включает build ID/bank/location, знаменатель — доказанные
исполняемые позиции. Профиль использует измеренный источник cycles/time
или sampling с указанной погрешностью и overhead.
## 8. MCP, DAP и полный цикл VS Code
### 8.1 CLI/MCP
Интерфейсы: attach/status/detach, where, disassemble_src, break_at,
clear_owned_breakpoints, read_var/write_var, watch_var, registers/memory,
step_instruction/step_in/next/step_out/pause/continue, logs с курсором,
console_log и загрузка набора точек. Возвращать build ID/generation,
фактическое разрешение и ограничения там, где они нужны для интерпретации.
Неподдержанное — явная ошибка. Старые низкоуровневые команды интегрируются
в арбитраж, а не обходят его.
### 8.2 DAP MVP и развитие
MVP: initialize, attach, configurationDone, disconnect, setBreakpoints,
setFunctionBreakpoints, threads, stackTrace, scopes, variables,
ограниченный evaluate, continue/pause, проверенный instruction step,
disassemble и logMessage/output. Один Z80 — один thread; stackTrace
сначала содержит один текущий frame. Source-step, если ещё не готов,
отвечает отказом с объяснением; instruction granularity включается
только при реализации.
Соблюдать initialize→initialized→configurationDone; stopped содержит
причину/ID точек, continued сообщает внешний resume, breakpoint — новое
разрешение. Terminated означает конец сессии; exited выдаётся только при
установленном завершении приложения и достоверном коде выхода.
SetBreakpoints заменяет набор данного source, включая очистку пустым
списком, а не добавляет точки бесконечно.
Capabilities отражают реальную поддержку. Расширения: breakpointLocations,
instruction/conditional/hit breakpoints, readMemory/writeMemory, setVariable,
dataBreakpointInfo вместе с setDataBreakpoints, source-step/next/stepOut,
cancel и сложные типы. Frame/variables references привязаны к остановке,
memory references различают банк/пространство. Предусмотреть пагинацию.
Stdout адаптера содержит только DAP, диагностики — stderr/log.
### 8.3 Разработка и запуск из редактора
**Архитектурное решение:** Sprinter-специфичный цикл реализуется собственным
расширением `VSCode-Sprinter`. Готовые C/C++ или clangd можно
использовать для подсветки, completion и навигации, а VS Code Tasks и Debug UI
— как стандартные интерфейсы. Они не знают ABI SDCC/Z80, пакет
`.sprinter-cc-*`, DSS, банковую адресацию и протокол MAME, поэтому не могут
заменить project extension и не считаются источником истины для диагностики
компилятора или отладки.
Мини-расширение уже содержит `contributes.debuggers`, точки для C, схему и
шаблоны конфигурации. Следующий уровень переносит в него build/run/debug
оркестрацию: `DebugConfigurationProvider` проверяет конфигурацию и при
необходимости запускает выбранную build task; команды расширения выбирают
target/profile/EXTRA_DATA; TaskProvider и problem matcher переводят ошибки
SDCC/линкера в Problems. Сборку всё равно выполняют `make`/`sprinter-cc`, а
запуск — общий Python launcher: расширение не дублирует их логику.
Установка через VSIX, разработка через Extension Development Host. Наличие
VS Code, pyenv Python и необязательного языкового расширения проверяется при
настройке, а не считается постоянным свойством конкретной машины.
Включить в поставку:
- TaskProvider и/или tasks.json: make/sprinter-cc и problem matcher
SDCC/ассемблера/линкера; debug не стартует после неуспешной сборки.
- Языковые настройки: include/defines/gfx/safe/memory из той же сборочной
конфигурации. SDCC-расширения вроде __naked требуют совместимых редакторских
определений; generic clang/GCC-анализ не равен полному анализу SDCC.
- launch.json: приложение/пакет, MAME/ROM/media, EXTRA_DATA, timeout,
stopOnEntry/stopOnMain, source mappings; без личных абсолютных путей.
- Launch: успешная сборка → упаковка → MAME → загрузка DSS → проверка образа
→ готовность runtime → main. Переиспользовать текущую упаковку/launcher,
не дублировать сценарии ввода клавиатуры.
- Attach к подготовленной сессии без второго MAME; restart с новым build ID
и пересчётом точек после новой сборки/загрузки.
- Disconnect не закрывает чужой MAME; terminate запущенного адаптером
процесса имеет явную политику. Выход приложения отличается от выхода
MAME. Ошибки сборки/ROM/media показываются в редакторе с причиной.
## 9. Альтернативный маршрут GDB
DAP выбран за прямую модель Sprinter, reuse сессии MCP и отсутствие
обязательного DWARF-писателя/target GDB. GDB полезен для его скриптов и
фронтендов; маршрут сохранён как самостоятельное расширение.
Первоначальная разведка: OSD gdbstub знает z84c015, но объявляет mame.z80
и другой порядок регистров; исследованный GDB ожидает org.gnu.gdb.z80.cpu
и набор с объединённым IR. Повторить проверку выбранной пары версий.
Lua gdbstub с i386-картой не является готовым backend Z80. Оценка патча
«20 строк» заменяется проверкой полного контракта.
1. Собрать/закрепить target GDB; проверить handshake. Патч feature/регистров
и IR проходит round-trip всех регистров, включая альтернативные,
byte order и семантику R.
2. Проверить RSP step/continue/interrupt, logical memory read/write,
break/watch и отсутствие патчинга инструкций. Разведка сообщала, что
Z0/Z2..Z4 используют точки MAME; включить это в регрессию.
3. sdbg_elf.py на общей карте: ELF32 EM_Z80, секции по реальному размещению,
symtab, debug_line, затем CU/subprogram/variable и базовые типы DWARF.
Дыры/банки не склеивать в ложный text. Сначала binutils/GDB offline,
затем live. ELF с symtab остаётся полезным самостоятельным экспортом.
4. debug_frame/CFI и locations — только по доказанным данным; строки DWARF
сами по себе не исправляют unwinder SDCC.
5. VS Code cppdbg/target GDB/app.elf — отдельная MI-конфигурация. Банки
сначала вручную/ограниченно; затем исследовать overlays и синхронизацию
с runtime, а не обещать автоматическую поддержку.
GDB имеет dprintf без пересборки и поддержку overlays. Редакторский
logMessage зависит от frontend; overlays требуют интеграции Sprinter.
Поэтому прежние «логпоинтов нет» и «банки невозможны» заменены конкретными
ограничениями. Области Registers/Ports также возможны поверх GDB,
но в DAP непосредственно используют нашу target-модель.
RSP и DAP не управляют исполнением независимо одновременно. Режим GDB
получает отдельного владельца; наблюдающие функции MCP проверяются отдельно.
Общую карту можно переиспользовать без совместного run/stop.
## 10. Этапы реализации и условия перехода
Календарные оценки прежнего плана считаются оценками демонстрационного
прототипа: надёжность lifecycle/банков/DAP ими не покрыта. После этапа 0
оценить каждый этап по репро и измерениям, раздельно прототип, поддержанный
выпуск и исследовательские функции. Проход этапа определяется приёмкой.
### Этап 0 — проверка предположений
Сохранить §2.3; воспроизвести общий inline в двух вызываемых TU и проверить
адреса обоих экземпляров/решение R01. Проверить anchors, huge/big/bank-data,
библиотечную CDB, memory aliases/watchpoint PC, lifecycle crt0, async step,
задержки моста и CRC банков. Закрепить версии и baseline производительности.
**Выход:** доказанный способ R01 либо явный ограниченный пер-модульный
режим; таблица проверенных/непроверенных возможностей. Общую карту не
объявлять готовой при скрытом конфликте.
### Этап 1 — сборка, manifest и карта
Флаги/app.mk, публикация пакета, исправление CDB, типизированные адреса,
resolver и CLI map/addr2line/line2addr/vars/asm. Debug-библиотеки добавляются
после успешного эксперимента. Обновить документацию сборки/mame-autotest.
**Выход:** fixtures, одинаковые basename, inline multi-TU, huge/big и
bank-data разрешаются достоверно; unknown явен. Полный cmp debug/non-debug;
make size-check без необъяснённой дельты. Baseline не обновляется ради
скрытия регрессии.
### Этап 2 — сессия и мост
Lifecycle/image verification, snapshot/generation, арбитраж, реестр точек,
событийный протокол. Сначала файловый backend/измерения, затем socket
по необходимости. Pause/continue/instruction step и ограниченный async
stepIn. Версионировать мост/патчи и установщик (§12). Новый самостоятельный плагин
можно загружать прямо из toolchain/mcp через pluginspath без копирования
в vendor. Для интерактивного режима без окон требуется решение R19.
**Выход:** нет ложных точек DSS, банки активируются после готовности,
exit/reset/reload инвалидируют состояние; потеря клиента не блокирует MAME,
конкурентные операции не читают смешанный snapshot.
### Этап 3 — полезная CLI/MCP-отладка и логи
Внешние точки/условия/логи, диспетчер совпадений, базовые типы, чтение/запись
резидентных объектов, доказанные watchpoint, where/disassemble_src/clog.
Интегрировать пакет/журнал в автотестовый launcher; проверить headless debugger.
**Выход:** значения совпадают с эталонными байтами, логи воспроизводимы
и ограничены, breakpoint не проглатывается логпоинтом, повторная загрузка
набора не дублирует точки.
### Этап 4 — VS Code MVP
DAP §8.2 и мини-расширение, сначала attach. Source/asm, break/logMessage,
globals/registers, pause/continue/instruction step. Source-step включить
после его приёмки, другие запросы не рекламировать заранее.
**Выход:** редакторская точка показывает фактическое разрешение,
остановка — верные строку/банк/значения; lifecycle DAP, удаление точек,
disconnect и stale references проверены протокольными тестами.
### Этап 5 — полный цикл разработки и расширенная отладка
Собственное расширение получает команды Build/Run/Debug, TaskProvider,
problem matcher, выбор target/profile/EXTRA_DATA и проверку инструментов.
Языковой сервис C/C++ либо clangd остаётся необязательной внешней
зависимостью с генерируемыми include/defines; его диагностика не подменяет
SDCC. Добавить launch/restart/stopOnMain и упаковку VSIX. Довести next,
доступные stepOut, memory/setVariable, data breakpoints, physical bank-data,
массивы/структуры и резидентные комментарии.
**Выход:** новый рабочий каталог проходит документированную настройку/F5
на обычной и банковой программе; restart не использует старую карту.
У каждой расширенной функции свой тест/capability; недоступная функция
не мешает использовать поддержанные.
### Этап 6 — якоря и исследовательские функции
Первый `SDBG_LOG` и ограниченный `SDBG_LOGIF` уже реализованы; продолжить
§7.2 проверкой сложных inline/оптимизаций и условных выражений только после
появления соответствующего evaluator. Независимые подэтапы: банковые комментарии,
доказанные stack frames/локальные, coverage/profiling. GDB §9 — отдельный
подэтап по потребности.
Финальные задачи для макросов, **без реализации в текущем этапе**:
- Добавить явные десятичные и hex-форматы для 8/16/32-битных signed/unsigned
объектов и адресов, с проверкой типа, знака, ширины и ограниченной общей
грамматикой для SDBG_LOG, внешних logpoints и DAP. Проверить значения на
реальных SDCC-сборках; не передавать Python format specifier в MAME `printf`.
- Для указателя выводить его собственное значение (логический/банковый адрес)
в decimal/hex и отдельно значение по адресу только если указатель не `NULL`.
`char *` трактовать как строку с bounded read до NUL и проверенной кодировкой;
`int8_t *`/`uint8_t *` — как один 8-битный signed/unsigned объект (для
16/32-битных typed pointers — соответствующий размер). Не выбирать режим
по одному CDB: `char *` и `uint8_t *` там неразличимы (R22); сохранить
объявленный тип/typedef chain из исходника и сверить с CDB. Чтение только
без side effects, после проверки размера, доступного банка, границ и image
identity; `NULL`, закрытая страница, отсутствие NUL в лимите и неизвестный
pointee дают явный `unavailable`/truncated, а не неверное значение.
**Выход:** доказательства и ограничения каждой функции опубликованы.
Эвристика не выдаётся за стек, anchors — за гарантированную идентичность,
посещения адресов — за время CPU.
## 11. Матрица приёмки и регрессий
| Сценарий | Проверяемое свойство | Этап / риски |
|---|---|---|
| hello/probe debug и обычный | Полный cmp exe, размеры, карта main | 01, R02 |
| Два TU с общим вызываемым inline | Нет коллизий, адреса/строки обоих экземпляров | 0–1, R01/R04 |
| Ошибка линковки при старом ihx | Ошибка сохранена, новый пакет не опубликован | 1, R01/R12 |
| Два utils.c и перенесённый проект | TU identity и source mapping | 1, R12 |
| Цикл/ветка/удалённая строка/много адресов | Все позиции или unverified, без ложного переноса | 1–4, R04 |
| Два банка с одинаковыми PC и ret | Верная строка/точка, нет чужого комментария | 0–6, R03/R11 |
| huge W3, big W1, поддержанные manual | Адреса/guards из фактической карты | 1–5, R03 |
| bank-data, невключённый банк, граница окна | Physical чтение или Unavailable | 15, R03 |
| Watchpoint через CPU/alias | Адрес, момент и PC-источник корректны | 0–5, R03 |
| Entry до банков, ошибка loader | Нет ранней активации/ложного ready | 2, R05 |
| DSS/exit/restart/reset/state load | Нет чужих попаданий и stale handles | 25, R05/R12 |
| Изменённый source или чужой exe | Явное расхождение карты | 1–5, R12 |
| Log + user + temporary по одному PC | Лог есть, остановка сохранена | 2–4, R08 |
| Два клиента, ручной resume, reconnect | Арбитраж/события, нет повторных mutations | 24, R07/R15 |
| Одна строка в цикле, HALT/ISR/рекурсия/trampoline | Ограниченный отменяемый шаг | 2–5, R06/R13 |
| Signed 8/16, pointer/array, CP866, нет NUL | Знак/тип/кодировка, bounded read | 35, R10 |
| Горячий лог, переполнение, выход | Overhead, счётчик потерь, flush | 3, R09 |
| Debug libc/libbgi fast/safe, DCE | Строки/типы, нет роста кода | 0–1, R17 |
| Anchors с #if/include/wrappers/inline | Метаданные совпадают, unsupported отклонён | 6, R02/R04 |
| DAP replace/empty/events/references | Протокол и честные capabilities | 4, R13 |
| F5 с данными, ошибка ROM/build, restart | Полный цикл и диагностика | 5, R18 |
| MAME update с ручными изменениями | Установщик не затирает расхождение | 2, R16 |
Тесты карты/протокола — небольшие fixtures без MAME; CPU/банки/lifecycle —
живые интеграционные сценарии. Golden-карта сверяется независимо с
листингом, байтами и фактическими остановками, не только выводом парсера.
Tests/hello и tests/banked — стартовые кандидаты, не вся приёмка.
Замеры: без debugger, debugger без точек, resident/bank breakpoints,
горячий лог, instruction trace, transport RTT, end-to-end step.
Записывать хост/версию, emulation speed, объём журнала, latency p50/p95.
Пороги зафиксировать после baseline этапа 0 до выбора backend;
«21 МГц терпимо» не критерий приёмки.
## 12. Размещение и воспроизводимость
Исходники моста/MCP — `toolchain/mcp/`, патчи MAME —
`toolchain/mame-patches/`; патч SDCC при необходимости отдельно с версией.
`toolchain/install-mame-bridge.sh` проверяет upstream revision/хеши,
показывает diff при расхождении и не затирает неизвестные ручные изменения.
Повторная установка идемпотентна; protocol version проверяется handshake.
Самостоятельный sdbgbridge загружается прямо из toolchain/mcp через
pluginspath: это устраняет необходимость копировать его в vendor и риск
потери изменений. Установщик остаётся нужен для патчей существующего
MAME/моста, если они потребуются. Игнорируемое mame/ не источник истины. Документировать сборку/применение
патчей, проверку установленного бинарника и откат. Не хранить личные пути,
ROM и большие образы в исходниках расширения.
Defaults вместо прежних открытых вопросов: source debug явный/профиль IDE;
CDB errors не подавляются; отдельный журнал; общий ограниченный язык;
DAP — основной IDE-путь; одна сессия владеет backend; socket по измерениям;
debug-библиотеки после эксперимента. Технически открыты: способ R01,
наблюдение загрузки/выхода, полная mapping-формула, physical RAM и стоимость
hooks. У каждого — репро этапа 0 и условие допуска, а не молчаливое
предположение следующих этапов.
## 13. Внешние спецификации
Локальные версии исходников и репро первичны для конкретной сборки.
Online-документация задаёт общую семантику; при реализации фиксировать
использованную версию.
- [DAP specification](https://microsoft.github.io/debug-adapter-protocol/specification): запросы, события, capabilities и references.
- [DAP specification source](https://github.com/microsoft/debug-adapter-protocol/blob/main/specification.md): полный контракт.
- [VS Code debugger extension](https://code.visualstudio.com/api/extension-guides/debugger-extension): регистрация/упаковка адаптера.
- [MAME general commands](https://docs.mamedev.org/debugger/general.html): printf/tracelog/source/trackpc.
- [MAME execution commands](https://docs.mamedev.org/debugger/execution.html): шаги и трассировка.
- [MAME Lua debugger classes](https://docs.mamedev.org/luascript/ref-debugger.html): низкоуровневый API.
- [GDB Dynamic Printf](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Dynamic-Printf.html): логирование без пересборки.
- [GDB Overlays](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Overlays.html): перекрывающиеся размещения и target-интеграция.