
Когда в моей Среде разработки К дошла очередь до отладчика, выбирать пришлось из двух вариантов.
По HTTP, через сервер отладки, подключиться проще. Но по мобильной разработке я помнил, чем за это платят: задержками и обрывами связи - именно тогда, когда отладчик нужен.
По TCP штатная среда говорит с тонким клиентом напрямую, без сервера, и заметно быстрее. Но протокол закрытый, документации к нему нет.
Я выбрал TCP. Цель - в своём окне поставить точку, запустить клиент и остановиться на строке: со стеком, локальными переменными и шагами. Путь - подсмотреть, как подключается штатная среда, понять обмен и повторить его.

Был и третий путь - вызвать транспорт самой платформы из её библиотек. Не вышло: библиотеки отладки наружу отдают одну функцию регистрации классов в уже запущенном рантайме платформы. Транспорт без всей платформы не достать, значит - свой.
Смотрим, что передаётся по сети
Между средой и клиентом я поставил прокси и записал обмен в обе стороны, байт в байт. Потом добавил перехват через Frida, чтобы видеть данные до отправки в сокет.
Каналов оказалось два. По первому клиент сообщает отладчику события - “остановился”, “жив”. По второму отладчик подключается к самому клиенту и шлёт команды. Порт клиента он не берёт из сообщений, а перебирает диапазон 1560-1591 из настроек отладки. На соседних портах этого диапазона сидят процессы сервера - rphost на 1560, dbda на 1561-1563, - и они отвечают на те же запросы. Перепутать легко.
Каждое сообщение заканчивается меткой, соединение открывается приветствием, а раз в пять секунд клиент шлёт “жив”:
53 f5 c6 1a 7b приветствие 66 53 b2 a6 конец сообщения 21 47 54 b3 пинг; молчание дольше 5 с - и платформа рвёт соединение
После входа поток открытый: тот же скобочный формат, в котором платформа хранит всё остальное, строки в UTF-16, GUID как есть. Шифруется только вход.

Вход
Сначала я решил, что шифра нет и вход можно заглушить. Ошибся. Повтор чужого входа из записи клиент отверг за 19 мс.
Схема оказалась взаимной. Подключающийся шлёт свой открытый ключ RSA-1024 открытым текстом. Принимающий шифрует под этот ключ свой профиль - RSA с дополнением PKCS#1 v1.5 - и в том же сообщении отдаёт ещё 192 байта, зашифрованные тройным DES. Ключ и вектор для них лежат в профиле. Без этих 192 байт клиент отвергал даже правильный блок - за 2 мс.
В библиотеках платформы рядом лежит OAEP, и он сбивал с толку: к отладке он отношения не имеет. Правильную схему подтвердил опыт - я зашёл к Конфигуратору со своим ключом и расшифровал его ответ своим приватным.
Поверх - NTLM через системный SSPI, в обе стороны. Токены едут в скобочном конверте.
“Рассогласование транспорта интерфейсов”
Вход проходил, а дальше клиент отвечал этой фразой. Причин нашлось четыре, и ни одну нельзя было угадать - только найти в записи.
Идентификатор создателя - константа. Я генерировал новый GUID на каждый запуск. Клиент ждёт всегда один и тот же. Со случайным он отвечает “успех”, но вместо номера соединения кладёт пустышку.
Номер соединения меняется. Мы шлём общеизвестный, клиент в ответе выдаёт свежий. Дальше во всех вызовах нужен свежий.
Контрольная сумма считается по полям GUID. Не по байтам в печатном порядке, а по числам, как GUID хранит Windows:
/// Data1+Data2+Data3+ΣData4 — слагаемое контрольной суммы для GUID (порядок Microsoft). pub fn gsum(g: &[u8; 16]) -> u64 { u32::from_le_bytes(g[..4].try_into().unwrap()) as u64 + u16::from_le_bytes([g[4], g[5]]) as u64 + u16::from_le_bytes([g[6], g[7]]) as u64 + g[8..].iter().map(|b| *b as u64).sum::<u64>() }
Сумма ответа - gsum(соединение) + номер вызова + вид + gsum(интерфейс) + метод.
На привязку надо ответить “семь методов”. У интерфейса IDebugConnection их семь: transmitDebugCommand, debugAuthenticate, testConnection, testTargetConnection, setCreatorSideInfoTargetID, setNotifyAboutDestroyFlag, getNotifyAboutDestroyFlag. Не ответишь - ошибки нет, клиент просто ждёт и цель не регистрирует.

