Перейти к содержанию

Модель DAP

Как представлен и как ведётся Debug Adapter Protocol. Крейта два: dap-protocol для типов и dap-client для самого разговора.

Bugsaur ориентируется на DAP 1.71.

dap-protocol: типы генерируются

Типы протокола генерируются из схемы DAP генератором crates/dap-protocol/generator, а не пишутся руками.

Это осознанный размен. Протокол большой и меняется на той стороне; копия, поддерживаемая вручную, расходится с ним, а расхождение проявляется как поле, которое молча никогда не приходит. С генерируемыми типами изменение схемы становится ошибкой компиляции.

Round-trip-тесты (crates/dap-protocol/tests/roundtrip.rs) проверяют, что сериализация и разбор сообщения дают то же самое значение, — а это именно то свойство, которое важно, когда на другой стороне программа, написанная не вами.

dap-client: два транспорта

Транспорт Кто использует Как работает
stdio debugpy, php Bugsaur запускает адаптер и говорит через его stdin / stdout
TCP dlv, codelldb Bugsaur выбирает свободный порт, передаёт его через tcp_argument и подключается

Кадрирование у обоих одинаковое: заголовки Content-Length, за ними JSON-тело. Реализовано в codec.rs и протестировано на битом и разрезанном вводе.

Разрезанный ввод важнее, чем кажется: граница чтения TCP может прийтись куда угодно, в том числе на середину заголовка, а кодек, считающий одно чтение одним сообщением, ломается только под нагрузкой.

Управление запросами

request.rs и менеджер запросов сопоставляют ответы с породившими их запросами по порядковым номерам протокола.

Три вещи, которые он обязан делать правильно.

Опоздавшие ответы. Ответ может прийти, когда состояния, к которому он относится, уже нет. Актуальность решает поколение остановки, и неактуальный ответ отбрасывается.

Долгие запросы. Стартовый handshake — launch / attach и configurationDone — ждут 120 секунд по умолчанию. Часть адаптеров компилирует внутри launch, а часть отвечает на configurationDone только после запуска отлаживаемого процесса. Каждые 30 секунд ожидание попадает в лог вместе с pid процесса, который держит ответ: молчащий отладчик неотличим от зависшего.

Смерть адаптера. Адаптер, вышедший посреди разговора, обязан аккуратно уронить сессию, а не оставить запросы висеть навсегда.

Чего Bugsaur не делает

Не интерпретирует тела запросов. launch_arguments из профиля уходит адаптеру как есть, перебивая сгенерированное ключ за ключом. Ни project-config, ни лаунчер не знают, что эти ключи означают: это знание принадлежит адаптеру.

Не заводит частных случаев под адаптеры в общем коде. Там, где адаптеру нужно особое обращение, оно живёт в каталоге адаптеров как данные. Это намеренный контраст с реализациями, где, например, разэкранирование питоновских строк лежит прямо в общем пути переменных.

Не логирует тела запросов. Значения env в лог не попадают никогда. Это инвариант: трассировку запросов нельзя расширять, не вычистив их предварительно, — поэтому в сообщении о долгом запросе есть только mode и program.

Тесты без адаптера

fake-dap-adapter — управляемый сценарием адаптер для тестов: он отвечает заранее заготовленными ответами, поэтому обработку протокола, редукцию состояния и интерфейс можно тестировать без установленных языковых тулчейнов и без сети.

Настоящие адаптеры покрыты ignored-тестами, которые запускаются явно:

make live-codelldb
make live-dlv-100k
make live-debugpy
make live-php-docker

Связанные решения

ADR Тема
ADR-001 слой протокола и генерируемые типы
ADR-002 транспорты
ADR-003 модель состояния и устаревшие ответы
ADR-006 дерево переменных: ленивость, пагинация, кэш
ADR-008 транспорт PHP DBGp

См. Architecture Decision Records.