Files
2026-09-17 10:23:18 +03:00

209 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Логи 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-окно. Подключённый
[MCP-адаптер C-уровня](sdbg-mcp.md) читает те же записи `output` через
`recent_events(after=...)`; он не ставит второй набор макросных точек.
Маршрут DAP проверен и с новым `MAME.HT/sprinter` 0.289 в режимах `sdbg`
и `osx`; выбор бинарника описан в [руководстве VS Code](vscode-sprinter-debug.md).
## Быстрый пример
```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`.