Тайна «Debug adapter process has terminated unexpectedly»

Любопытная история приключилась со мной намедни. Моя основная C# IDE на никсовых системах — Visual Studio Code — внезапно приказала долго жить. Не целиком, а самая полезная её часть — дебаггер. Стоило мне поставить брейкпоинт где-нибудь в коде и запустить проект, как редактор останавливал праздник интеллекта с ошибкой «Debug adapter process has terminated unexpectedly»:

Debug adapter process has terminated unexpectedly

A вот раньше всё работало просто прекрасно. Так как незадолго до проблемы я устанавливал на Убунту сразу несколько новых связанных с дебаггингом утилит (lldbperf и lttng), то естественно подумал, что проблема пришла от них. Но ни их удаление, ни переустановка VS Code целиком не изменили ровным счётом ничего.

В довесок к этому единственная внятная альтернатива VS Code для работы с C# на Linux — JetBrains Rider — тоже косячит. Какие-то проекты она может запустить и дебаггить, какие-то — нет. Те, что мне нужны, например, она даже запустить не может. В общем, не оставалось ничего другого кроме как начать разбираться, кто убил VS Code.

Ищем файлы дампов

Ни один процесс на моей Убунте не может умереть насильственной смертью и не оставить после себя дамп файл. Эту фичу я включил достаточно давно для другой задачи, и вот теперь она снова оказалась полезной. Ведь если сообщение об ошибке выглядит как «Debug adapter process has terminated unexpectedly», то, значит, был процесс, была насильственная эвтаназия, и, следовательно, должен быть дамп. И он нашёлся! Прямо в папке VS Code, в плагине, отвечающим за C#:

Судя по имени, падал vsdbg-ui. Он лежит в этой же папке, и теперь можно натравить на него lldb и посмотреть, от чего же тот слёг.

Смотрим предсмертный стек

Итак, берём дебаггер и смотрим в дамп:

Обычно первой командой в таких случаях я запускаю  backtrace — показать стек текущего потока. Часто наводит на умные мысли:

Круууто! То есть процесс скончался, печаль-печаль, но судя по libcoreclr.so это был .NET Core процесс и у меня с этим больше шансов разобраться, чем с каким-нибудь C++. Более того, в стеке нашлись слова FinalizerThread и DispatchManagedException, что означает, в очереди деструкторов кто-то бросил ошибку и тем самым завершил процесс. Теперь только надо выяснить, где и что это была за ошибка.

Так как это дамп .NET Core процесса, стоит сначала включить SOS плагин и работать уже с нормальными managed потоками и объектами.

Переходим к управляемому коду

Самый простой способ раздобыть SOS плагин — отдолжить его у .NET Core SDK. У меня как раз есть такой. А с плагином можно получить команду sos PrintException и узнать, что же там за ошибка была.

Так, оно жалуется на отсутствующий libsos.so. Я определённо знаю, что в .NET Core SDK есть и такой, так что можно тупо скопировать его оттуда в текущую папку и попытаться снова:

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

Разбираемся с потоками

И ещё раз «Круууто!». Напротив самого первого потока (OSID — 2c42) стоит отметка, что он бросил System.NotSupportedException ошибку. И даже слово Finalizer сбоку — деструктор, то есть. Определённо наш клиент. И там же адрес ошибки — 00007fa5e42303a0, так что можно заглянуть внутрь:

Свойство _message тоже не пустое и можно узнать текст ошибки:

Зашибись. Какой-то объект не поддерживает метод «GetObjectID». Интересно, какой. Нужно снова посмотреть в поток.

Находим стек ошибки

В прошлый раз PrintException не сработал, потому что текущий поток — не дотнэтовский поток. Но можно привязать проблемный дотнэтовский поток к текущему, нативному, и SOS, возможно, успокоится. Какой там текущий нативный поток?

Тоже первый. В общем, надо указать, что CLR поток #1 (OSID — 2c42) и нативный #1 — одно и то же лицо.

Прекрасный стек, просто прекрасный. Теперь нужно лезть в гугл и искать, о чём этот код вообще. .NET Core же open source теперь.

Работаем с сырцами, и убийца это…

Было достаточно просто найти код InteropExtensions.cs,  в котором полагалось быть GetObjectID методу. Там такой есть, и он реально бросает ошибку.

Смотрим, кто его вызывает, и сырцы для __ComObject.Cleanup оказались более полезными, потому что все места, в которых вызывается GetObjectID, выглядят вот так:

То есть это какой-то встроенный логгинг/трэйсинг. Идём немного в сторону от стека и смотрим, когда InteropEventProvider.IsEnabled может стать true. Великого откровения там тоже нет, но есть интересная идея.

Там внутри есть EventSource, и я о нём слышал. Одна из вещей, которые делал параллельно с установкой всех этих lldb/lttng и прочих, была установка переменной среды: COMPlus_EnableEventLog. Она включает трассировку в .NET рантайме, мне это было нужно для одной задачи (и блог поста). Рантайм он и есть рантайм, и на vsdbg-ui этот флаг бы тоже распространился. Что если дело в нём? Что если vsdbg-ui, поставляемый с VS Code C# расширением, не поддерживает трассировку по крайней мере на моей машине?

Через минуту я убираю переменную, рестартую сессию, и VS Code снова работает как часы.

Мораль

Есть в мире истории, которые завершаются хэппи эндом, но не совсем удовлетворительным, потому что осталась уйма вопросов. Эта одна из таких. Например, судя по коду, EventSource будет работать только под WinRT билдами. Но ведь у меня Убунта, какой к чертям WinRT?! Потом, было на самом деле две реализации InteropExtensions класса в CoreRT репозитории,  и дебаггер почему-то выбрал «неправильную». Как же так? И вообще, почему я не могу повторить эту же ошибку на чистой Убунте с каким-нибудь «Hello World» приложением?

Но с другой стороны, я получил свой дебаггер назад. Если вы думали, что дебаггить JavaScript через alert — верх идиотизма, попробуйте тот же принцип, но с Console.WriteLine и C#.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *