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

22 KiB
Raw Blame History

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.

Подготовка программы

Соберите приложение с полной картой:

pyenv exec make -C tests/hello SRC_DEBUG=1

Рядом с EXE появится .sprinter-cc-hello/manifest.json. При изменении C, заголовка или опций make пересоберёт пакет по хэшу содержимого.

Загрузка development-расширения

Из корня репозитория откройте VS Code с распакованным расширением:

code --extensionDevelopmentPath="/путь/к/VSCode-Sprinter" "$PWD"

Если команда code не добавлена в PATH, на macOS этого проекта доступен полный путь:

"/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:

{
  "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:

{
  "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. Порядок запуска, 27 доступных инструментов и ограничения совместного управления описаны в руководстве по MCP.

Команда палитры 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:

#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-конфигурацию:

"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). Его штатная сборка и имя исполняемого файла:

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… работает без пересборки. В сообщении разрешены литералы и простые подстановки:

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, затем:

{
  "type": "sprinter-mame",
  "request": "attach",
  "name": "Sprinter MAME: Attach",
  "socket": "/tmp/sprinter-sdbg.sock"
}

Команды server и диагностического CLI приведены в mame-source-debug-status.md. Автономный MCP-запуск описан в 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]; адаптер не выбирает строку молча.