Files
Sprinter-SDCC/docs/mame-source-debug.md
T
2026-09-16 19:55:27 +03:00

73 KiB
Raw Blame History

Отладка C-приложений Sprinter в MAME и VS Code

Редакция: 2026-09-14, после технического ревью плана от 2026-09-13. Статус: ЧАСТИЧНАЯ РЕАЛИЗАЦИЯ. Сборка/оффлайновая карта, транспорт, live C-сессия, постоянный server, DAP и VS Code launch MVP реализованы; результаты — 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, sprinter-cc, app.mk, mame_interactive.py, bank.s, crt0_banked.s. MAME fork теперь самостоятельный проект: MAME/plugins/mamebridge/init.lua, MAME/src/mame_mcp.py; среда запуска задаётся MAME_HOME, а исходники fork приложению не нужны. Расширение VS Code находится в отдельном VSCode-Sprinter; Python DAP backend остаётся в SDK.

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 из первоначальной разведки:

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. Архитектура, сборка и пакет

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.

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 Жизненный цикл

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. Этот документ — источник истины для пользовательского контракта макросов: при изменении грамматики, типов, регистров, чтения указателей или вывода в 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-специфичный цикл реализуется собственным расширением VSCode-Sprinter. Готовые 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.

Текущая реализация опрашивает backend и при остановленном CPU: потеря MAME переводит DAP в terminated (проверено живым пробником с внезапным выходом). Проверенные живые lifecycle-пробники вызывают штатный schedule_exit() через Lua machine:exit() и реальный save/load state при остановке в main: оба завершают DAP, post-load удаляет старые точки. SDL3-событие закрытия окна использует тот же schedule_exit(); прямой UI-клик не автоматизировался. Reset по-прежнему требует безопасного отдельного репро. SIGTERM остановленному процессу MAME пока не дал DAP terminated за 15 секунд; это отдельный OS-signal путь, не проверка штатного выхода.

Этап 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 01, R02
Два TU с общим вызываемым inline Нет коллизий, адреса/строки обоих экземпляров 01, R01/R04
Ошибка линковки при старом ihx Ошибка сохранена, новый пакет не опубликован 1, R01/R12
Два utils.c и перенесённый проект TU identity и source mapping 1, R12
Цикл/ветка/удалённая строка/много адресов Все позиции или unverified, без ложного переноса 14, R04
Два банка с одинаковыми PC и ret Верная строка/точка, нет чужого комментария 06, R03/R11
huge W3, big W1, поддержанные manual Адреса/guards из фактической карты 15, R03
bank-data, невключённый банк, граница окна Physical чтение или Unavailable 15, R03
Watchpoint через CPU/alias Адрес, момент и PC-источник корректны 05, R03
Entry до банков, ошибка loader Нет ранней активации/ложного ready 2, R05
DSS/exit/restart/reset/state load Нет чужих попаданий и stale handles 25, R05/R12
Изменённый source или чужой exe Явное расхождение карты 15, R12
Log + user + temporary по одному PC Лог есть, остановка сохранена 24, R08
Два клиента, ручной resume, reconnect Арбитраж/события, нет повторных mutations 24, R07/R15
Одна строка в цикле, HALT/ISR/рекурсия/trampoline Ограниченный отменяемый шаг 25, R06/R13
Signed 8/16, pointer/array, CP866, нет NUL Знак/тип/кодировка, bounded read 35, R10
Горячий лог, переполнение, выход Overhead, счётчик потерь, flush 3, R09
Debug libc/libbgi fast/safe, DCE Строки/типы, нет роста кода 01, 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-документация задаёт общую семантику; при реализации фиксировать использованную версию.