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

32 KiB
Raw Blame History

Отладка исходников: реализация и результаты

Дата: 2026-09-16. План: 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. 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:

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:

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:

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

{
  "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. Интегрированный запуск:

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

Воспроизводимые проверки

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: 0x42bb0x42c1; 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 → hellomain: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.

Размерный регресс и оставшаяся работа

make size-check теперь проходит (46 свежих карт). Прежние 12 расхождений разобраны: open() добавил 9 байт после исправления режимов DSS; openenv содержит ещё новый регрессионный тест; прежний размер w3bgfx был снят по несвежей карте. Подробное сравнение с исходной ревизией и правила обновления эталона — в плане разделения.

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 не поддержаны, карта сообщает ограничения.