Files
Sprinter-SDCC/docs/vscode-sprinter-debug.md
T
2026-09-17 23:17:38 +03:00

337 lines
22 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.
# 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. Порядок запуска,
29 доступных инструментов и ограничения совместного управления описаны в
[руководстве по 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]`; адаптер не выбирает строку молча.