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

367 lines
32 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.
# Отладка исходников: реализация и результаты
Дата: 2026-09-16. План: [mame-source-debug.md](mame-source-debug.md).
Реализованы сборка/карта, проверенный транспорт, базовая C-сессия,
постоянный session server, DAP MVP, VS Code launch и build task. MCP C-уровня,
безопасный restart и расширенная отладка ещё не готовы.
Этапы плана не считаются завершёнными целиком по одному успешному репро.
## Статус платформ
- **macOS:** текущий end-to-end цикл проверен, включая backend `sdbg` и
одновременную работу VS Code со штатным `osx` debugger MAME.
- **Linux:** ожидается работа через `sdbg`, `qt` или `imgui`, но полный
живой прогон ещё не выполнен.
- **Windows:** весь функционал пока работать не будет. Provider
`debugger: "windows"` открывает штатный debugger MAME, однако host-часть
использует `fcntl`, Unix domain sockets и Unix launcher. Native Windows
launch/attach из VS Code официально не поддерживается до реализации
переносимого транспорта, блокировки, запуска и end-to-end тестов.
## Доступно сейчас
- `bin/sprinter-cc --src-debug` и повторяемый `--src-debug-file FILE`.
- `app.mk`: `SRC_DEBUG=1` либо `SRC_DEBUG_FILES=...`; выбор интерпретатора
через `PYTHON` в make и `SPRINTER_PYTHON` у обёртки. В проекте используется
local pyenv Python 3.12 (`.python-version`); личный путь не зашит в код.
- Уникальные имена CDB-позиций и TU, включая повторные метки внутри одного
модуля. Публичные C/asm-символы и инструкции не переименовываются.
- Сборка в отдельном временном каталоге с проверкой пакета перед публикацией.
Ошибка compiler/linker/парсера сохраняет предыдущие EXE и пакет.
Конкурентные debug-сборки одного output сериализованы; одновременная
обычная и debug-сборка одного output пока не поддерживается.
- Manifest: build ID, хеш EXE/артефактов/исходников/включённых заголовков,
сохранённые исходники, параметры памяти, команды, версия SDCC и выбранные TU.
Зависимости собираются также для TU без карты в пер-модульном режиме.
- Проверяемый JSON-индекс `.sdbg.json`, offline CLI функций, переменных,
символов и соответствия C/asm/адресов. Банковые адреса учитывают секцию
и окно huge/big; банк данных не трактуется как резидентная память.
- Пересборка make при смене команды/содержимого зависимостей. Быстрая правка
заголовка не зависит от секундной точности mtime; повторный make без
изменений не вызывает compiler.
- Отдельный плагин `toolchain/mcp/sdbgbridge`, загружаемый непосредственно
из репозитория через pluginspath. Он не заменяет существующий mamebridge
и не требует копирования изменённых файлов в игнорируемое дерево MAME.
- Версия протокола, ID сессии, generation, атомарные файловые ответы,
ограниченный журнал событий, stopped snapshots, чтение logical memory
с отключёнными side effects, pause/continue/instruction step и точки
с проверкой владения. FileBridge запрещает повтор mutations после timeout.
- `DebugSession` проверяет EXE, Intel HEX и исполняемые байты реально
загруженного resident/current-bank образа перед интерпретацией PC. Он читает
`_bank_pages`, отвергает нули/дубликаты и учитывает, что state MAME хранит
старшие флаги над 8-битным значением page-port.
- Точки по строке и функции, удаление логической группы, `where` и чтение
поддержанных 1/2/4-байтных global/static. Банковская точка получает условие
по I/O page-port (`ib@e2==...` для W3), формируемое самим plugin; произвольная
debugger expression через этот вызов не принимается. Resident-объект или
bank-data не читается, если его физическая страница сейчас закрыта.
- Bootstrap живого теста не держит entry-breakpoint во время загрузки DSS.
Он активирует единственную service-точку перед вводом команды запуска,
сверяет сигнатуру/образ на `main`, а исходные точки ставит после attach.
Банковые пользовательские точки нельзя безопасно активировать до заполнения
`_bank_pages`, поэтому они намеренно создаются после runtime init.
- `sdbg_server.py` — единственный долгоживущий владелец backend. Локальный
Unix JSON-RPC допускает несколько клиентов, сериализует команды, хранит
логические группы и события. `set_source_breakpoints` и
`set_function_breakpoints` создают новый disabled-набор, при ошибке удаляют
его, затем заменяют прежний набор и активируют точки.
- Session server проверяет `snapshot` и при остановленном CPU раз в 0,5 с:
reset/state load инвалидирует сессию, потеря MAME приводит к закрытию после
ограниченного timeout backend. Если выполнение продолжено из родного окна
debugger MAME, server сообщает DAP смену состояния. Закрытая сессия всегда
выдаёт DAP `terminated`, даже если событие `invalidated` выпало из журнала.
- DAP MVP и минимальное VS Code-расширение: attach к session server,
source/function breakpoints, один достоверный frame, registers,
поддержанные globals/statics, evaluate одного имени, continue/pause и
instruction step. F11 идёт до другой C-позиции, F10 использует MAME `over`,
Shift+F11 выходит через bank trampoline до C-позиции caller. Автомат имеет
предел 512 машинных операций и сохраняет приоритет пользовательской точки.
Длинный вызов с ожиданием внешнего ввода не блокирует DAP; Pause доступен.
Неподдержанные setVariable, disassemble, restart/terminate не рекламируются
либо возвращают явную ошибку.
- Собственное расширение является обязательной Sprinter-частью интеграции:
готовые C/C++ или clangd можно добавить для редактирования, но они не знают
source-debug package, DSS, банки и MAME DAP. Версия 0.2.0 даёт TaskProvider
для приложений с `app.mk`, команду Build Active Project и автоматическую
debug-сборку перед F5. Она использует `make SRC_DEBUG=1` и local Python 3.12;
ошибки SDCC с файлом/строкой попадают в Problems. Код сборки проверяется
самим расширением: при ошибке MAME не запускается. VSIX 0.2.0 собран и
проверен в отдельном workspace SprPoP. Run-команда, расширенные
linker/assembler diagnostics и выбор profile/EXTRA_DATA остаются
следующими шагами.
- DAP `logMessage` без пересборки: разрешены литералы, `{{`/`}}` и только
подстановки `{variable}`. Session server читает типизированное значение,
отправляет `output` и продолжает CPU. Совпавшая обычная точка имеет
приоритет остановки, поэтому logpoint её не проглатывает. Произвольные
выражения, format specifier и conversion намеренно отвергаются.
- `<sdbg.h>`: `SDBG_LOG(tag, "total={total}")` и ограниченный
`SDBG_LOGIF(tag, flag, "...")` создают нулевые asm-якоря только в
выбранном debug-TU. Метаданные приходят из активного препроцессорного
потока; package содержит связанный адрес/причину unverified. Session server
автоматически ставит эти точки после attach, зеркалирует текст в MAME
debugger console и DAP Debug Console, затем продолжает CPU. Совпавшая
пользовательская точка по-прежнему останавливает исполнение. Локальные,
C-выражения и printf-форматы пока не доступны; значения читаются из
поддержанных global/static объектов, исходное приложение не печатает в DSS.
DAP event ring ограничен 1024 событиями; если клиент отстал, Debug Console
показывает число пропущенных событий. Частые logpoints останавливают CPU
при каждом попадании, их overhead пока не измерен. DAP хранит точный UTF-8
текст; MAME `printf` получает безопасную проекцию: кавычки заменяются
апострофом, управляющие символы — пробелами.
Практический контракт и таблица типов —
[руководство по SDBG_LOG](sdbg-log-macros.md). Decimal/hex formatter,
форматированный адрес и значение по ненулевому указателю — задачи финального
этапа, сейчас не поддерживаются. Для него `char *` задан как строка,
`int8_t *`/`uint8_t *` — как один 8-битный объект; текущий CDB не
различает `char *` и `uint8_t *`, поэтому нужна metadata declared type.
- `sdbg_launcher.py` реализует VS Code launch: создаёт временную дискету,
копию системного HDD и отдельные state-каталоги, показывает окно Sprinter,
ждёт DSS, ставит только service-точку `main`, вводит `a:\\NAME.EXE`,
проверяет сигнатуру entry и запускает session server. Закрытие DAP
завершает только созданные им server/MAME и удаляет временный каталог.
## Использование карты
В shell без инициализированных pyenv shims используйте `pyenv exec`:
```sh
pyenv exec make -C tests/hello SRC_DEBUG=1
pyenv exec python toolchain/sdbg.py --build tests/hello/.sprinter-cc-hello verify
pyenv exec python toolchain/sdbg.py --build tests/hello/.sprinter-cc-hello map
pyenv exec python toolchain/sdbg.py --build tests/hello/.sprinter-cc-hello vars
pyenv exec python toolchain/sdbg.py --build tests/hello/.sprinter-cc-hello line2addr hello.c 33
```
`addr2line` принимает адрес **линковщика** с префиксом `0x`, например
`0x1c00c`, а не логический PC без информации о банке. Ответ включает
logical_address, bank, window, функцию, инструкцию `.asm` и все найденные
исходные позиции. Неизвестный адрес возвращает unknown. Неоднозначности
не скрываются; изменённый исходник имеет stale-статус, сохранённый текст
собранной версии остаётся в пакете.
Для одной TU: `pyenv exec make -C tests/banked SRC_DEBUG_FILES=bank1.c`.
Режимы all/selected взаимоисключающие. `--debug` по-прежнему означает
runtime DEBUG_RT и не включает карту C.
Низкоуровневый live CLI работает с уже запущенным `sdbgbridge`; его IPC и
session ID должны совпадать с окружением процесса MAME:
```sh
pyenv exec python toolchain/sdbg_session.py \
--build tests/hello/.sprinter-cc-hello \
--ipc "$SDBG_IPC_DIR" --session "$SDBG_SESSION_ID" attach
```
Доступны `where`, `step`, `continue`, `break-line`, `break-function`,
`read-variable`, `activate-breakpoints` и `deactivate-breakpoints`.
Это диагностический однооперационный CLI: логические ID групп точек живут
только внутри процесса. Для долгой работы и DAP используйте общий session
server ниже.
Предпочтительный режим для нескольких клиентов — один server:
```sh
pyenv exec python toolchain/sdbg_server.py \
--build tests/hello/.sprinter-cc-hello \
--ipc "$SDBG_IPC_DIR" --session "$SDBG_SESSION_ID" \
--socket /tmp/sprinter-sdbg.sock
pyenv exec python toolchain/sdbg_client.py \
--socket /tmp/sprinter-sdbg.sock status
```
VS Code-расширение находится в отдельном репозитории `../VSCode-Sprinter`. В
Extension Development Host используется attach-конфигурация:
```json
{
"type": "sprinter-mame",
"request": "attach",
"name": "Sprinter MAME: Attach",
"socket": "/tmp/sprinter-sdbg.sock"
}
```
В VS Code расширение `0.2.0` находит SDK через `sprinterDebugger.sdkRoot`,
а Python 3.12 — через local pyenv shim или установленную pyenv-версию.
Это устраняет зависимость от `PATH` процесса VS Code, открытого из Dock,
и позволяет отлаживать приложение из отдельного workspace.
Выбранные пути видны в Output → `Sprinter MAME Debug`; при раннем сбое
launcher сообщает stderr и последние строки MAME log.
Интегрированный запуск:
```json
{
"type": "sprinter-mame",
"request": "launch",
"name": "Sprinter MAME: Launch",
"build": "${workspaceFolder}/tests/hello/.sprinter-cc-hello"
}
```
Launcher читает text-mode descriptors из VRAM и ждёт стабильный пустой prompt
`X:…>` 0,25 секунды. `dssTimeout` по умолчанию равен 30 эмулируемым секундам;
`launchAt` теперь только необязательная нижняя граница. Перед вводом launcher
сохраняет снимок DSS.
Пошаговый запуск development-расширения описан в
[vscode-sprinter-debug.md](vscode-sprinter-debug.md).
## Воспроизводимые проверки
```sh
pyenv exec python -m unittest discover -s tests/sdbg -v
pyenv exec python tests/sdbg/run_mame_probe.py
pyenv exec python tests/sdbg/run_mame_probe.py --bridge
pyenv exec python tests/sdbg/run_mame_probe.py --bridge --banked
pyenv exec python tests/sdbg/run_mame_probe.py --bridge --banked --server
pyenv exec python tests/sdbg/run_mame_probe.py --banked --bridge --server --lifecycle-exit
pyenv exec python tests/sdbg/run_mame_probe.py --banked --bridge --server --lifecycle-load
pyenv exec python tests/sdbg/run_mame_probe.py --banked --launcher
pyenv exec python tests/sdbg/run_mame_probe.py --banked --launcher --native-debugger
pyenv exec python tests/sdbg/run_mame_probe.py --banked --launcher --source-step
pyenv exec python tests/sdbg/run_mame_probe.py --banked --launcher --step-out
pyenv exec python tests/sdbg/run_vscode_dap_probe.py
pyenv exec python tests/sdbg/run_vscode_dap_probe.py --exit-while-stopped
pyenv exec python tests/sdbg/run_vscode_dap_probe.py --stop-while-stopped
pyenv exec python tests/sdbg/run_vscode_dap_probe.py --term-while-stopped
pyenv exec python tests/sdbg/run_vscode_dap_probe.py --waitkey --manual-key
```
Живой тест использует отдельную дискету, копию системного HDD и отдельные
каталоги состояния в `build/sdbg-live/`. Общие media и запущенный вручную
MAME не изменяются. Без `--bridge` проверяется шаг из Lua callback;
с `--bridge` — отдельный протокол и файловый клиент. `--native-debugger`
добавляет родное окно к тому же DAP-сценарию. Скрипт, запущенный системным Python <3.12,
перезапускает себя через local pyenv. Результаты — `result.jsonl`, `mame.log`.
Остановка процесса в конце bridge-теста относится только к MAME этого теста.
Зафиксировано на SDCC 4.5.0 #15242 и MAME v0.287,
commit `b0c4527c2edb1ee177fd09b3c412b65b385bf35b`:
| Проверка | Результат |
|---|---|
| Обычная debug-линковка двух TU с общим inline | Воспроизводится rc != 0, `Multiple definition of C$...` |
| Уникализация debug-символов | Успешная линковка; функции обоих экземпляров различаются |
| Повторные метки sprinter.h:111 | Сохранены начало и эпилог в каждом из трёх TU: 6 адресов |
| debug/non-debug probe, banked huge и big | Полные EXE идентичны |
| Одинаковые basename двух utils.c | Две функции и два независимых статика |
| bank-data в big | Переменная разрешена в банке 1, окне W1 |
| Ошибка линковки поверх опубликованной сборки | Предыдущие EXE и manifest не изменились |
| Испорченный артефакт / изменённый source | Ошибка проверки / явный stale; старый текст сохранён |
| Пер-модульная карта | Только выбранная TU в карте, все компиляционные зависимости учтены |
| Быстрая правка header / переключение debug | Пересборка; без изменений повторной компиляции нет |
| Архив с libc/string/strlwr.c | L-записи попадают в общий CDB, F/S из архивного adb не импортируются |
| Lua step на main | PC=0x8218 сразу после запроса; PC=0x821B на следующем callback |
| Порядок DSS → arm → launch | VRAM-detector нашёл стабильный prompt в строке 22 на 4,69 с; только затем включена entry-точка, ложных попаданий 0 |
| Новый мост, родной debugger | Проверены образ, регистры, `total`: 0 → 42, C-line breakpoint; instruction step около 32–124 мс в отдельных прогонах |
| Банковская точка huge | Условие через W3 page-port; остановка PC=0xC000 разрешена как linker 0x1C000, bank 1 |
| Session server + DAP | Owner lock удержан; DAP function breakpoint остановил `worker`; frame сохранил bank 1 |
| Production launcher | DSS prompt → `main` с `false_hits=0`; backend `sdbg` и опциональный `osx` прошли одинаковый DAP-сценарий, штатный terminate без нового crash report |
| DAP logpoint + breakpoint | В bank 1 напечатано `bank_value=0`; совпавшая function breakpoint сохранила остановку |
| Авторский `SDBG_LOG`, 2026-09-15 | Fixture `logmacro`: EXE с/без macro побайтово равны, сообщение отсутствует в EXE; активный wrapper и склейка литералов привязаны к linked-якорю, `#if 0` исключён. Живой DAP launch в `sdbg` и `osx`: `total=1` одновременно в MAME debugger console и VS Code Debug Console, `false_hits=0` |
| Source F11/F10 | `main`: `0x42bb``0x42c1`; F10 выполнил банковский `worker` и остановился в caller на `0x42c9` |
| Source Shift+F11 | Из `worker` bank 1 выполнен выход через trampoline в `main:6`, `0x42c9` |
| VS Code stdio DAP launch, 2026-09-15 | Local pyenv shim → DAP → изолированный MAME/DSS → `hello``main:17`, PC=`0x8224`; `false_hits=0`, штатный disconnect |
| F10 через `getchar()`, 2026-09-15 | Асинхронный `next` ответил за 21,49 мс; при ручном `x` в окне MAME `hello.c:62``:63`, возврат `DE.low=0x78`; обе клавиатуры включены в изолированном cfg |
| VS Code TaskProvider Build, 2026-09-15 | Одиннадцать Node-проверок: выбор Makefile рядом с пакетом/в `build/`, команда make с Python shim, SDCC matcher, успешный и неуспешный код задачи, pyenv вне workspace; реальный make в `tests/hello` прошёл, VS Code показал Build → MAME/DSS → `main` |
| Ошибочная debug-сборка, 2026-09-15 | Изолированный `fault.c` вернул код 2 от make, `file:1: error 20` сохранился, Python traceback удалён; MAME не участвует |
| Владение и generation | Второй FileBridge и чтение со stale generation отклоняются |
Полный host-набор: 39 тестов (38 прошли, один Unix-socket тест пропущен только
из-за запрета `bind` в sandbox). Тот же socket path проверен живым DAP-запуском.
После разделения репозиториев установленный VSIX в изолированном профиле
VS Code запустил сборку SprPoP и остановился в `src/sprpop.c:264` (`main`)
через API extension host. Новый живой пробник закрытия собственного MAME
при остановке в `hello/main` подтверждает DAP `terminated` после внезапной
потери процесса. Штатный DAP launch в `hello/main` также повторно прошёл
с `sdbg` и опциональным `osx` debugger provider.
Первоначально прямой `SIGTERM` остановленному MAME не давал DAP `terminated`:
через 12 секунд процесс оставался живым. Причина: SDL3 по умолчанию
преобразует `SIGTERM` в `SDL_EVENT_QUIT`, а SDL3 OSD MAME это событие
не обрабатывает. Launcher теперь выставляет `SDL_NO_SIGNAL_HANDLERS=1`
только для своего MAME, если переменная не задана пользователем. Повторный
живой прогон подтвердил `SIGTERM` → DAP `terminated`; DAP `disconnect`
(VS Code Stop) завершил MAME и адаптер с provider `sdbg` и `osx`.
Отдельный патч MAME для этого
не требуется. Закрытие окна по-прежнему проверено только через тот же
`schedule_exit()` в исходнике MAME, без автоматического UI-клика.
Живой lifecycle-пробник на банковой программе теперь прошёл с `sdbg` и
опциональным `osx` provider. На остановке в `main` Lua вызвал
`machine:exit()`: MAME вышел с кодом 0, DAP получил `terminated`, старый
session server отверг запрос. Закрытие SDL3-окна в MAME вызывает тот же
`schedule_exit()`; физический клик по кнопке окна не автоматизировался.
При save/load state пробник после сохранения поставил банковую точку,
загрузил сохранённое состояние и получил `terminated`; post-load проверка
`bplist()` показала ноль оставшихся точек, старый session server отверг
запрос. Для save/load пробник продолжает CPU после планирования операции:
эти команды MAME сами не выводят CPU из debugger stop-loop.
Это репро доступности механизма, а не полный benchmark или проверка всех
runtime/mapping/lifecycle. Отдельная публикация debug-библиотек требует
добавления метаданных извлечённых архивных модулей; одних флагов --debug
недостаточно. Режим библиотек пока не включён.
## Новое ограничение MAME
`src/osd/modules/debugger/none.cpp::wait_for_debugger()` вызывает `go()`.
Поэтому `-debugger none` автоматически продолжает CPU при остановке.
Автономные logpoint actions с ним возможны, ожидание интерактивных команд
в stopped-состоянии — нет. Поэтому добавлен provider `sdbg`: он возвращается
в stopped-loop после короткого sleep, а ядро вызывает Lua periodic перед каждой
итерацией. Во время остановки CPU обычная обработка событий окна не работает:
ранний `sdbg` только спал, отчего macOS помечала MAME «не отвечает» и окно
не открывалось через Cmd-Tab/Dock. Provider теперь опрашивает события
активного OSD примерно 100 раз в секунду (в текущем macOS SDL3 build:
`input_update(false)` и `process_events()`; в native macOS OSD:
`MacPollInputs()`). Новый бинарник проходит DAP launch до `main`; пользователь
подтвердил, что при остановке в `main` окно снова открывается через
Cmd-Tab/Dock. Регрессионный DAP-проход через `getchar()` с клавишей `x`
также завершился на следующей строке с кодом `0x78`.
Воспроизводимый patch, установщик и сборочные рецепты находятся в отдельном
`../MAME/scripts/sprinter/`. Provider `osx` остаётся
доступной launch-опцией и проверен одновременно с DAP. Для непатченного MAME
можно использовать `auto`: штатные варианты — `osx` на macOS, `windows` в
native Windows build, `qt` или `imgui` в Linux. `qt` зависит от
`USE_QTDEBUG=1`, а `imgui` требует графическое окно. Windows-версия самого
host-моста пока не готова: FileBridge использует `fcntl`, server — Unix socket.
Также startup debugscript с `g` может продолжить первое реальное попадание;
живой bridge-тест не использует такой скрипт. Повторная загрузка autoboot
на reset защищена от дублирования callback. Старый mamebridge параллельно
с sdbgbridge не загружать: общая арбитрирующая сессия ещё не реализована.
Два одновременно запущенных процесса MAME через MCP bridge не проверялись;
корректная маршрутизация команд между ними не гарантируется. Это отдельная
отложенная задача в [TODO.md](TODO.md).
## Размерный регресс и оставшаяся работа
`make size-check` теперь проходит (46 свежих карт). Прежние 12 расхождений
разобраны: `open()` добавил 9 байт после исправления режимов DSS;
`openenv` содержит ещё новый регрессионный тест; прежний размер `w3bgfx`
был снят по несвежей карте. Подробное сравнение с исходной ревизией и
правила обновления эталона — в
[плане разделения](project-reorganization.md).
Hard reset из RPC был удалён после воспроизводимого падения MAME 0.287:
старый Lua periodic callback обращался к `debugger_manager` во время нового
`running_machine::start()`. Reset notifier теперь только инвалидирует plugin,
а live-пробник прекращает работу callback до рестарта. До отдельного
безаварийного репро reset/restart capability не публикуется.
Crash reports в 19:22/19:23 относились к неподдержанному register symbol в
условии MAME; публичный протокол теперь принимает только window/page и plugin
строит проверенное условие через I/O port. После штатных прогонов в 20:43 и
позже новых `mame.arm-*.ips` не появилось.
Далее по плану: сделать MCP-адаптер, безопасный restart и расширенные
выражения. VSIX уже упакован;
для IDE ещё нужны Run-команда, выбор профиля
сборки/данных и расширенная диагностика assembler/linker.
Базовый attach уже проверяет принадлежность resident/current-bank к build,
но не умеет читать неотображённую physical RAM. Нет автоматического чтения
неотображённого bank-data, локальных или backtrace.
Пути исходников в текущем пакете абсолютные. Снимки и уникальные TU уже
есть, переносимый source mapping — следующая доработка. Пока библиотечные
описания и отдельные форматы CDB не поддержаны, карта сообщает ограничения.