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

29 KiB
Raw Blame History

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

Дата: 2026-09-15. План: 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-набор, при ошибке удаляют его, затем заменяют прежний набор и активируют точки.
  • 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 не запускается. Run-команда, расширенные linker/assembler diagnostics, выбор profile/EXTRA_DATA и VSIX остаются следующими шагами.
  • 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; до него для долгой ручной работы нужен один Python-процесс с DebugSession.

Предпочтительный режим для нескольких клиентов — один 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-расширение находится в toolchain/vscode-sprinter-debug/. В Extension Development Host используется attach-конфигурация:

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

В VS Code расширение 0.2.0 запускает адаптер через абсолютный путь к local pyenv shim ~/.pyenv/shims/python и задаёт корень workspace как cwd. Это устраняет зависимость от PATH процесса VS Code, открытого из Dock. Выбранные пути видны в 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 --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 --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-набор: 29 тестов прошли, один Unix-socket тест пропущен только из-за запрета bind в sandbox. Тот же socket path проверен живым DAP-запуском.

Это репро доступности механизма, а не полный 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 и идемпотентный установщик находятся в toolchain/mame-patches/ и toolchain/apply-mame-sdbg-patch.sh; make mame-sdbg собирает и устанавливает бинарник. 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 запущен: сообщает рост у 12 программ относительно текущего эталона, отсутствующие и новые сборки. В списке роста нет пересобранного hello. Дополнительно cat и hello собраны исходной обёрткой из HEAD и новой на тех же runtime/библиотеках: EXE попарно идентичны. Эталон не изменялся. Этот общий check пока не считается прошедшим; расхождение существующих сборок с baseline отделено от побайтовых регрессий новой debug-сборки.

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 не появилось.

Далее по плану: завершить инвалидацию на exit/state load, MCP-адаптер, безопасный restart, расширенные выражения и упаковку VS Code-расширения. Для IDE ещё нужны Run-команда, выбор профиля сборки/данных и расширенная диагностика assembler/linker. Базовый attach уже проверяет принадлежность resident/current-bank к build, но не умеет читать неотображённую physical RAM. Нет автоматического чтения неотображённого bank-data, локальных или backtrace.

Пути исходников в текущем пакете абсолютные. Снимки и уникальные TU уже есть, переносимый source mapping — следующая доработка. Пока библиотечные описания и отдельные форматы CDB не поддержаны, карта сообщает ограничения.