337 lines
22 KiB
Markdown
337 lines
22 KiB
Markdown
# Source-debug Sprinter в VS Code
|
||
|
||
Статус: MVP для разработки самого отладчика. Поддержаны автоматический launch,
|
||
ручной attach, точки по строкам и функциям, logpoints, один C-frame,
|
||
регистры, простые global/static, continue/pause, instruction step и шаги
|
||
по исходнику F10/F11/Shift+F11.
|
||
|
||
> [!WARNING]
|
||
> **Windows пока не поддерживает полный цикл отладки.** Значение
|
||
> `debugger: "windows"` включает только штатное окно debugger MAME.
|
||
> Текущие launcher, owner lock и DAP/session transport используют Unix
|
||
> shell, `fcntl` и Unix domain sockets. Поэтому native Windows launch/attach
|
||
> из VS Code пока не считается рабочим. Поддержка потребует отдельного
|
||
> Windows transport и end-to-end проверки.
|
||
|
||
## Какие расширения нужны
|
||
|
||
Для build/run/debug используется отдельный проект `VSCode-Sprinter`. Только
|
||
его расширение знает формат source-debug
|
||
пакета, загрузку приложения через DSS, банки Sprinter и DAP-сессию MAME.
|
||
|
||
Microsoft C/C++ или clangd можно поставить дополнительно ради completion,
|
||
переходов по исходникам и подсветки. Оба анализируют код как близкий к
|
||
обычному C и не являются точной моделью SDCC: параметры `__naked`, ABI и
|
||
часть target-заголовков потребуют отдельных defines/configuration. Ошибки
|
||
реальной сборки всегда определяет `sprinter-cc`.
|
||
|
||
## Подготовка программы
|
||
|
||
Соберите приложение с полной картой:
|
||
|
||
```sh
|
||
pyenv exec make -C tests/hello SRC_DEBUG=1
|
||
```
|
||
|
||
Рядом с EXE появится `.sprinter-cc-hello/manifest.json`. При изменении C,
|
||
заголовка или опций `make` пересоберёт пакет по хэшу содержимого.
|
||
|
||
## Загрузка development-расширения
|
||
|
||
Из корня репозитория откройте VS Code с распакованным расширением:
|
||
|
||
```sh
|
||
code --extensionDevelopmentPath="/путь/к/VSCode-Sprinter" "$PWD"
|
||
```
|
||
|
||
Если команда `code` не добавлена в `PATH`, на macOS этого проекта доступен
|
||
полный путь:
|
||
|
||
```sh
|
||
"/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" \
|
||
--new-window \
|
||
--extensionDevelopmentPath="/путь/к/VSCode-Sprinter" \
|
||
"$PWD"
|
||
```
|
||
|
||
В локальных настройках workspace задайте `sprinterDebugger.sdkRoot` и
|
||
`sprinterDebugger.mameHome`. Первый указывает на установленный C-Compiler,
|
||
второй — на среду `MAME/runtime` с бинарником, ROM и DSS/CHD. Для выбора
|
||
stock/sdbg или нестандартной установки используются поля `mameBin`,
|
||
`mameRompath`, `mameDssImage`, `mameSystemHddImage`, `mameBios` профиля launch.
|
||
Для нового fork можно оставить `mameHome` на подготовленной прежней среде и
|
||
задать `"mameBin": "/путь/к/MAME.HT/sprinter"` в launch-конфигурации.
|
||
|
||
Расширение в режиме `auto` использует `~/.pyenv/shims/python` при наличии
|
||
local `.python-version`; для внешнего workspace ищет установленный
|
||
`~/.pyenv/versions/3.12*/bin/python`, затем `.venv/bin/python` и известные
|
||
абсолютные пути Python 3.12. Local-версия передаётся задаче сборки через
|
||
`PYENV_VERSION`, даже если приложение лежит вне workspace. Это не зависит от `PATH` процесса VS Code,
|
||
который мог быть открыт из Dock. Выбор можно переопределить абсолютным путём
|
||
в `sprinterDebugger.pythonCommand`; дополнительные аргументы задаются через
|
||
`sprinterDebugger.pythonArguments`.
|
||
|
||
## Автоматический launch
|
||
|
||
Добавьте в локальный `.vscode/launch.json`:
|
||
|
||
```json
|
||
{
|
||
"version": "0.2.0",
|
||
"configurations": [
|
||
{
|
||
"type": "sprinter-mame",
|
||
"request": "launch",
|
||
"name": "Sprinter MAME: hello",
|
||
"build": "${workspaceFolder}/tests/hello/.sprinter-cc-hello"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
Launcher создаёт временные floppy/HDD/state-каталоги и видимое окно MAME.
|
||
В отдельный `cfg/sprinter.cfg` он записывает только включение обеих клавиатур
|
||
Sprinter: `:` и `:kbd:ms_naturl`. MAME по умолчанию активирует лишь первую,
|
||
поэтому без этой настройки физические клавиши не доходят до DSS `getchar()`.
|
||
Действующая пользовательская конфигурация MAME при этом не копируется.
|
||
Он читает символы текстового экрана из VRAM, находит пустой prompt `X:…>` и
|
||
требует, чтобы тот оставался готовым 0,25 секунды. Только затем ставится
|
||
service-точка `main` и вводится `a:\\HELLO.EXE`. После совпадения PC и
|
||
сигнатуры кода запускается session server, DAP принимает редакторские точки,
|
||
а VS Code показывает остановку на entry. Завершение debug session останавливает
|
||
только созданные ей MAME/server.
|
||
|
||
При reset/state load или потере MAME остановленная сессия становится
|
||
недействительной и DAP завершает её; для новой отладки запускайте F5 заново.
|
||
Проверены внезапная потеря собственного MAME, штатный `machine:exit()` и
|
||
реальная загрузка state при остановке в `main`: каждый раз DAP получает
|
||
`terminated`, после load старые точки удалены. Закрытие SDL3-окна MAME
|
||
вызывает тот же `schedule_exit()`, что `machine:exit()`; прямой UI-клик
|
||
в пробнике не выполнялся. Эти проверки прошли с `sdbg` и опциональным
|
||
`osx` debugger provider.
|
||
|
||
`dssTimeout` задаёт предельное время ожидания prompt (30 эмулируемых секунд).
|
||
`launchAt` можно задать как необязательную нижнюю границу времени запуска;
|
||
наличие prompt всё равно обязательно. Перед вводом сохраняется диагностический
|
||
снимок DSS.
|
||
|
||
Для перенесённого SprPoP его workspace содержит локальный профиль F5:
|
||
|
||
```json
|
||
{
|
||
"type": "sprinter-mame",
|
||
"request": "launch",
|
||
"name": "Sprinter: SprPoP (VS Code)",
|
||
"build": "${workspaceFolder}/build/.sprinter-cc-sprpop",
|
||
"buildTarget": "hdd",
|
||
"appHdd": "${workspaceFolder}/build/hdd/sprpop.chd",
|
||
"launchPath": "d:\\games\\sprpop\\sprpop.exe"
|
||
}
|
||
```
|
||
|
||
Расширение перед F5 запускает `make SRC_DEBUG=1 hdd` в каталоге игры,
|
||
затем загружает EXE с её локального диска. Для такого профиля нужен
|
||
`SPRINTER_ROOT` только на этапе сборки и `MAME_HOME` при запуске.
|
||
|
||
Дополнительные файлы на floppy задаются массивом `data`; для приложения с
|
||
собственным CHD задайте `appHdd`, `launchPath` и `buildTarget: "hdd"`.
|
||
Launcher монтирует временную копию CHD как `-hard2`, а DSS вводит путь EXE
|
||
из `launchPath`. Сам debug EXE остаётся также на временной A: для проверки
|
||
сигнатуры; несовпадение кода на диске с пакетом отладки останавливает launch.
|
||
|
||
## Ручная проверка VS Code
|
||
|
||
В репозитории локально подготовлены два профиля `.vscode/launch.json`:
|
||
`Sprinter: hello (VS Code)` и `Sprinter: hello (VS Code + MAME debugger)`.
|
||
Файл `.vscode` намеренно игнорируется Git и не содержит личных путей.
|
||
|
||
1. Соберите `hello` командой из раздела «Подготовка программы» и запустите
|
||
Extension Development Host одной из команд выше.
|
||
2. Откройте `tests/hello/hello.c` и поставьте breakpoint на строке 31,
|
||
вызове `puts`.
|
||
3. В Run and Debug выберите `Sprinter: hello (VS Code)` и нажмите F5.
|
||
Расширение сначала выполнит `make SRC_DEBUG=1` в `tests/hello` как задачу
|
||
Sprinter Build. После успешной сборки должно появиться окно Sprinter;
|
||
launcher дождётся prompt DSS, введёт
|
||
`A:\\HELLO.EXE` и VS Code остановится в `main`. До этой остановки
|
||
не вводите символы в MAME: они смешаются с командой launcher в DSS.
|
||
4. Проверьте Call Stack, scope Registers и Debug Console. После Continue
|
||
должна сработать подтверждённая точка строки 31.
|
||
5. На остановке проверьте F11 и F10. Курсор должен переходить только после
|
||
фактической остановки CPU, а не сразу после отправки команды. На строке 62
|
||
(`getchar`) нажмите F10, щёлкните окно Sprinter MAME и нажмите латинскую `x`:
|
||
выполнение должно перейти на строку 63. Пока программа ждёт клавишу,
|
||
кнопка Pause в VS Code должна останавливать CPU.
|
||
6. Завершите сессию кнопкой Stop. Затем повторите профиль с `osx`: вместе с
|
||
тем же VS Code-сеансом должно открыться штатное Cocoa-окно debugger MAME.
|
||
|
||
Stop отправляет DAP `disconnect`; launcher завершает запущенный им MAME.
|
||
Чтобы SDL3 не перехватывал `SIGTERM` в необрабатываемое MAME событие Quit,
|
||
launcher задаёт этому процессу `SDL_NO_SIGNAL_HANDLERS=1`, если переменная
|
||
не была задана вручную. Прямой `SIGTERM` и Stop проверены живыми пробниками
|
||
на остановке в `main`; они не требуют отдельного патча MAME.
|
||
|
||
Для одновременного чтения C-сессии из MCP задайте в launch-профиле короткий
|
||
фиксированный `"socket": "/tmp/sprinter-sdbg-hello.sock"` и подключите
|
||
`toolchain/sdbg_mcp.py` после остановки в `main`. MCP использует тот же
|
||
session server; его личные точки не заменяют точки VS Code. Порядок запуска,
|
||
31 доступный инструмент и ограничения совместного управления описаны в
|
||
[руководстве по MCP](sdbg-mcp.md).
|
||
|
||
Команда палитры `Sprinter: Build Active Project` собирает приложение по
|
||
Makefile открытого C-файла. Задачи `Sprinter: Build ...` доступны и в
|
||
`Tasks: Run Task`. Для launch автоматическая сборка включена по умолчанию,
|
||
если из `build` можно найти Makefile с `app.mk`; нестандартный каталог задаётся
|
||
полем `project`, а уже собранный пакет запускается с `"autoBuild": false`.
|
||
Ошибка SDCC с файлом и строкой видна в Problems; ненулевой код задачи
|
||
останавливает F5 до запуска MAME. Явный `preLaunchTask` использует стандартное
|
||
поведение VS Code и отключает автоматическую сборку расширения.
|
||
|
||
Если F5 сообщает, что тип `sprinter-mame` неизвестен, окно запущено без
|
||
`--extensionDevelopmentPath`. Сообщение `Couldn't find a debug adapter
|
||
descriptor` означает, что extension manifest загружен, но расширение не
|
||
активировалось; после изменения `package.json` выполните `Developer: Reload
|
||
Window`. Канал Output → `Sprinter MAME Debug` показывает выбранные Python и
|
||
DAP. При раннем завершении MAME ответ launch включает последние строки
|
||
MAME log. Если сборочный пакет устарел, адаптер должен отказать до запуска
|
||
программы и показать причину, а не использовать старую карту.
|
||
|
||
Логи из исходника без вывода в DSS задаются в C через `<sdbg.h>`;
|
||
полный синтаксис и таблица поддержанных типов — в
|
||
[руководстве по SDBG_LOG](sdbg-log-macros.md):
|
||
|
||
```c
|
||
#include <sdbg.h>
|
||
|
||
volatile int total;
|
||
|
||
/* tag уникален в этом .c; total читает debugger, не приложение. */
|
||
SDBG_LOG(after_increment, "total={total}");
|
||
```
|
||
|
||
При F5 debug-сборка привяжет нулевой asm-якорь к адресу и session server
|
||
поставит logpoint после загрузки DSS. Попадание напишет `total=...` в
|
||
Debug Console VS Code и в debugger console MAME, затем продолжит выполнение.
|
||
В обычной сборке макрос пуст. Поддерживаются только global/static переменные
|
||
доказанного типа, литералы и `{name}`; локальные, выражения и `%d` пока нет.
|
||
`SDBG_LOGIF(tag, flag, "...")` выводит сообщение, если поддержанная
|
||
global/static `flag` ненулевая. Аргументы не исполняются на Sprinter, поэтому
|
||
выражения с побочными эффектами не имеют здесь смысла. В режиме `sdbg`
|
||
журнал MAME существует, но его штатное окно не открывается; для видимого
|
||
окна выберите `osx` ниже. Горячий logpoint может заметно замедлить эмуляцию:
|
||
CPU останавливается на каждом попадании. Если DAP не успевает забрать
|
||
события из ограниченного журнала, Debug Console сообщает число пропусков.
|
||
Текст в VS Code сохраняется точно; MAME console заменяет двойные кавычки
|
||
апострофами и управляющие символы пробелами из-за синтаксиса `printf`.
|
||
|
||
## Родной debugger MAME вместе с VS Code
|
||
|
||
По умолчанию используется project backend `sdbg`: видимо окно Sprinter,
|
||
а отдельное Cocoa-окно debugger не создаётся. Для регистров, дизассемблера,
|
||
памяти и console штатного MAME добавьте в launch-конфигурацию:
|
||
|
||
```json
|
||
"debugger": "osx"
|
||
```
|
||
|
||
Bridge и DAP продолжают работать параллельно. Ручные `go`, изменение точек и
|
||
reset в native console обходят модель состояния VS Code; reset пока нельзя
|
||
использовать из-за lifecycle-crash MAME 0.287.
|
||
|
||
Для MAME без project patch выберите штатный provider:
|
||
|
||
| Платформа/сборка | `debugger` |
|
||
|---|---|
|
||
| macOS | `osx` |
|
||
| Windows native | `windows` |
|
||
| Linux с Qt debugger | `qt` |
|
||
| Linux/SDL без Qt | `imgui` |
|
||
| Автоматический выбор доступного provider | `auto` |
|
||
|
||
`imgui` использует основное графическое окно MAME; launcher уже запускает его
|
||
с `-video soft -window`. `none` немедленно продолжает остановленный CPU, а
|
||
`gdbstub` ждёт протокол GDB, поэтому они не подходят для sdbgbridge.
|
||
|
||
Текущая host-часть sdbg использует `fcntl` и Unix domain sockets. Она работает
|
||
на macOS/Linux. Для native Windows нужны отдельные реализации owner lock,
|
||
локального RPC и launcher; выбор `windows` решает только сторону MAME и не
|
||
обеспечивает работу VS Code-интеграции.
|
||
|
||
Backend `sdbg` уже входит в новый fork `MAME.HT` на базе MAME 0.289
|
||
(Holub/Tolik). Его штатная сборка и имя исполняемого файла:
|
||
|
||
```sh
|
||
cd /путь/к/MAME.HT
|
||
make SUBTARGET=sprinter SOURCES=src/mame/sinclair/sprinter.cpp
|
||
./sprinter -version
|
||
scripts/sprinter/build-variants.sh stock
|
||
scripts/sprinter/build-variants.sh sdbg
|
||
```
|
||
|
||
Результат `make` — `./sprinter`, при запуске система по-прежнему задаётся
|
||
аргументом `sprinter`. Для проверки на имеющихся ROM/DSS можно оставить
|
||
старый `MAME_HOME` и указать `MAME_BIN=/путь/к/MAME.HT/sprinter`.
|
||
В новом fork patch уже зафиксирован в Git; для чистой базы сохранён
|
||
`scripts/sprinter/apply-sdbg-patch.sh`. Варианты копируются в
|
||
`MAME.HT/runtime/bin/stock/sprinter` и `.../sdbg/sprinter`, не подменяя
|
||
активный `MAME_HOME/sprinter`. Старый MAME 0.287 с `mame.arm` также
|
||
поддерживается через явный `MAME_BIN` или прежнюю среду запуска.
|
||
|
||
## Logpoints
|
||
|
||
Обычная команда VS Code **Add Logpoint…** работает без пересборки. В сообщении
|
||
разрешены литералы и простые подстановки:
|
||
|
||
```text
|
||
frame={frame_counter} state={state}
|
||
```
|
||
|
||
Подстановка принимает только имя поддержанной переменной. Выражения,
|
||
format specifier и conversion отклоняются. Если logpoint и обычная точка
|
||
разрешились в один адрес, сообщение печатается, а CPU остаётся остановленным.
|
||
|
||
## Шаги по исходнику
|
||
|
||
F11 выполняет Z80-инструкции до следующей отличающейся C-позиции. F10 на
|
||
каждом машинном шаге использует MAME `over`, поэтому банковский вызов проходит
|
||
целиком. Shift+F11 сначала делает MAME `out`, затем проходит служебный bank
|
||
trampoline до первой C-позиции вызывающей функции. Instruction granularity
|
||
остаётся доступна для точного одиночного шага.
|
||
|
||
Автомат ограничен 512 машинными операциями. Команда шага отвечает VS Code
|
||
сразу; пока `getchar()` или другой вызов ждёт внешнего ввода, MAME продолжает
|
||
работать, а пользователь может нажать клавишу в его окне либо выполнить Pause
|
||
в VS Code. Время ожидания ввода не ограничивается таймаутом исходного шага.
|
||
При достижении предела CPU остаётся остановленным, а Debug Console получает
|
||
сообщение.
|
||
Пользовательская точка внутри шага имеет приоритет; logpoint печатается и шаг
|
||
продолжается с прежней семантикой.
|
||
|
||
## Ручной attach
|
||
|
||
Для общего MAME между CLI и VS Code сначала запустите `sdbg_server.py` либо
|
||
автономный MCP `start_session` и дождитесь `session_status.phase=ready`, затем:
|
||
|
||
```json
|
||
{
|
||
"type": "sprinter-mame",
|
||
"request": "attach",
|
||
"name": "Sprinter MAME: Attach",
|
||
"socket": "/tmp/sprinter-sdbg.sock"
|
||
}
|
||
```
|
||
|
||
Команды server и диагностического CLI приведены в
|
||
[mame-source-debug-status.md](mame-source-debug-status.md). Автономный
|
||
MCP-запуск описан в [sdbg-mcp.md](sdbg-mcp.md); DAP disconnect не завершает
|
||
MAME, созданный MCP, а `stop_session` завершает его вместе с socket.
|
||
|
||
## Ограничения MVP
|
||
|
||
- Reset/restart отключён после найденного crash MAME 0.287 на старом Lua
|
||
callback. Session завершается штатным terminate.
|
||
- Локальные, backtrace, setVariable, watchpoints и чтение неотображённого
|
||
bank-data ещё не реализованы.
|
||
- Если одна инструкция имеет несколько C-маркеров, frame помечается
|
||
`[ambiguous]`; адаптер не выбирает строку молча.
|