Sprinter: добавить отладку C-исходников и интеграцию VS Code
This commit is contained in:
@@ -471,6 +471,21 @@ text_cursor load_cursor/get_cursor(mouse_cursor_t*) set_sensitivity
|
||||
get_sensitivity_x/y video_mode_changed`. Sensitivity = делитель
|
||||
(меньше = быстрее). Solid-C алиасы ms_* включены.
|
||||
|
||||
## <sdbg.h> — логи source debugger без кода вывода Sprinter
|
||||
|
||||
`SDBG_LOG(tag, "text={global}")` и
|
||||
`SDBG_LOGIF(tag, global_flag, "text={global}")` создают авторские
|
||||
точки журналирования в выбранном `--src-debug` TU. `tag` — уникальный
|
||||
C-идентификатор внутри TU; условие `LOGIF` пока только имя поддержанной
|
||||
global/static переменной, ненулевое значение означает вывод. Подстановки
|
||||
`{name}` читаются отладчиком при попадании; локальные и C-выражения пока
|
||||
не поддержаны. Обычная сборка превращает макросы в пустое выражение.
|
||||
Полный контракт и планы форматирования —
|
||||
[руководство по SDBG_LOG](sdbg-log-macros.md).
|
||||
Аргументы не вычисляются кодом приложения, поэтому побочные эффекты в них
|
||||
недопустимы. Сообщение видно в MAME debugger console и VS Code Debug Console
|
||||
при подключённой source-debug сессии; без неё макрос сам ничего не печатает.
|
||||
|
||||
## <sprinter.h> — платформа
|
||||
|
||||
Константы портов (PORT_PAGE_W0..W3, PORT_RGADR, PORT_RGMOD), номера
|
||||
|
||||
+20
-1
@@ -239,7 +239,6 @@ end
|
||||
Lua-REPL — удобно нащупывать поля/тайминги вживую перед скриптованием.
|
||||
- **Второй видеорежим/варианты BIOS** — при необходимости менять
|
||||
`COMMON_ARGS`.
|
||||
```
|
||||
|
||||
## 10. Тайминги MCP-моста (run_bridge.sh) — НЕ ждать дольше
|
||||
|
||||
@@ -258,3 +257,23 @@ end
|
||||
Пересобрал HDD-образ (`make hdd`) → MAME **обязан** полный рестарт
|
||||
(`chdman -f` даёт новый inode; см. memory `mame_hdd_rebuild_restart`):
|
||||
остановка через `exit` в дебаггере, затем `run_bridge.sh` заново.
|
||||
|
||||
## 11. Карта C/asm для отладки
|
||||
|
||||
`SRC_DEBUG=1` в app.mk создаёт проверенный пакет исходников и адресов без
|
||||
изменения EXE в проверенных конфигурациях. Пер-модульный вариант:
|
||||
`SRC_DEBUG_FILES=helper.c`. Команды карты и результаты живых экспериментов:
|
||||
[mame-source-debug-status.md](mame-source-debug-status.md); полный план:
|
||||
[mame-source-debug.md](mame-source-debug.md). Базовый C-attach/where/точки и
|
||||
чтение простых переменных доступны через `toolchain/sdbg_session.py`.
|
||||
`sdbg_server.py`, DAP MVP, VS Code launch и development-расширение уже есть;
|
||||
безопасный restart и MCP C-уровня пока не готовы. `-debugger none`
|
||||
автоматически продолжает CPU и не подходит для ожидания команд пошаговой
|
||||
сессии.
|
||||
|
||||
Source-debug launcher ждёт до 10-й секунды эмулируемого времени, сохраняет
|
||||
снимок готового DSS, только затем вооружает entry breakpoint и вводит команду
|
||||
EXE. Пользовательские точки создаются после проверенного `main`, когда
|
||||
`_bank_pages` уже заполнена. В обычной конфигурации это даёт 0 попаданий на
|
||||
адрес entry во время загрузки DSS. Автоматического распознавания приглашения
|
||||
`C:\\>` пока нет; `launchAt` в VS Code можно увеличить.
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
# Отладка исходников: реализация и результаты
|
||||
|
||||
Дата: 2026-09-15. План: [mame-source-debug.md](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](sdbg-log-macros.md). 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`:
|
||||
|
||||
```sh
|
||||
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:
|
||||
|
||||
```sh
|
||||
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:
|
||||
|
||||
```sh
|
||||
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-конфигурация:
|
||||
|
||||
```json
|
||||
{
|
||||
"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.
|
||||
Интегрированный запуск:
|
||||
|
||||
```json
|
||||
{
|
||||
"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](vscode-sprinter-debug.md).
|
||||
|
||||
## Воспроизводимые проверки
|
||||
|
||||
```sh
|
||||
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`: `0x42bb` → `0x42c1`; 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 → `hello` → `main: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 не загружать: общая арбитрирующая сессия ещё не реализована.
|
||||
|
||||
## Размерный регресс и оставшаяся работа
|
||||
|
||||
`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 не поддержаны, карта сообщает ограничения.
|
||||
@@ -0,0 +1,817 @@
|
||||
# Отладка C-приложений Sprinter в MAME и VS Code
|
||||
|
||||
Редакция: **2026-09-14**, после технического ревью плана от 2026-09-13.
|
||||
Статус: **ЧАСТИЧНАЯ РЕАЛИЗАЦИЯ**. Сборка/оффлайновая карта, транспорт,
|
||||
live C-сессия, постоянный server, DAP и VS Code launch MVP реализованы; результаты —
|
||||
[mame-source-debug-status.md](mame-source-debug-status.md).
|
||||
Полный lifecycle/restart и расширенная отладка ещё не реализованы.
|
||||
Основной маршрут включает CLI/MCP и VS Code через DAP;
|
||||
GDB — самостоятельное расширение по отдельной потребности.
|
||||
|
||||
### Статус платформ
|
||||
|
||||
- **macOS** — полный текущий цикл build → DSS → MAME → DAP → VS Code
|
||||
проверен живыми запусками. Доступны backend `sdbg` и совместный режим
|
||||
VS Code + штатное окно debugger через `debugger: "osx"`.
|
||||
- **Linux** — архитектура и используемые host-механизмы совместимы; для
|
||||
штатного окна MAME выбирается `qt` либо `imgui`. Полный живой прогон на
|
||||
Linux ещё требуется, поэтому статус остаётся экспериментальным.
|
||||
- **Windows** — полный функционал пока **не поддерживается**. Значение
|
||||
`debugger: "windows"` выбирает только штатное окно debugger MAME, но
|
||||
текущие owner lock (`fcntl`), Unix domain socket, shell-команды запуска и
|
||||
тесты рассчитаны на macOS/Linux. Нужны Windows-реализации блокировки и
|
||||
локального RPC, переносимый launcher и отдельные end-to-end тесты. До
|
||||
этого launch/attach из VS Code на native Windows не считаются рабочими.
|
||||
|
||||
Прежние фазы 0–5A/5B заменены этапами §10. IDE учитывается в архитектуре
|
||||
с самого начала; прежняя «опциональная фаза 4» теперь распределена между
|
||||
этапами IDE и расширенной диагностики. Это изменение состава плана,
|
||||
а не разрешение выполнять ранее отложенную реализацию.
|
||||
|
||||
## 1. Цель и критерий доверия
|
||||
|
||||
Целевой цикл: редактирование C → сборка sprinter-cc → упаковка приложения
|
||||
и данных → запуск MAME/DSS → остановка на main → отладка из VS Code,
|
||||
CLI или MCP.
|
||||
|
||||
Планируемые возможности:
|
||||
|
||||
- Точки по функции/строке C, условия, счётчики попаданий, логпоинты без
|
||||
пересборки, watchpoint на поддержанные объекты.
|
||||
- Текущий исходник, сгенерированный `.asm`, реальные инструкции, регистры.
|
||||
- Чтение/изменение глобалов и статиков с учётом типов, банков и окон.
|
||||
- Шаг по инструкции и исходнику; затем next/stepOut, проверенные локальные
|
||||
переменные и восстановление стека в поддержанных случаях.
|
||||
- Структурированные логи для автотестов/IDE, комментарии в родном
|
||||
дизассемблере; позднее покрытие и профилирование.
|
||||
- Общая семантика адресов и одна сессия для всех интерфейсов.
|
||||
|
||||
**Критерий доверия:** неизвестная строка, неподтверждённая точка или
|
||||
недоступная переменная лучше правдоподобного неверного результата.
|
||||
Карта оптимизированного кода не обещает отдельную инструкцию для каждого
|
||||
оператора C и наличие всех переменных в любой момент исполнения.
|
||||
|
||||
Связанные файлы: [mame-autotest.md](mame-autotest.md),
|
||||
[sprinter-cc](../bin/sprinter-cc), [app.mk](../app.mk),
|
||||
[mame_interactive.py](../toolchain/mame_interactive.py),
|
||||
[bank.s](../runtime/bank.s), [crt0_banked.s](../runtime/crt0_banked.s).
|
||||
Локальная интеграция: `mame/sources/MAME/plugins/mamebridge/init.lua`,
|
||||
`mame/sources/MAME/src/mame_mcp.py`.
|
||||
|
||||
## 2. Доказательства и пределы выводов
|
||||
|
||||
### 2.1 Результаты первоначальной разведки
|
||||
|
||||
Ниже сохранены результаты редакции 2026-09-13. При обновлении плана
|
||||
сборочные эксперименты не повторялись. Перед реализацией их требуется
|
||||
воспроизвести с сохранением репро, команд и версий.
|
||||
|
||||
| Эксперимент | Зафиксированный результат | Предел вывода |
|
||||
|---|---|---|
|
||||
| `sdcc --debug`, probe | `.exe` идентичен; `_CODE` 3354 → 3354 | Один пример, не гарантия для всех программ |
|
||||
| `tests/banked`, huge, 3 TU, 2 банка | `.ihx` идентичен сборке без debug | rc=1 из-за C$; корректность карты не доказана |
|
||||
| `.globl _dbg_x` и `_dbg_x:` в inline asm | Ошибка локальных меток `NNNNN$` в проверенных циклах/ветвлениях | Такую форму якоря не используем |
|
||||
| `.globl _dbg_x` и `_dbg_x = .` | Релоцируемый символ; в probe байты идентичны, якорь у `ret` | Отдельно проверить влияние inline asm на оптимизацию |
|
||||
| `;;SDBG x` | Код идентичен, адреса в `.map` нет | Для адреса нужен листинг/дополнительная привязка |
|
||||
| SDCC 4.5.0 z80, `--out-fmt-elf` | Неизвестная опция | Готового ELF/DWARF этого target ожидать нельзя |
|
||||
|
||||
SDCC сам использует `sym = .`. Это подтверждает пригодность ассемблерной
|
||||
формы, но не доказывает отсутствие влияния пользовательского inline asm
|
||||
на оптимизатор C и peephole. Раздельно проверяем идентичность бинарника,
|
||||
точность карты и скорость программы под debugger.
|
||||
|
||||
Примеры `.cdb` из первоначальной разведки:
|
||||
|
||||
```text
|
||||
L:C$probe.c$18$3_0$48:8236 C-строка → адрес линковщика
|
||||
L:A$probe$111:8236 строка .asm → адрес
|
||||
L:G$main$0$0:8222 начало функции
|
||||
L:Fprobe$counter$0_0$0:8E40 статик модуля
|
||||
L:G$total$0_0$0:8E42 глобал
|
||||
S:G$total$0_0$0({2}SI:S),E,0,0 описание типа
|
||||
S:Lprobe.add$s$1_0$44({2}SI:S),R,0,0,[e,d] описание регистровой локальной
|
||||
```
|
||||
|
||||
Парсер разбирает семейства `L:`, `S:`, `F:` и диагностирует неизвестные
|
||||
записи. Построчная форма не делает семантику типов, scope, инлайнинга
|
||||
и границ функций тривиальной. Семантику конечных адресов установить репро,
|
||||
внутренние диапазоны нормализовать к `[start, end)`.
|
||||
|
||||
### 2.2 Проверено чтением текущих локальных исходников
|
||||
|
||||
- MAME поддерживает breakpoint с condition/action, printf/logerror/tracelog,
|
||||
source/debugscript, comadd и debugger без окна (`-debugger none`).
|
||||
`none` автоматически вызывает go() при остановке: он пригоден для
|
||||
автономных logpoint actions, но не для ожидания интерактивных команд.
|
||||
Подтверждено исходниками и живым репро; прототип использует osx debugger.
|
||||
- `device_debug::compute_opcode_crc32` в `src/emu/debug/debugcpu.cpp`
|
||||
считает CRC **одной инструкции**. Одинаковый `ret` по одному адресу
|
||||
в двух банках имеет одинаковый ключ комментария. CRC не определяет банк.
|
||||
- `execute_trace` в `src/emu/debug/debugcmd.cpp` включает трассировку CPU,
|
||||
а не просто открывает файл для сообщений.
|
||||
- Мост содержит `clog` и обслуживает `register_periodic` при stopped.
|
||||
Комментарий у `do_step` прямо указывает: инструкция выполняется после
|
||||
возврата callback, поэтому чтение PC сразу после step даст старый PC.
|
||||
- `emu.symbol_table` в `luaengine_debug.cpp` создаёт отдельную таблицу;
|
||||
готового symadd для консоли нет. C-имена разрешаем на своей стороне.
|
||||
- В штатных debug views нет окна C-исходника. Для родного UI используем
|
||||
комментарии, для полноценного исходника — IDE.
|
||||
- sprinter-cc размещает банки по `(bank << 16) | 0xC000` в huge и
|
||||
`(bank << 16) | 0x4000` в big. Есть `--bank-data`: банки содержат и данные.
|
||||
- `_bank_pages[1..N]` заполняется crt0 при загрузке банков, а не до entry.
|
||||
Драйвер экспортирует PG0..PG3 и другие состояния отображения.
|
||||
- `bootstrap_r/w` в sprinter.cpp при обычном исполнении перенаправляют
|
||||
логический адрес в `0x10000 | addr`; загрузочный режим отличается.
|
||||
Адрес CPU и адрес пространства MAME нельзя смешивать.
|
||||
|
||||
### 2.3 Пакет доказательств этапа 0
|
||||
|
||||
Сохранить небольшие исходники-репро, команды, полные диагностики,
|
||||
версии SDCC/ассемблера/линкера, commit MAME и патчи, хеши бинарников,
|
||||
выбранные фрагменты `.asm/.map/.cdb`, протокол живой проверки MAME.
|
||||
Большие артефакты допускается хранить вне Git с командой воспроизведения.
|
||||
Ссылки на меняющиеся номера строк MAME заменять именами функций и
|
||||
закреплённой ревизией в отчёте эксперимента.
|
||||
|
||||
## 3. Реестр решений: что заменено и почему
|
||||
|
||||
| ID | Риск / прежнее предложение | Решение и обоснование |
|
||||
|---|---|---|
|
||||
| R01 | Игнорировать rc=1 при C$, если есть `.ihx` | Устранить конфликт debug-имён; исправность кода не доказывает карту, старый `.ihx` может пережить ошибку |
|
||||
| R02 | «Полная карта, ноль влияния» | Раздельные регрессии бинарника, карты и overhead; гарантия ограничена проверенными конфигурациями |
|
||||
| R03 | Адрес — одно число, банки только у кода | Типизированные адреса и snapshot отображения, включая bank-data |
|
||||
| R04 | Первый адрес строки / следующая строка вместо якоря | Все доказанные позиции, фактическое разрешение и unverified; другой путь исполнения не заменяет удалённую точку |
|
||||
| R05 | Арминг по совпадению PC с entry | Проверка образа, готовности runtime/банков и выхода; DSS использует те же адреса |
|
||||
| R06 | Цикл step внутри Lua | Асинхронный автомат с лимитами/отменой: шаг исполняется после callback |
|
||||
| R07 | Разные ID файлов достаточно для нескольких клиентов | Общая сессия и арбитраж; ID не устраняют гонки run/stop |
|
||||
| R08 | Безусловный `g` в логпоинте, clear-all | Реестр владельцев и диспетчер попаданий; логи не отменяют остановку |
|
||||
| R09 | `trace` по умолчанию для логов | Отдельный журнал; instruction trace имеет другое назначение и стоимость |
|
||||
| R10 | Формат C printf можно передать MAME | Ограниченная грамматика, типы, знак, длина строк и CP866; форматы различаются |
|
||||
| R11 | CRC скрывает чужой банк | Сначала резидентные комментарии; затем обновление при remap или расширение ключа |
|
||||
| R12 | Каталог сборки достаточен для attach | Manifest, хеши и фиксированный пакет сессии; исключить смешение сборок |
|
||||
| R13 | Все DAP-запросы сразу, стек эвристикой | Честные capabilities и один достоверный frame в MVP; сложные функции отдельно |
|
||||
| R14 | GDB не имеет логов/банков | Учесть dprintf/overlays; DAP выбран за модель Sprinter и общую сессию |
|
||||
| R15 | Сокет автоматически ускорит шаг | Сначала измерить RTT и execution latency; callback сокетом не исправляется |
|
||||
| R16 | Ручные правки в игнорируемом mame/ | Версионируемые исходники/патчи и установщик с проверкой расхождений |
|
||||
| R17 | Библиотеки/локальные автоматически следуют из CDB | Эксперимент архивной линковки и доказанные location ranges |
|
||||
| R18 | Один launch.json завершает интеграцию | Сборка, диагностики, язык, упаковка данных, запуск DSS и повторный F5 |
|
||||
| R19 | debugger none держит stopped для IDE | В none wait_for_debugger вызывает go(); использовать родной debugger, для режима без окон реализовать отдельный backend ожидания |
|
||||
| R20 | `debugger: "windows"` означает поддержку всей цепочки на Windows | Разделить MAME provider и host-инструменты: Windows provider существует, но Python/DAP launch/attach остаются неподдержанными до замены `fcntl`/Unix sockets, переноса launcher и живых тестов |
|
||||
| R21 | Короткий sleep в `sdbg` достаточно для окна MAME | При stopped CPU обычный frame loop не обрабатывает события GUI, и macOS помечает приложение «не отвечает». В `wait_for_debugger` периодически вызывать event pump выбранного OSD (SDL3: `input_update` + `process_events`, native macOS: штатный poll), ограничив частоту; проверить Cmd-Tab/Dock при длительной остановке и сохранение DAP/клавиатурного ввода |
|
||||
| R22 | CDB pointee type достаточен для выбора строки или скаляра | SDCC 4.5 кодирует проверенные `char *` и `uint8_t *` одинаково (`DG,SC:U`). В финальном debug-пакете сохранять исходный declared type/typedef chain, сверять его с CDB и размером; при неопределённости явно `unavailable` либо запросить типовую аннотацию. Отдельный fixture должен доказать, что `char *` читается как строка, а `int8_t *`/`uint8_t *` — как один 8-битный объект |
|
||||
|
||||
## 4. Архитектура, сборка и пакет
|
||||
|
||||
```text
|
||||
sprinter-cc → .exe + отладочный пакет + manifest
|
||||
│
|
||||
sdbg: карта и типы
|
||||
│
|
||||
CLI ────────┐ │
|
||||
MCP ────────┼──→ общая сессия отладки ──→ мост MAME ──→ CPU/debugger
|
||||
VS Code/DAP ┘ состояние, точки,
|
||||
события, загрузка
|
||||
```
|
||||
|
||||
### 4.1 Ответственность компонентов
|
||||
|
||||
- `toolchain/sdbg.py` — публичный CLI; реализацию разделить на модули
|
||||
пакета/парсера, адресов, типов, точек, сессии и транспорта по мере роста.
|
||||
- Карта — чистые преобразования артефактов без команд MAME.
|
||||
- Общая сессия — процесс для подключения CLI/MCP/DAP, хранит build ID,
|
||||
generation, состояние, владельца управления и реестр точек.
|
||||
- Мост — низкоуровневые действия, согласованные снимки, асинхронные
|
||||
операции и события; второго парсера CDB в Lua нет.
|
||||
- DAP/MCP — тонкие адаптеры; существующие input/screenshots сохраняются,
|
||||
команды изменения исполнения проходят арбитраж.
|
||||
- `.dbgs` — ограниченный автономный экспорт, не второй менеджер сессии.
|
||||
Неподдержанную семантику экспорт отклоняет с объяснением.
|
||||
|
||||
### 4.2 Флаги и manifest
|
||||
|
||||
`--src-debug` включает debug для всех пользовательских TU;
|
||||
повторяемый `--src-debug-file FILE` — для выбранных. Режимы взаимоисключающие,
|
||||
неизвестный FILE — ошибка. Необязательный аргумент прежнего флага убран,
|
||||
поскольку он неоднозначен рядом с позиционными `.c`.
|
||||
`--debug` остаётся DEBUG_RT. В app.mk: `SRC_DEBUG := 1` либо
|
||||
`SRC_DEBUG_FILES := a.c b.c`. Обычные сборки прежние; профиль IDE явно
|
||||
включает карту, а не меняет defaults всего проекта.
|
||||
|
||||
Пакет в `.sprinter-cc-<name>/`: `.cdb/.map/.noi/.ihx/.asm`, нужные листинги,
|
||||
manifest и индекс `.sdbg.json`. Конфигурация и содержимое входов входят
|
||||
в ключ пересборки: смена debug/fast/safe/memory не оставляет stale artifacts.
|
||||
Параллельные сборки одного output сериализуются или используют разные
|
||||
каталоги. Пакет публикуется только после успешных проверок, атомарно.
|
||||
|
||||
Manifest: schema version, build ID, хеш `.exe` и артефактов, версии
|
||||
инструментов, команды/флаги, библиотеки, TU и исходники/заголовки с хешами,
|
||||
соответствующие `.asm`, entry, секции, окна, банки и полнота debug-покрытия.
|
||||
Адрес `>=0x10000` не объявляется банком без проверки секции.
|
||||
Неподдержанное размещение диагностируется, не угадывается по имени режима.
|
||||
|
||||
Пути относительно корня сборки, уникальный TU ID, поддержка одинаковых
|
||||
basename и source path mapping при переносе проекта. IDE проверяет хеши;
|
||||
при расхождении показывает stale source или сохранённый снимок через DAP
|
||||
source. Сессия фиксирует пакет: новая сборка не меняет карту старого образа.
|
||||
|
||||
### 4.3 Ошибки CDB
|
||||
|
||||
Основное решение R01: проверить уникализацию отладочных имён по TU до
|
||||
линковки в `.asm` с согласованным преобразованием всех связанных CDB-записей.
|
||||
Если формат не позволяет надёжный внешний проход, подготовить патч SDCC.
|
||||
Не изменять публичные C/asm-символы и инструкции. Выбор способа — результат
|
||||
репро этапа 0, а не утверждение, что простое переименование уже достаточно.
|
||||
|
||||
До исправления допустим пер-модульный режим только при успешной линковке;
|
||||
manifest перечисляет отсутствующие TU. Неполный выбранный набор отличается
|
||||
от повреждённой карты. Повреждённая карта не публикуется.
|
||||
Автоматического превращения rc=1 в успех по наличию `.ihx` не будет.
|
||||
Неуспешные артефакты можно сохранять для исследования отдельно, но обычный
|
||||
attach их не принимает. Полные диагностики сохраняются, включая pipefail.
|
||||
|
||||
### 4.4 Библиотеки
|
||||
|
||||
Проверить извлечение `S/F/L` и путей из архивов libc/libbgi. После успеха
|
||||
добавить явный режим debug-артефактов библиотек с отдельным ключом кэша,
|
||||
сохранив fast/safe и «одна функция — один модуль». Проверить DCE, типы,
|
||||
строки, отсутствие коллизий и идентичность кода. До этого библиотечный
|
||||
код доступен как asm/символы без выдуманных C-строк.
|
||||
|
||||
## 5. Адреса, исходники, переменные и выражения
|
||||
|
||||
### 5.1 Адресная модель
|
||||
|
||||
`CodeLocation/DataLocation`: build ID, секция, адрес линковщика, logical
|
||||
CPU address, bank ID приложения при наличии, окно и offset.
|
||||
Физическая страница — свойство сессии, не константа пакета.
|
||||
`MappingSnapshot`: generation остановки, PC/SP, регистры, PG0..PG3,
|
||||
остальные нужные биты отображения, готовность `_bank_pages`.
|
||||
|
||||
```text
|
||||
line_locations(source_id, line, function_id=None) → список CodeLocation
|
||||
resolve_pc(pc, mapping_snapshot) → SourceLocation | Unknown
|
||||
asm_at(code_location) → номер/текст asm и происхождение
|
||||
function_at(code_location) → функция | Unknown
|
||||
resolve_symbol(name, module_id=None) → Symbol | Ambiguous | Unknown
|
||||
read_variable(symbol, snapshot) → TypedValue | Unavailable
|
||||
```
|
||||
|
||||
Backend различает logical CPU memory, пространство MAME и physical RAM.
|
||||
Преобразование `0x10000 | addr` инкапсулировано в драйверном backend.
|
||||
Bootstrap/configuration имеет отдельную семантику и не допускает обычный
|
||||
attach приложения.
|
||||
|
||||
Банковая точка проверяет страницу окна против `_bank_pages[N]` после
|
||||
подтверждения таблицы и резидентного контекста приложения. huge — W3,
|
||||
big — W1. Проверить достаточность PGn с учётом CNF и других битов драйвера;
|
||||
простое равенство — базовый случай, не доказанный полный контракт.
|
||||
Manual-размещения допускаются по фактической карте и поддержке backend.
|
||||
|
||||
### 5.2 Разрешение строк
|
||||
|
||||
Хранить все позиции строки и диапазоны инструкции/функции/секции.
|
||||
Breakpoint строки по умолчанию покрывает все доказанные позиции исполнения;
|
||||
дополнительно можно выбрать функцию/экземпляр. Resolver возвращает реальные
|
||||
адреса, фактическую строку и причины неоднозначности/переноса.
|
||||
Для пустой или удалённой строки можно предложить ближайшую позицию в том
|
||||
же контексте, но не подтверждать её молча как точное совпадение.
|
||||
Без допустимого соответствия — unverified с объяснением.
|
||||
|
||||
addr2line не распространяет предыдущую метку через конец функции, дыру,
|
||||
секцию или банк. Inline-экземпляры и неоднозначности сохраняются, вместо
|
||||
выбора первого TU. Привязка точного макроса рассматривается отдельно (§7.2).
|
||||
|
||||
### 5.3 Типы, память и watchpoint
|
||||
|
||||
MVP: доказанные целочисленные типы, указатели, глобалы/статики. Одинаковые
|
||||
имена требуют квалификации модулем. Затем массивы, структуры, битовые поля
|
||||
и другие типы с fixtures. Размеры, знак, byte order и ABI задаёт target
|
||||
SDCC, не хост. Неподдержанный тип отображается raw bytes с диагностикой.
|
||||
|
||||
Регистры/память/mapping читаются в одной остановке, пакетные запросы
|
||||
привязаны к generation. После resume старые handles и snapshots недействительны.
|
||||
Запись требует stopped, актуальную generation, проверку диапазона/типа
|
||||
и известного отображения. Порты показывать по экспортированному состоянию;
|
||||
отладочные чтения отключают side effects там, где backend это поддерживает.
|
||||
|
||||
Неотображённый банк читается через проверенный доступ к physical RAM без
|
||||
переключения страниц приложения; до реализации — Unavailable. Один
|
||||
16-битный указатель не содержит достаточного bank ID: его нельзя выдумывать.
|
||||
Чтение/запись через границу окна обрабатывается явно.
|
||||
|
||||
Для watchpoint экспериментом установить пространство/alias реального пути
|
||||
CPU и момент остановки до/после записи. PC-источник, старое и новое значение
|
||||
показывать лишь при достоверном получении: текущий PC может отличаться от
|
||||
адреса записавшей инструкции. Банковый watchpoint учитывает mapping;
|
||||
до доказательства не объявлять его поддержанным.
|
||||
|
||||
### 5.4 Выражения
|
||||
|
||||
Один парсер для condition/logMessage/evaluate/watches: имена, integer
|
||||
literals, `$`-регистры, ограниченные арифметические/битовые/сравнительные
|
||||
операции. Документировать грамматику, приоритеты, знаковость и ошибки.
|
||||
Поля/индексы/разыменование добавлять по поддержке типов. По умолчанию
|
||||
evaluate без присваиваний, вызовов C и других побочных эффектов.
|
||||
|
||||
Raw MAME expressions/commands — отдельный явно обозначенный режим;
|
||||
это не C. Изменяющие команды также требуют управления и синхронизации.
|
||||
Команды генерировать из проверенного AST с экранированием строк/путей,
|
||||
не конкатенацией произвольного текста в action.
|
||||
|
||||
## 6. Общая сессия и управление исполнением
|
||||
|
||||
### 6.1 Жизненный цикл
|
||||
|
||||
```text
|
||||
disconnected → waiting_load → verifying_image → runtime_initializing
|
||||
→ stopped ↔ running
|
||||
→ exited
|
||||
reset / state load / потеря связи → invalidated → повторная проверка
|
||||
```
|
||||
|
||||
Совпадение PC с entry — только кандидат загрузки. Подтверждение сочетает
|
||||
build ID выбранного файла, протокол запуска и сравнение неизменяемых
|
||||
участков RAM по карте. Хеш `.exe` на хосте сам по себе RAM не проверяет.
|
||||
Изменяемые crt0 данные/таблицы исключаются; сигнатуры и точки проверки
|
||||
определяются для конкретного runtime.
|
||||
|
||||
На entry активируются только доказанные резидентные точки. Банковые —
|
||||
после загрузки и проверки `_bank_pages`. На main runtime должен быть готов.
|
||||
Отладка crt0 — отдельный режим с asm и постепенным появлением областей.
|
||||
Частичная ошибка загрузки не переводит сессию в ready.
|
||||
|
||||
Выход через runtime/ESTEX и возврат в DSS деактивируют точки приложения.
|
||||
Проверить обычный/аварийный выход и обход штатного exit. Если контекст
|
||||
невозможно уверенно распознать — invalidated, а не продолжение со старой картой.
|
||||
Reset/state load/restart сбрасывают mapping, temporary points, handles,
|
||||
generation. Attach к работающей программе сначала останавливает CPU,
|
||||
проверяет образ и runtime; неизвестную сборку не принимает молча.
|
||||
|
||||
### 6.2 Управление и реестр точек
|
||||
|
||||
Один клиент владеет run/step/write/input; остальные наблюдают согласованные
|
||||
данные. Передача управления явная. Потеря клиента снимает lease и отменяет
|
||||
его незавершённые операции согласно политике сессии. Screenshots доступны
|
||||
наблюдателям; ввод влияет на приложение и арбитрируется. Изменения run/stop
|
||||
из родного UI MAME отражаются событиями.
|
||||
|
||||
Реестр: logical breakpoint ID, MAME IDs, owner, build ID/generation,
|
||||
вид (user/log/temporary/service), адреса/условия. Clear-all ограничен owner.
|
||||
Повторная загрузка файла точек заменяет его набор, не дублирует его.
|
||||
|
||||
Совпавшие точки обслуживает диспетчер: вычислить условия, записать логи,
|
||||
собрать причины остановки, продолжить только если их нет. User breakpoint,
|
||||
pause и ошибка шага имеют приоритет над автоматическим `g`.
|
||||
Изменения точек в родном UI требуют сверки; если backend не может
|
||||
гарантировать совместное управление, сообщить конфликт.
|
||||
|
||||
### 6.3 Протокол и транспорт
|
||||
|
||||
Protocol version/capabilities, session ID, request ID, generation,
|
||||
ответы и упорядоченные события stop/run/output/reset/exit/error.
|
||||
Ответ на установку шага значит «принят», не «CPU уже шагнул».
|
||||
Нужны timeout/cancel, snapshot/varbatch, EOF/disconnect и защита от повторного
|
||||
исполнения: write/continue после timeout не повторяется вслепую.
|
||||
|
||||
Сначала измерить файловый IPC: RTT, pause→snapshot, step→stopped,
|
||||
periodic при running/stopped. Файловый backend: отдельный каталог сессии,
|
||||
один писатель backend, атомарная публикация запросов/ответов, очистка stale
|
||||
и журнал событий с курсором. Разделение диапазонов ID независимых MCP
|
||||
заменяется одним владельцем backend: оно не решало гонки состояния.
|
||||
|
||||
Сокет реализует тот же протокол при подтверждённом выигрыше. MAME имеет
|
||||
пример `emu.file` socket в plugins/gdbstub, но неблокирующий ввод/вывод
|
||||
при stop проверяется отдельно. Framing, частичные сообщения, лимит очереди,
|
||||
reconnect, loopback по умолчанию обязательны. Callback не ждёт клиента
|
||||
блокирующим чтением. Сокет не исправляет задержку исполнения инструкции.
|
||||
|
||||
### 6.4 Шаги
|
||||
|
||||
Сначала надёжный instruction step. Source-step — асинхронный автомат:
|
||||
задать действие, вернуть управление MAME, дождаться фактической остановки,
|
||||
получить snapshot, решить следующий шаг. Для скорости применить temporary
|
||||
breakpoints на доказанных границах либо C++ hook, если Lua periodic
|
||||
недостаточен по измерениям.
|
||||
|
||||
StepIn идёт к следующей доступной позиции исходника с заходом в вызов.
|
||||
Next обходит вызов только при распознанной семантике; stepOut требует
|
||||
достоверного контекста возврата. Сравнивать source/TU/function/bank и
|
||||
исполняемую позицию, не один номер строки. Повтор строки в цикле не должен
|
||||
вечно ждать смены номера.
|
||||
|
||||
Составной шаг имеет предел инструкций/wall time, отмену и итоговую причину.
|
||||
Код без исходников, HALT, ISR, рекурсия, tail call, bank trampolines,
|
||||
BIOS rst 08 и ESTEX rst 10 имеют явную политику. Для неизвестного случая
|
||||
допустим отказ или переход к asm с объяснением; stepIn не выдаётся за next.
|
||||
Системный код пропускается только при безопасном способе дождаться возврата.
|
||||
|
||||
## 7. Логи, макросы и расширенная диагностика
|
||||
|
||||
### 7.1 Внешние точки и журнал
|
||||
|
||||
Основной путь — версионируемый внешний файл: ID, source/function/location,
|
||||
condition, hit condition, сообщение и выражения. Тот же resolver и реестр,
|
||||
что у VS Code; пересборка не требуется. Адреса пересчитываются для новой
|
||||
сборки из сохранённой исходной привязки. DAP logMessage переводится в эту
|
||||
модель, не образует отдельный движок.
|
||||
|
||||
Журнал: sequence, build/session ID, host time, доступное emulated time,
|
||||
PC, bank/page, location, tag и типизированные значения. JSONL — автотестам,
|
||||
текст — CLI, DAP output — IDE, курсор — MCP. Ограничить объём/частоту,
|
||||
предусмотреть ротацию или bounded buffer, счётчик потерь, flush на stop/exit.
|
||||
clog остаётся для сообщений MAME, но хвост консоли не считается полным
|
||||
надёжным журналом приложения.
|
||||
|
||||
Форматирование — задача финального этапа §10, а не свойство текущего
|
||||
`{name}`. Целевой ограниченный набор включает decimal/hex, ширину,
|
||||
символ и строку; MAME `%d/%x/%X/%c/%s/%%` может быть форматом совместимого
|
||||
экспорта, но не передаётся напрямую как C-макрос. Принято это разделение,
|
||||
потому что formatter обязан проверять тип/знак, размер, pointer против
|
||||
массива, границы и кодировку, а CDB не отличает `char *` от `uint8_t *`
|
||||
(R22). Для строк нужны bounded read до NUL и проверка CP866→UTF-8;
|
||||
raw bytes имеют отдельное представление. Значения собираются до resume.
|
||||
Горячие логи могут исполняться в мосте по скомпилированному описанию;
|
||||
стоимость измеряется отдельно.
|
||||
|
||||
Console/logerror допустимы для совместимого экспорта. Trace/tracelog —
|
||||
явный режим instruction tracing, не default для логов. Экспорт `.dbgs`
|
||||
указывает ограничения и не ставит безусловный resume в общей сессии.
|
||||
|
||||
### 7.2 SDBG_LOG и SDBG_LOGIF
|
||||
|
||||
Практический синтаксис, типы, ограничения и примеры описаны в отдельном
|
||||
[руководстве по SDBG_LOG](sdbg-log-macros.md). Этот документ — источник
|
||||
истины для пользовательского контракта макросов: при изменении грамматики,
|
||||
типов, регистров, чтения указателей или вывода в MAME/DAP обновлять его
|
||||
вместе с кодом и проверками, затем синхронизировать краткие описания здесь.
|
||||
|
||||
Первый поддержанный вариант реализован в `<sdbg.h>`: `SDBG_LOG(tag,
|
||||
"total={total}")` и `SDBG_LOGIF(tag, flag, "total={total}")`.
|
||||
Обычная сборка получает `((void)0)`; выбранный `--src-debug` TU —
|
||||
символ `sym = .` без инструкции. `tag` обязан быть уникальным в TU,
|
||||
условие пока только имя поддержанной global/static переменной, а
|
||||
`{name}` соответствует ограниченной грамматике DAP logMessage.
|
||||
Сборщик извлекает только активные вызовы из отдельного препроцессорного
|
||||
metadata-прохода, проверяет единственный asm-якорь и linked address.
|
||||
Автоматически установленный logpoint пишет в журнал debugger MAME и DAP
|
||||
`output`, затем продолжает CPU; совпавшая обычная точка сохраняет остановку.
|
||||
Событийный буфер DAP ограничен 1024 записями и сообщает разрыв курсора с
|
||||
числом пропусков; overhead горячих macro-logpoints ещё требуется измерить.
|
||||
Десятичный/hex formatter и чтение типизированного значения по указателю
|
||||
отложены до финального этапа §10: сначала нужны доказанные тип, контекст
|
||||
банка, границы и безопасное чтение памяти, иначе лог может показать неверный
|
||||
объект. Текущий `{name}` выводит десятичное число и никогда не разыменовывает
|
||||
указатель.
|
||||
Если якорь не совпал с началом доказанной инструкции, он помечен unverified
|
||||
и не активируется. В проверенном fixture EXE совпал побайтово с обычной
|
||||
сборкой; это доказательство конкретного случая, не гарантия для всех
|
||||
оптимизаций SDCC и inline asm.
|
||||
|
||||
Макросы остаются расширением для авторских устойчивых точек. Без
|
||||
anchors — `((void)0)`, с anchors — символ `sym = .` без инструкции.
|
||||
Аргументы логирования не исполняются приложением и не имеют C-побочных
|
||||
эффектов; это явно документируется, чтобы counter++ не считался кодом C.
|
||||
|
||||
Точная точка требует найденного и проверенного якоря. Без него — missing/
|
||||
unverified; приблизительная привязка выбирается отдельно с показом
|
||||
фактической позиции. Якорь задаёт машинную границу перед инструкцией,
|
||||
но не гарантирует материализацию локальной или порядок всех вычислений C.
|
||||
|
||||
ID включает TU и tag. Повторные/inline экземпляры имеют отдельные ID либо
|
||||
отклоняются до линковки; уникальный tag в тексте программы не предотвращает
|
||||
повторное разворачивание inline.
|
||||
|
||||
Реализованный препроцессорный проход учитывает активные #if, include,
|
||||
wrappers, многострочные вызовы, комментарии и склейку литералов; связывает
|
||||
SDBG-описания с единственным реально собранным якорем. Текущая грамматика
|
||||
намеренно отклоняет вычисляемый tag/condition, нестроковое сообщение и
|
||||
повторные tag; поддержку сложных C-выражений и локальных добавлять только
|
||||
после доказанного location range и ограниченного парсера.
|
||||
|
||||
Приёмка сравнивает полные бинарники с/без anchors и без макроса на циклах,
|
||||
ветках, inline и multi-TU. Если оптимизация меняется, внешние логпоинты
|
||||
остаются путём без пересборки, anchors обозначаются инструментированным
|
||||
режимом с измеренной дельтой. Без доказательства «ноль влияния» не обещать.
|
||||
При добавлении sdbg.h обновить публичный справочник API.
|
||||
|
||||
### 7.3 Комментарии, стек, локальные, покрытие
|
||||
|
||||
Резидентные comadd ставятся после проверки образа и обновляются при смене
|
||||
сборки. Банковые включаются после поддержки remap-обновления с удалением
|
||||
старых либо патча ключа `(address, page/context, opcode CRC)`.
|
||||
Оффлайновый cmt допустим для проверенного резидентного образа; CRC из ihx
|
||||
не решает банковые коллизии. Учитывать владение пользовательскими комментариями.
|
||||
|
||||
Backtrace сначала даёт текущий достоверный frame. Затем поддержать
|
||||
распознанные прологи/эпилоги, SDCC __sdcccall(1), callee-pops, IX,
|
||||
trampolines и ISR. Сканирование стека на похожие адреса — отдельная
|
||||
маркированная эвристика, не основание для stepOut/локальных. Возможная
|
||||
альтернатива — история call/return, но attach посреди исполнения не знает
|
||||
прошлого, а нестандартные переходы требуют инвалидирования истории.
|
||||
|
||||
Локальные доступны лишь при доказанном location range (регистр/стек/память).
|
||||
Лексический scope не равен live-range. Неизвестные/оптимизированные
|
||||
значения — unavailable/optimized out. CFI/location lists из asm — отдельное
|
||||
исследование, а не автоматически доступная возможность CDB.
|
||||
|
||||
Покрытие различает посещение адреса, число исполнений и время. Trackpc
|
||||
сам по себе не даёт времени и требует проверки банковых коллизий.
|
||||
Ключ покрытия включает build ID/bank/location, знаменатель — доказанные
|
||||
исполняемые позиции. Профиль использует измеренный источник cycles/time
|
||||
или sampling с указанной погрешностью и overhead.
|
||||
|
||||
## 8. MCP, DAP и полный цикл VS Code
|
||||
|
||||
### 8.1 CLI/MCP
|
||||
|
||||
Интерфейсы: attach/status/detach, where, disassemble_src, break_at,
|
||||
clear_owned_breakpoints, read_var/write_var, watch_var, registers/memory,
|
||||
step_instruction/step_in/next/step_out/pause/continue, logs с курсором,
|
||||
console_log и загрузка набора точек. Возвращать build ID/generation,
|
||||
фактическое разрешение и ограничения там, где они нужны для интерпретации.
|
||||
Неподдержанное — явная ошибка. Старые низкоуровневые команды интегрируются
|
||||
в арбитраж, а не обходят его.
|
||||
|
||||
### 8.2 DAP MVP и развитие
|
||||
|
||||
MVP: initialize, attach, configurationDone, disconnect, setBreakpoints,
|
||||
setFunctionBreakpoints, threads, stackTrace, scopes, variables,
|
||||
ограниченный evaluate, continue/pause, проверенный instruction step,
|
||||
disassemble и logMessage/output. Один Z80 — один thread; stackTrace
|
||||
сначала содержит один текущий frame. Source-step, если ещё не готов,
|
||||
отвечает отказом с объяснением; instruction granularity включается
|
||||
только при реализации.
|
||||
|
||||
Соблюдать initialize→initialized→configurationDone; stopped содержит
|
||||
причину/ID точек, continued сообщает внешний resume, breakpoint — новое
|
||||
разрешение. Terminated означает конец сессии; exited выдаётся только при
|
||||
установленном завершении приложения и достоверном коде выхода.
|
||||
SetBreakpoints заменяет набор данного source, включая очистку пустым
|
||||
списком, а не добавляет точки бесконечно.
|
||||
|
||||
Capabilities отражают реальную поддержку. Расширения: breakpointLocations,
|
||||
instruction/conditional/hit breakpoints, readMemory/writeMemory, setVariable,
|
||||
dataBreakpointInfo вместе с setDataBreakpoints, source-step/next/stepOut,
|
||||
cancel и сложные типы. Frame/variables references привязаны к остановке,
|
||||
memory references различают банк/пространство. Предусмотреть пагинацию.
|
||||
Stdout адаптера содержит только DAP, диагностики — stderr/log.
|
||||
|
||||
### 8.3 Разработка и запуск из редактора
|
||||
|
||||
**Архитектурное решение:** Sprinter-специфичный цикл реализуется собственным
|
||||
расширением `toolchain/vscode-sprinter-debug`. Готовые C/C++ или clangd можно
|
||||
использовать для подсветки, completion и навигации, а VS Code Tasks и Debug UI
|
||||
— как стандартные интерфейсы. Они не знают ABI SDCC/Z80, пакет
|
||||
`.sprinter-cc-*`, DSS, банковую адресацию и протокол MAME, поэтому не могут
|
||||
заменить project extension и не считаются источником истины для диагностики
|
||||
компилятора или отладки.
|
||||
|
||||
Мини-расширение уже содержит `contributes.debuggers`, точки для C, схему и
|
||||
шаблоны конфигурации. Следующий уровень переносит в него build/run/debug
|
||||
оркестрацию: `DebugConfigurationProvider` проверяет конфигурацию и при
|
||||
необходимости запускает выбранную build task; команды расширения выбирают
|
||||
target/profile/EXTRA_DATA; TaskProvider и problem matcher переводят ошибки
|
||||
SDCC/линкера в Problems. Сборку всё равно выполняют `make`/`sprinter-cc`, а
|
||||
запуск — общий Python launcher: расширение не дублирует их логику.
|
||||
|
||||
Установка через VSIX, разработка через Extension Development Host. Наличие
|
||||
VS Code, pyenv Python и необязательного языкового расширения проверяется при
|
||||
настройке, а не считается постоянным свойством конкретной машины.
|
||||
|
||||
Включить в поставку:
|
||||
|
||||
- TaskProvider и/или tasks.json: make/sprinter-cc и problem matcher
|
||||
SDCC/ассемблера/линкера; debug не стартует после неуспешной сборки.
|
||||
- Языковые настройки: include/defines/gfx/safe/memory из той же сборочной
|
||||
конфигурации. SDCC-расширения вроде __naked требуют совместимых редакторских
|
||||
определений; generic clang/GCC-анализ не равен полному анализу SDCC.
|
||||
- launch.json: приложение/пакет, MAME/ROM/media, EXTRA_DATA, timeout,
|
||||
stopOnEntry/stopOnMain, source mappings; без личных абсолютных путей.
|
||||
- Launch: успешная сборка → упаковка → MAME → загрузка DSS → проверка образа
|
||||
→ готовность runtime → main. Переиспользовать текущую упаковку/launcher,
|
||||
не дублировать сценарии ввода клавиатуры.
|
||||
- Attach к подготовленной сессии без второго MAME; restart с новым build ID
|
||||
и пересчётом точек после новой сборки/загрузки.
|
||||
- Disconnect не закрывает чужой MAME; terminate запущенного адаптером
|
||||
процесса имеет явную политику. Выход приложения отличается от выхода
|
||||
MAME. Ошибки сборки/ROM/media показываются в редакторе с причиной.
|
||||
|
||||
## 9. Альтернативный маршрут GDB
|
||||
|
||||
DAP выбран за прямую модель Sprinter, reuse сессии MCP и отсутствие
|
||||
обязательного DWARF-писателя/target GDB. GDB полезен для его скриптов и
|
||||
фронтендов; маршрут сохранён как самостоятельное расширение.
|
||||
|
||||
Первоначальная разведка: OSD gdbstub знает z84c015, но объявляет mame.z80
|
||||
и другой порядок регистров; исследованный GDB ожидает org.gnu.gdb.z80.cpu
|
||||
и набор с объединённым IR. Повторить проверку выбранной пары версий.
|
||||
Lua gdbstub с i386-картой не является готовым backend Z80. Оценка патча
|
||||
«20 строк» заменяется проверкой полного контракта.
|
||||
|
||||
1. Собрать/закрепить target GDB; проверить handshake. Патч feature/регистров
|
||||
и IR проходит round-trip всех регистров, включая альтернативные,
|
||||
byte order и семантику R.
|
||||
2. Проверить RSP step/continue/interrupt, logical memory read/write,
|
||||
break/watch и отсутствие патчинга инструкций. Разведка сообщала, что
|
||||
Z0/Z2..Z4 используют точки MAME; включить это в регрессию.
|
||||
3. sdbg_elf.py на общей карте: ELF32 EM_Z80, секции по реальному размещению,
|
||||
symtab, debug_line, затем CU/subprogram/variable и базовые типы DWARF.
|
||||
Дыры/банки не склеивать в ложный text. Сначала binutils/GDB offline,
|
||||
затем live. ELF с symtab остаётся полезным самостоятельным экспортом.
|
||||
4. debug_frame/CFI и locations — только по доказанным данным; строки DWARF
|
||||
сами по себе не исправляют unwinder SDCC.
|
||||
5. VS Code cppdbg/target GDB/app.elf — отдельная MI-конфигурация. Банки
|
||||
сначала вручную/ограниченно; затем исследовать overlays и синхронизацию
|
||||
с runtime, а не обещать автоматическую поддержку.
|
||||
|
||||
GDB имеет dprintf без пересборки и поддержку overlays. Редакторский
|
||||
logMessage зависит от frontend; overlays требуют интеграции Sprinter.
|
||||
Поэтому прежние «логпоинтов нет» и «банки невозможны» заменены конкретными
|
||||
ограничениями. Области Registers/Ports также возможны поверх GDB,
|
||||
но в DAP непосредственно используют нашу target-модель.
|
||||
|
||||
RSP и DAP не управляют исполнением независимо одновременно. Режим GDB
|
||||
получает отдельного владельца; наблюдающие функции MCP проверяются отдельно.
|
||||
Общую карту можно переиспользовать без совместного run/stop.
|
||||
|
||||
## 10. Этапы реализации и условия перехода
|
||||
|
||||
Календарные оценки прежнего плана считаются оценками демонстрационного
|
||||
прототипа: надёжность lifecycle/банков/DAP ими не покрыта. После этапа 0
|
||||
оценить каждый этап по репро и измерениям, раздельно прототип, поддержанный
|
||||
выпуск и исследовательские функции. Проход этапа определяется приёмкой.
|
||||
|
||||
### Этап 0 — проверка предположений
|
||||
|
||||
Сохранить §2.3; воспроизвести общий inline в двух вызываемых TU и проверить
|
||||
адреса обоих экземпляров/решение R01. Проверить anchors, huge/big/bank-data,
|
||||
библиотечную CDB, memory aliases/watchpoint PC, lifecycle crt0, async step,
|
||||
задержки моста и CRC банков. Закрепить версии и baseline производительности.
|
||||
|
||||
**Выход:** доказанный способ R01 либо явный ограниченный пер-модульный
|
||||
режим; таблица проверенных/непроверенных возможностей. Общую карту не
|
||||
объявлять готовой при скрытом конфликте.
|
||||
|
||||
### Этап 1 — сборка, manifest и карта
|
||||
|
||||
Флаги/app.mk, публикация пакета, исправление CDB, типизированные адреса,
|
||||
resolver и CLI map/addr2line/line2addr/vars/asm. Debug-библиотеки добавляются
|
||||
после успешного эксперимента. Обновить документацию сборки/mame-autotest.
|
||||
|
||||
**Выход:** fixtures, одинаковые basename, inline multi-TU, huge/big и
|
||||
bank-data разрешаются достоверно; unknown явен. Полный cmp debug/non-debug;
|
||||
make size-check без необъяснённой дельты. Baseline не обновляется ради
|
||||
скрытия регрессии.
|
||||
|
||||
### Этап 2 — сессия и мост
|
||||
|
||||
Lifecycle/image verification, snapshot/generation, арбитраж, реестр точек,
|
||||
событийный протокол. Сначала файловый backend/измерения, затем socket
|
||||
по необходимости. Pause/continue/instruction step и ограниченный async
|
||||
stepIn. Версионировать мост/патчи и установщик (§12). Новый самостоятельный плагин
|
||||
можно загружать прямо из toolchain/mcp через pluginspath без копирования
|
||||
в vendor. Для интерактивного режима без окон требуется решение R19.
|
||||
|
||||
**Выход:** нет ложных точек DSS, банки активируются после готовности,
|
||||
exit/reset/reload инвалидируют состояние; потеря клиента не блокирует MAME,
|
||||
конкурентные операции не читают смешанный snapshot.
|
||||
|
||||
### Этап 3 — полезная CLI/MCP-отладка и логи
|
||||
|
||||
Внешние точки/условия/логи, диспетчер совпадений, базовые типы, чтение/запись
|
||||
резидентных объектов, доказанные watchpoint, where/disassemble_src/clog.
|
||||
Интегрировать пакет/журнал в автотестовый launcher; проверить headless debugger.
|
||||
|
||||
**Выход:** значения совпадают с эталонными байтами, логи воспроизводимы
|
||||
и ограничены, breakpoint не проглатывается логпоинтом, повторная загрузка
|
||||
набора не дублирует точки.
|
||||
|
||||
### Этап 4 — VS Code MVP
|
||||
|
||||
DAP §8.2 и мини-расширение, сначала attach. Source/asm, break/logMessage,
|
||||
globals/registers, pause/continue/instruction step. Source-step включить
|
||||
после его приёмки, другие запросы не рекламировать заранее.
|
||||
|
||||
**Выход:** редакторская точка показывает фактическое разрешение,
|
||||
остановка — верные строку/банк/значения; lifecycle DAP, удаление точек,
|
||||
disconnect и stale references проверены протокольными тестами.
|
||||
|
||||
### Этап 5 — полный цикл разработки и расширенная отладка
|
||||
|
||||
Собственное расширение получает команды Build/Run/Debug, TaskProvider,
|
||||
problem matcher, выбор target/profile/EXTRA_DATA и проверку инструментов.
|
||||
Языковой сервис C/C++ либо clangd остаётся необязательной внешней
|
||||
зависимостью с генерируемыми include/defines; его диагностика не подменяет
|
||||
SDCC. Добавить launch/restart/stopOnMain и упаковку VSIX. Довести next,
|
||||
доступные stepOut, memory/setVariable, data breakpoints, physical bank-data,
|
||||
массивы/структуры и резидентные комментарии.
|
||||
|
||||
**Выход:** новый рабочий каталог проходит документированную настройку/F5
|
||||
на обычной и банковой программе; restart не использует старую карту.
|
||||
У каждой расширенной функции свой тест/capability; недоступная функция
|
||||
не мешает использовать поддержанные.
|
||||
|
||||
### Этап 6 — якоря и исследовательские функции
|
||||
|
||||
Первый `SDBG_LOG` и ограниченный `SDBG_LOGIF` уже реализованы; продолжить
|
||||
§7.2 проверкой сложных inline/оптимизаций и условных выражений только после
|
||||
появления соответствующего evaluator. Независимые подэтапы: банковые комментарии,
|
||||
доказанные stack frames/локальные, coverage/profiling. GDB §9 — отдельный
|
||||
подэтап по потребности.
|
||||
|
||||
Финальные задачи для макросов, **без реализации в текущем этапе**:
|
||||
|
||||
- Добавить явные десятичные и hex-форматы для 8/16/32-битных signed/unsigned
|
||||
объектов и адресов, с проверкой типа, знака, ширины и ограниченной общей
|
||||
грамматикой для SDBG_LOG, внешних logpoints и DAP. Проверить значения на
|
||||
реальных SDCC-сборках; не передавать Python format specifier в MAME `printf`.
|
||||
- Для указателя выводить его собственное значение (логический/банковый адрес)
|
||||
в decimal/hex и отдельно значение по адресу только если указатель не `NULL`.
|
||||
`char *` трактовать как строку с bounded read до NUL и проверенной кодировкой;
|
||||
`int8_t *`/`uint8_t *` — как один 8-битный signed/unsigned объект (для
|
||||
16/32-битных typed pointers — соответствующий размер). Не выбирать режим
|
||||
по одному CDB: `char *` и `uint8_t *` там неразличимы (R22); сохранить
|
||||
объявленный тип/typedef chain из исходника и сверить с CDB. Чтение только
|
||||
без side effects, после проверки размера, доступного банка, границ и image
|
||||
identity; `NULL`, закрытая страница, отсутствие NUL в лимите и неизвестный
|
||||
pointee дают явный `unavailable`/truncated, а не неверное значение.
|
||||
|
||||
**Выход:** доказательства и ограничения каждой функции опубликованы.
|
||||
Эвристика не выдаётся за стек, anchors — за гарантированную идентичность,
|
||||
посещения адресов — за время CPU.
|
||||
|
||||
## 11. Матрица приёмки и регрессий
|
||||
|
||||
| Сценарий | Проверяемое свойство | Этап / риски |
|
||||
|---|---|---|
|
||||
| hello/probe debug и обычный | Полный cmp exe, размеры, карта main | 0–1, R02 |
|
||||
| Два TU с общим вызываемым inline | Нет коллизий, адреса/строки обоих экземпляров | 0–1, R01/R04 |
|
||||
| Ошибка линковки при старом ihx | Ошибка сохранена, новый пакет не опубликован | 1, R01/R12 |
|
||||
| Два utils.c и перенесённый проект | TU identity и source mapping | 1, R12 |
|
||||
| Цикл/ветка/удалённая строка/много адресов | Все позиции или unverified, без ложного переноса | 1–4, R04 |
|
||||
| Два банка с одинаковыми PC и ret | Верная строка/точка, нет чужого комментария | 0–6, R03/R11 |
|
||||
| huge W3, big W1, поддержанные manual | Адреса/guards из фактической карты | 1–5, R03 |
|
||||
| bank-data, невключённый банк, граница окна | Physical чтение или Unavailable | 1–5, R03 |
|
||||
| Watchpoint через CPU/alias | Адрес, момент и PC-источник корректны | 0–5, R03 |
|
||||
| Entry до банков, ошибка loader | Нет ранней активации/ложного ready | 2, R05 |
|
||||
| DSS/exit/restart/reset/state load | Нет чужих попаданий и stale handles | 2–5, R05/R12 |
|
||||
| Изменённый source или чужой exe | Явное расхождение карты | 1–5, R12 |
|
||||
| Log + user + temporary по одному PC | Лог есть, остановка сохранена | 2–4, R08 |
|
||||
| Два клиента, ручной resume, reconnect | Арбитраж/события, нет повторных mutations | 2–4, R07/R15 |
|
||||
| Одна строка в цикле, HALT/ISR/рекурсия/trampoline | Ограниченный отменяемый шаг | 2–5, R06/R13 |
|
||||
| Signed 8/16, pointer/array, CP866, нет NUL | Знак/тип/кодировка, bounded read | 3–5, R10 |
|
||||
| Горячий лог, переполнение, выход | Overhead, счётчик потерь, flush | 3, R09 |
|
||||
| Debug libc/libbgi fast/safe, DCE | Строки/типы, нет роста кода | 0–1, R17 |
|
||||
| Anchors с #if/include/wrappers/inline | Метаданные совпадают, unsupported отклонён | 6, R02/R04 |
|
||||
| DAP replace/empty/events/references | Протокол и честные capabilities | 4, R13 |
|
||||
| F5 с данными, ошибка ROM/build, restart | Полный цикл и диагностика | 5, R18 |
|
||||
| MAME update с ручными изменениями | Установщик не затирает расхождение | 2, R16 |
|
||||
|
||||
Тесты карты/протокола — небольшие fixtures без MAME; CPU/банки/lifecycle —
|
||||
живые интеграционные сценарии. Golden-карта сверяется независимо с
|
||||
листингом, байтами и фактическими остановками, не только выводом парсера.
|
||||
Tests/hello и tests/banked — стартовые кандидаты, не вся приёмка.
|
||||
|
||||
Замеры: без debugger, debugger без точек, resident/bank breakpoints,
|
||||
горячий лог, instruction trace, transport RTT, end-to-end step.
|
||||
Записывать хост/версию, emulation speed, объём журнала, latency p50/p95.
|
||||
Пороги зафиксировать после baseline этапа 0 до выбора backend;
|
||||
«21 МГц терпимо» не критерий приёмки.
|
||||
|
||||
## 12. Размещение и воспроизводимость
|
||||
|
||||
Исходники моста/MCP — `toolchain/mcp/`, патчи MAME —
|
||||
`toolchain/mame-patches/`; патч SDCC при необходимости отдельно с версией.
|
||||
`toolchain/install-mame-bridge.sh` проверяет upstream revision/хеши,
|
||||
показывает diff при расхождении и не затирает неизвестные ручные изменения.
|
||||
Повторная установка идемпотентна; protocol version проверяется handshake.
|
||||
|
||||
Самостоятельный sdbgbridge загружается прямо из toolchain/mcp через
|
||||
pluginspath: это устраняет необходимость копировать его в vendor и риск
|
||||
потери изменений. Установщик остаётся нужен для патчей существующего
|
||||
MAME/моста, если они потребуются. Игнорируемое mame/ не источник истины. Документировать сборку/применение
|
||||
патчей, проверку установленного бинарника и откат. Не хранить личные пути,
|
||||
ROM и большие образы в исходниках расширения.
|
||||
|
||||
Defaults вместо прежних открытых вопросов: source debug явный/профиль IDE;
|
||||
CDB errors не подавляются; отдельный журнал; общий ограниченный язык;
|
||||
DAP — основной IDE-путь; одна сессия владеет backend; socket по измерениям;
|
||||
debug-библиотеки после эксперимента. Технически открыты: способ R01,
|
||||
наблюдение загрузки/выхода, полная mapping-формула, physical RAM и стоимость
|
||||
hooks. У каждого — репро этапа 0 и условие допуска, а не молчаливое
|
||||
предположение следующих этапов.
|
||||
|
||||
## 13. Внешние спецификации
|
||||
|
||||
Локальные версии исходников и репро первичны для конкретной сборки.
|
||||
Online-документация задаёт общую семантику; при реализации фиксировать
|
||||
использованную версию.
|
||||
|
||||
- [DAP specification](https://microsoft.github.io/debug-adapter-protocol/specification): запросы, события, capabilities и references.
|
||||
- [DAP specification source](https://github.com/microsoft/debug-adapter-protocol/blob/main/specification.md): полный контракт.
|
||||
- [VS Code debugger extension](https://code.visualstudio.com/api/extension-guides/debugger-extension): регистрация/упаковка адаптера.
|
||||
- [MAME general commands](https://docs.mamedev.org/debugger/general.html): printf/tracelog/source/trackpc.
|
||||
- [MAME execution commands](https://docs.mamedev.org/debugger/execution.html): шаги и трассировка.
|
||||
- [MAME Lua debugger classes](https://docs.mamedev.org/luascript/ref-debugger.html): низкоуровневый API.
|
||||
- [GDB Dynamic Printf](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Dynamic-Printf.html): логирование без пересборки.
|
||||
- [GDB Overlays](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Overlays.html): перекрывающиеся размещения и target-интеграция.
|
||||
@@ -0,0 +1,204 @@
|
||||
# Логи C-приложения через `SDBG_LOG`
|
||||
|
||||
Этот документ описывает **работающий контракт** макросов из `<sdbg.h>`.
|
||||
Они ставят авторские logpoints в MAME debugger без вызовов DSS/BIOS и без
|
||||
кода печати в приложении. Сообщение попадает в Debug Console VS Code и в
|
||||
журнал debugger MAME. Отдельное окно MAME debugger видно при launch с
|
||||
`"debugger": "osx"`; режим `sdbg` оставляет окно Sprinter и журнал MAME,
|
||||
но не открывает штатное debugger-окно.
|
||||
|
||||
## Быстрый пример
|
||||
|
||||
```c
|
||||
#include <stdint.h>
|
||||
#include <sdbg.h>
|
||||
|
||||
volatile uint16_t frame_no;
|
||||
volatile uint8_t ready;
|
||||
|
||||
void draw_frame(void)
|
||||
{
|
||||
++frame_no;
|
||||
SDBG_LOG(frame_counter, "frame={frame_no}");
|
||||
SDBG_LOGIF(frame_ready, ready, "ready={ready}, frame={frame_no}");
|
||||
}
|
||||
```
|
||||
|
||||
Соберите приложение с `make SRC_DEBUG=1` либо запустите F5 в VS Code:
|
||||
расширение выполняет debug-сборку перед запуском. Для прямого вызова
|
||||
обёртки подходит `bin/sprinter-cc --src-debug ...`; в режиме
|
||||
`--src-debug-file FILE` якоря создаются только в выбранных единицах
|
||||
трансляции. После загрузки DSS launcher останавливается в `main`, проверяет
|
||||
образ, и session server активирует макросные точки. При попадании он читает
|
||||
поддержанные значения, выводит текст и продолжает CPU. Если на том же
|
||||
адресе есть обычный breakpoint, сообщение печатается, а CPU остаётся
|
||||
остановленным.
|
||||
|
||||
В обычной сборке оба макроса раскрываются в `((void)0)`: без source-debug
|
||||
сессии они сами ничего не печатают. Строка сообщения не хранится в EXE;
|
||||
в проверенных обычном и банковом fixture бинарники с макросом и без него
|
||||
побайтово совпали. Inline asm может влиять на оптимизацию в других случаях,
|
||||
поэтому одинаковый размер/код каждой программы следует проверять отдельно.
|
||||
|
||||
## Параметры и место вызова
|
||||
|
||||
`SDBG_LOG(tag, message)` принимает два аргумента.
|
||||
|
||||
| Параметр | Что передать | Ограничение |
|
||||
|---|---|---|
|
||||
| `tag` | Имя точки, например `frame_counter` | C-идентификатор `[A-Za-z_][A-Za-z_0-9]*`, **без кавычек**, уникальный внутри `.c`/TU |
|
||||
| `message` | Строковый литерал C, например `"frame={frame_no}"` | От 1 до 1024 символов после декодирования, поддержана склейка соседних литералов |
|
||||
|
||||
Одинаковый `tag` в разных `.c` допустим: сборщик добавляет к символу
|
||||
уникальный ID TU. Повторный `tag` в одном TU, в том числе из повторно
|
||||
развёрнутого inline-макроса, отклоняется при сборке. Вычисляемый tag,
|
||||
строка вместо tag и автоматический `__LINE__` пока не поддержаны.
|
||||
Используйте название, которое остаётся понятным после правки строк файла.
|
||||
|
||||
`SDBG_LOGIF(tag, condition, message)` принимает третий смысловой компонент:
|
||||
между tag и сообщением указывается **одно имя** поддержанной global/static
|
||||
переменной. Если её значение при попадании равно нулю, запись пропускается,
|
||||
CPU продолжается. Ненулевое значение включает запись. Например:
|
||||
|
||||
```c
|
||||
SDBG_LOGIF(after_load, ready, "ready={ready}");
|
||||
```
|
||||
|
||||
`ready != 0`, `!ready`, вызов функции, локальная переменная и регистр CPU
|
||||
как `condition` сейчас не поддержаны. Условие не исполняется кодом C:
|
||||
отладчик читает значение при остановке. Если переменная недоступна, запись
|
||||
пропускается и отладчик один раз сообщает причину. Значения с побочными
|
||||
эффектами (`counter++`, вызовы функций) нельзя использовать и в шаблоне:
|
||||
аргументы макроса не вычисляются на Sprinter.
|
||||
|
||||
Якорь привязан к текущему адресу ассемблера без инструкции. Он обозначает
|
||||
машинную границу рядом с вызовом макроса, а не обещает точный порядок всех
|
||||
выражений C после оптимизации. Ставьте вызов отдельным statement после
|
||||
интересующего действия и проверяйте фактический адрес/значение на нужной
|
||||
сборке. Если адрес не совпал с началом доказанной инструкции, точка
|
||||
получает статус `unverified` и не активируется. Макрос в неактивном `#if`
|
||||
не создаёт точку; wrappers, многострочные вызовы и склейка литералов
|
||||
обрабатываются активным препроцессорным проходом.
|
||||
|
||||
## Синтаксис сообщения сейчас
|
||||
|
||||
После обработки C-escape-последовательностей шаблон состоит из литералов
|
||||
и подстановок `{name}`. `name` — имя **одной** доступной переменной;
|
||||
значение выводится десятичным числом. Например:
|
||||
|
||||
```c
|
||||
SDBG_LOG(progress, "step={step}, total={total}");
|
||||
SDBG_LOG(braces, "literal {{value}}; actual={total}");
|
||||
SDBG_LOG(multiline, "step={step}, " "total={total}");
|
||||
```
|
||||
|
||||
`{{` и `}}` дают буквальные `{` и `}`. `%` сейчас обычный символ: `%d`,
|
||||
`%x` и `%s` **не являются** форматами макроса. Синтаксис `{name:04X}`,
|
||||
`{name!r}`, `{name+1}`, индексы массивов и разыменование указателя
|
||||
отклоняются во время debug-сборки, до запуска MAME. Не добавляйте префикс
|
||||
`0x` перед `{name}`: значение пока десятичное, и результат будет неверно
|
||||
выглядеть как шестнадцатеричный.
|
||||
|
||||
Подстановки разрешаются в контексте TU, где расположен макрос. Можно
|
||||
прочитать единственную поддержанную global или file-static переменную этого
|
||||
TU. Если имя не найдено/неоднозначно, тип неподдержан или физический банк
|
||||
сейчас не отображён, вместо значения выводится `<unavailable: причина>`.
|
||||
Локальные/параметры функции не имеют доказанных location ranges и пока
|
||||
не читаются. Resident-страница, закрытая банком, тоже недоступна.
|
||||
|
||||
Текст DAP сохраняет символы UTF-8. Перед отправкой в MAME debugger `printf`
|
||||
кавычки заменяются апострофами, а управляющие символы — пробелами;
|
||||
проценты и обратные слэши экранируются. Очень длинный сформированный текст
|
||||
может не пройти ограничение MAME console 2048 байт, но DAP output остаётся.
|
||||
При частом попадании CPU останавливается каждый раз, поэтому эмуляция может
|
||||
замедлиться. DAP хранит до 1024 событий и показывает число пропусков,
|
||||
если клиент отстал; скорость горячих logpoints ещё не измерена.
|
||||
|
||||
## Какие значения можно подставить
|
||||
|
||||
Источник истины — тип и размер глобального/file-static объекта в debug-карте
|
||||
SDCC. Доступны целые скаляры размером 1, 2 или 4 байта, а также проверенный
|
||||
обычный 16-битный указатель. Значение читается из памяти без side effects.
|
||||
Ниже приведены результаты реальной debug-сборки SDCC 4.5 для этих типов.
|
||||
|
||||
| C-тип объекта | Сейчас в `{name}` | Что остаётся недоступным |
|
||||
|---|---|---|
|
||||
| `uint8_t` / `unsigned char` | Десятичное 0…255 | Hex/битовая маска |
|
||||
| `int8_t` / `signed char` | Десятичное −128…127 | Принудительный unsigned/hex |
|
||||
| `uint16_t` / `unsigned int` | Десятичное 0…65535 | Hex с 4 цифрами |
|
||||
| `int16_t` / `int` | Десятичное со знаком | Иной числовой формат |
|
||||
| `uint32_t` / `unsigned long` | Десятичное 0…4294967295 | Hex с 8 цифрами |
|
||||
| `int32_t` / `long` | Десятичное со знаком | Иной числовой формат |
|
||||
| `char` | **Числовой код** байта согласно signedness SDCC; в проверенной сборке `char` был unsigned | Вывод как символ, CP866→UTF-8 |
|
||||
| `char *` | **Числовой 16-битный адрес** указателя | По принятому для финального API правилу это строка, а не один `char`: bounded read до NUL, границы, кодировка |
|
||||
| `int8_t *` / `uint8_t *` | **Числовой 16-битный адрес** указателя | По финальному API — один знаковый/беззнаковый 8-битный объект по ненулевому адресу, decimal/hex |
|
||||
| `char[]`, другие массивы, struct/union, float/double | Не поддержаны | Элементы/поля/значение объекта |
|
||||
| Локальные и параметры функции | Не поддержаны | Нужны доказанные регистр/стек и live-range |
|
||||
| Регистры CPU (`PC`, `HL`, `DE`, `AF`, `PG0`…) | Не являются подстановками макроса | Они видны в scope Registers VS Code и MAME debugger, но `{PC}` сейчас не читает регистр |
|
||||
|
||||
Для `char *` вывод адреса **не доказывает**, что память по нему доступна.
|
||||
Сам массив `char[]` сейчас не выводится даже если его размер 1/2/4 байта.
|
||||
Числовые коды `char` не декодируются как символы Sprinter. Поддержка
|
||||
конкретного объекта зависит и от того, есть ли он в карте: оптимизированное
|
||||
или неразрешённое объявление может быть недоступно.
|
||||
|
||||
Тип указателя нельзя выбирать только по текущему CDB: SDCC 4.5 записал
|
||||
проверенные `char *` и `uint8_t *` одинаково как `DG,SC:U` (2 байта),
|
||||
а `int8_t *` как `DG,SC:S`. Для финального вывода строки или скаляра нужны
|
||||
метаданные **исходного объявленного типа**, сохранённые в debug-пакете и
|
||||
сверенные с CDB/размером. Неоднозначные typedef/объявления должны давать
|
||||
`unavailable` либо требовать явную аннотацию формата, а не угадывать по CDB.
|
||||
|
||||
## Форматирование, которое рассматривается
|
||||
|
||||
Эта таблица описывает **предложение для следующей версии**, а не действующий
|
||||
синтаксис. В текущей версии все записи справа вызовут ошибку сборки.
|
||||
|
||||
| Предлагаемый шаблон | Назначение | Что нужно реализовать и проверить |
|
||||
|---|---|---|
|
||||
| `{u8:02X}`, `{u16:04X}`, `{u32:08X}` | Hex с шириной по типу | Ограниченный formatter, signed/unsigned и ширина 8/16/32 бит |
|
||||
| `{value:d}`, `{value:x}`, `{value:X}` | Одну переменную можно вывести в десятичном и hex виде в одном сообщении | Проверка типа/знака, 8/16/32-битной ширины и недопущение произвольных Python format specifier |
|
||||
| `{letter:c}` | Символ из `char` | CP866→UTF-8, различие числового кода и отображения символа |
|
||||
| `{ptr:p}` | Значение самого указателя: логический/банковый адрес | Типизированный адрес, явный `NULL`, различие числового decimal/hex отображения |
|
||||
| `{p8:*d}`, `{p8:*x}` **условно** | Один 8-битный scalar по `int8_t *`/`uint8_t *` при ненулевом адресе; decimal/hex, окончательный синтаксис ещё не утверждён | Исходный тип и signedness, side-effect-free чтение одного байта, границы/банк; `NULL`/закрытая страница → `unavailable` |
|
||||
| `{text:s}` | Строка по `char *` при ненулевом адресе, а не один `char`; прямой `char[]` — отдельный случай | Сохранить declared type, bounded read до NUL, доступный банк, отсутствие NUL/кодировка/лимит вывода |
|
||||
| `{reg:PC}` | Значение регистра CPU | Отдельное пространство имён, чтобы не спутать регистр с C-global `PC` |
|
||||
|
||||
Форматирование должно использовать один и тот же ограниченный движок для
|
||||
C-макросов, внешних logpoints и DAP output. До реализации предпочтительнее
|
||||
выводить десятичное значение и читать hex/регистры в штатных views debugger,
|
||||
чем имитировать `printf` в тексте сообщения.
|
||||
|
||||
Обе задачи — decimal/hex вывод и чтение значения по указателю — записаны
|
||||
в финальный [этап 6 плана](mame-source-debug.md).
|
||||
Сейчас `{ptr}` показывает только десятичное значение адреса; разыменование
|
||||
не выполняется даже при ненулевом указателе. В финальном API `char *`
|
||||
обозначает строку, а указатель на один 8-битный объект записывается как
|
||||
`int8_t *` или `uint8_t *`. Адрес любого указателя должен выводиться
|
||||
отдельно от значения по адресу. Для строки нужна отдельная проверка NUL,
|
||||
лимита и кодировки (CP866→UTF-8 либо явное представление raw bytes).
|
||||
Целевой результат можно представить как `text=<адрес>, value=<строка>` для
|
||||
`char *` и `p8=<адрес>, value=<одно 8-битное число>` для `uint8_t *`;
|
||||
показанные выше `{text:s}`/`{p8:*d}` ещё нельзя вставлять в рабочий C-код.
|
||||
|
||||
## Если лог не появился
|
||||
|
||||
| Симптом | Причина и действие |
|
||||
|---|---|
|
||||
| Нет лога после обычной сборки | Макрос пуст; запустите `SRC_DEBUG=1`/F5 и проверьте, что TU выбран для карты |
|
||||
| Сборка сообщает о повторном `tag` | Дайте каждой точке отдельный идентификатор; повторные inline-развёртки в одном TU требуют отдельного решения |
|
||||
| Сборка сообщает «только подстановки» | Уберите `:04X`, `%d` не подставляет значение; используйте `{name}` |
|
||||
| `unverified`/«не активирован» в Debug Console | Якорь не совпал с исполняемой инструкцией; переместите макрос к доказанной границе и пересоберите |
|
||||
| `<unavailable: …>` | Проверьте global/static тип и отображение страницы банка; локальные пока не поддерживаются |
|
||||
| Нет сообщения в окне MAME при `sdbg` | Штатное debugger-окно в этом режиме скрыто; Debug Console VS Code работает, для окна выберите `osx` |
|
||||
| При частом логе программа заметно медленнее | Каждое попадание останавливает CPU; уменьшите частоту или используйте условный макрос |
|
||||
|
||||
## Актуализация контракта
|
||||
|
||||
При изменении `<sdbg.h>`, парсера/форматтера шаблона, набора читаемых типов,
|
||||
источников значений, условий, поведения breakpoint или маршрутов MAME/DAP
|
||||
сначала обновляйте этот документ и проверяемые примеры. Затем синхронизируйте
|
||||
краткое описание в `docs/mame-source-debug.md`, `docs/vscode-sprinter-debug.md`,
|
||||
`docs/mame-source-debug-status.md` и `docs/libc-reference.md`. Реальные
|
||||
проверки: `tests/sdbg/test_sdbg.py`, `test_server.py`, `test_dap.py` и живой
|
||||
`tests/sdbg/run_macro_log_probe.py` для `sdbg`/`osx`.
|
||||
@@ -0,0 +1,272 @@
|
||||
# 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 используется собственное расширение этого проекта —
|
||||
`toolchain/vscode-sprinter-debug`. Только оно знает формат 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="$PWD/toolchain/vscode-sprinter-debug" "$PWD"
|
||||
```
|
||||
|
||||
Если команда `code` не добавлена в `PATH`, на macOS этого проекта доступен
|
||||
полный путь:
|
||||
|
||||
```sh
|
||||
"/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" \
|
||||
--new-window \
|
||||
--extensionDevelopmentPath="$PWD/toolchain/vscode-sprinter-debug" \
|
||||
"$PWD"
|
||||
```
|
||||
|
||||
Расширение в режиме `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.
|
||||
|
||||
`dssTimeout` задаёт предельное время ожидания prompt (30 эмулируемых секунд).
|
||||
`launchAt` можно задать как необязательную нижнюю границу времени запуска;
|
||||
наличие prompt всё равно обязательно. Перед вводом сохраняется диагностический
|
||||
снимок DSS.
|
||||
|
||||
Дополнительные файлы на floppy задаются массивом `data`, путь к другому MAME —
|
||||
полем `mame`.
|
||||
|
||||
## Ручная проверка 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`.
|
||||
4. Проверьте Call Stack, scope Registers и Debug Console. После Continue
|
||||
должна сработать подтверждённая точка строки 31 по адресу `0x824b`.
|
||||
5. На остановке проверьте F11 и F10. Курсор должен переходить только после
|
||||
фактической остановки CPU, а не сразу после отправки команды. На строке 62
|
||||
(`getchar`) нажмите F10, щёлкните окно Sprinter MAME и нажмите латинскую `x`:
|
||||
выполнение должно перейти на строку 63. Пока программа ждёт клавишу,
|
||||
кнопка Pause в VS Code должна останавливать CPU.
|
||||
6. Завершите сессию кнопкой Stop. Затем повторите профиль с `osx`: вместе с
|
||||
тем же VS Code-сеансом должно открыться штатное Cocoa-окно debugger MAME.
|
||||
|
||||
Команда палитры `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` входит как воспроизводимый patch к MAME 0.287. Для локального
|
||||
checkout достаточно:
|
||||
|
||||
```sh
|
||||
make mame-sdbg
|
||||
```
|
||||
|
||||
Команда идемпотентно применяет
|
||||
`toolchain/mame-patches/0001-sdbg-debugger-backend.patch`, инкрементально
|
||||
собирает MAME и устанавливает `mame/v306/mame.arm`.
|
||||
|
||||
## 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`, затем:
|
||||
|
||||
```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).
|
||||
|
||||
## Ограничения MVP
|
||||
|
||||
- Reset/restart отключён после найденного crash MAME 0.287 на старом Lua
|
||||
callback. Session завершается штатным terminate.
|
||||
- Локальные, backtrace, setVariable, watchpoints и чтение неотображённого
|
||||
bank-data ещё не реализованы.
|
||||
- Если одна инструкция имеет несколько C-маркеров, frame помечается
|
||||
`[ambiguous]`; адаптер не выбирает строку молча.
|
||||
Reference in New Issue
Block a user