Почему нельзя просто повторить запись
Записанный поток воспроизводится байт в байт: тест прогоняет 1125 сообщений и 446 вызовов из записей без единого расхождения. Но отладчик повтором не построить. Команда с кодом 2, посланная не вовремя, роняет dbgtgt.dll - отладочную библиотеку клиента. А клиенту, который ничего не исполняет, негде остановиться.
Все действия идут через один метод transmitDebugCommand, отличаются кодом. Коды пришлось понять по смыслу, а не по байтам:
Код |
Что делает |
|---|---|
29 |
список точек останова |
26 |
событие “остановился” (от клиента) |
9 |
список остановленных целей; Конфигуратор шлёт его только после остановки |
8, 12, 13 |
шаг в, через, из |
10, 11, 7 |
продолжить (F5): 10 и 11 всем целям, 7 текущей |
14 |
вычислить выражение |
15 |
локальные переменные |
34 |
остановка по ошибке; текст ошибки приходит событием 25 |
Смысл полей брал из записей, а где их не хватало - из XML-схем самой платформы (debugBreakpoints.xsd, debugRTEFilter.xsd, dbgtgtCommands.xsd лежат в её ресурсах) и из разбора большого switch по кодам в dbgtgt.dll.

Первая остановка
Точке нужна версия модуля - она лежит в самом файле конфигурации. Клиенту нужен код, который точно выполнится: модуль приложения с циклом.
Первое время клиент вставал на первой строке, где бы ни стояла точка. Настройки “остановиться на входе” нет. Виноват код 9: я слал его при подключении, а Конфигуратор шлёт только после первой остановки, всем целям сразу. Переставил - клиент встал на точке.


Цикл “клавиша - команда - событие - обновление окон” занимает около 13 мс, вычисление выражения - 1 мс туда и обратно.
Остальное
После первой остановки приём один: посмотреть в записи, как делает Конфигуратор, повторить.
Шаги - в, через, из.
Вычисление выражений. Свойства объекта приходят в том же ответе, если попросить, как Конфигуратор: другой флаг и буфер 307 200 байт вместо 10 240.
Локальные переменные на каждой остановке.
Точки с условием и отключённые, “Идти до курсора” - временная точка, снимается на следующей остановке.
Точки-сообщения. Особого события у них нет: клиент просто встаёт, а сообщение собирает отладчик - выражение, стек, число попаданий - и сам продолжает.
Остановка по ошибке с фильтром по тексту.

Диалоги точки и фильтра ошибок - формы самой платформы, со всеми полями.

Точка-сообщение на строке Счетчик = Счетчик + Значение с выражением Значение печатает квадраты, и клиент на ней не встаёт. Остановился он только на точке с условием Номер = 2:

Остановку по ошибке проверял на делении на ноль: с фильтром “Деление” отладчик встаёт, с “XYZ” - нет.


В Среде разработки К
Клавиши - как в штатной среде: F5 - начать и продолжить, F9 или щелчок по полю - точка, F10, F11, Shift+F11 - шаги, Shift+F9 - вычислить. Стрелка на текущей строке, стек, локальные, табло, панель целей. F5 сам запускает тонкий клиент с ключами /DEBUG -tcp /DEBUGGERURL. Серверную сторону тоже отлаживает - проверял точку в модуле сеанса.

Что не получилось
Команды завершения в протоколе нет. Код 36 по смыслу похож на неё, но вешает клиент. Поэтому “Завершить” закрывает процесс клиента, если его запустила Среда разработки К. Клиент, запущенный со стороны, так не закрыть.
В подписи цели Конфигуратор показывает число в скобках. У меня там единица, у него двойка, а что оно значит, я пока не понял.
Итог
Среда разработки К подключается к живому тонкому клиенту по штатному TCP-протоколу: точки, шаги, стек, локальные, выражения, остановка по ошибке. База настоящая, сервер отладки не нужен.
Теперь F9, F5 - и клиент стоит на моей строке.