Вскрываем дамп NodeJS процесса с llnode

Научиться дебаггить дампы .net core процессов на Linux при помощи lldb и SOS плагина оказалось очень приятным опытом. Всё-таки «дамп процесса» звучит как нечто крайне низкоуровневое, а тут можно и на управляемые потоки посмотреть, и в памяти покопаться. В общем, приятное колдунство. И между прочим, lldb плагины были не только для .NET. Я видел ещё и для Python, и для Java.  Интересно, а есть ли что-нибудь похожее для NodeJS и JavaScript?

А то!. Есть плагин под названием llnode, который как раз с NodeJS и работает. Сегодня я не буду копать в нём слишком уж глубоко, всё-таки со скриптом в последнее время практически не работаю. Но посмотреть, как это вообще делается, интересно. Так что приступим.

Устанавливаем инструменты

За отсутствием другого железа сегодня я буду работать на живом Маке и macOS, так что не будет ни докера, ни всяких apt-get. NodeJS v8.9.4 тут уже установлен, так что всего-то осталось доставить llnode и lldb. Если бы макось не стремилась превратиться в новую Windows, то сделать это можно было бы всего в две команды:

  1. brew update && brew install --with-lldb --with-toolchain llvm для lldb, и
  2. sudo npm install -g llnode для llnode.

Но реальность — беспощадная штука, так что макось жаловалась на всё. «Пожалуйста, установите XCode», «Пожалуйста, настройте сертификат для кода», «Доступ запрещён». В конечном итоге мне удалось добыть всё необходимое, но llnode целиком так и не поставился. Это не такая уж и проблема, потому что сам по себе llnode — это шелл скрипт, который запускает lldb и загружает в него llnode плагин. Файл плагина успешно установился, загружать его в lldb я умею, так что того, что есть, должно хватить.

Последним штрихом включим автоматическое создание дампов упавших процессов —  ulimit -c unlimited — и можно начинать.

Добываем дамп процесса

Теоретически, следующий кусок JavaScript должен обязательно упасть:

А отправив его в NodeJS вот таким образом, можно и дамп получить в придачу:

Создание дампа занимает некоторое время, потому что даже для такого мелкого скрипта размер дампа занял аж 1.7 гига бесценного SSD пространства. Не самый большой дамп в моей карьере, но и не самый маленький.

Отлично. Дамп есть, полезем внутрь ковыряться.

Смотрим внутрь

Вообще говоря, последовательность действий точно такая же, как и с .NET Core — открываем дамп, загружаем плагин:

Так же как и SOS, llnode добавляет в lldb несколько своих команд, все из которых начинаются с v8.  Запомнить легко. Если просто ввести v8, то можно посмотреть, какие команды для JavaScript есть в принципе:

  • bt
  • findjsinstances
  • findobjects
  • findrefs
  • inspect
  • nodeinfo
  • print
  • source

bt

bt — обычно самая первая команда, которой смотрят, на чём же упал процесс. Для NodeJS/JavaScript это не исключение:

И на этом вскрытие можно было бы завершать, потому что имя проблемной функции уже видно в предсмертном стеке — delayedFailure. Но мы поковыряемся ещё немного.

Смотрим в исходный код

Ковыряясь в стеке упавшего процесса, можно, оказывается, запросить исходный код для его фреймов. Например, проблемная функция delayedFailure была в пятом фрейме:

На него можно переключиться при помощи frame select и посмотреть, из чего же эта она сделана:

Она сделана не только из ассемблера. Есть команда v8 source list, которая должна вернуть JavaScript, и которая, если верить StackOverflow, раньше работала как часы. Но не сейчас.

Теперь, чтобы она заработала, нужно подавать ей на вход ещё и адрес фрейма:

Как посмотреть на JavaScript объекты

В нашей программулине никаких пользовательских объектов не было, так что можно было бы на этом и остановиться. Но сам NodeJS наверняка создал что-нибудь от себя, так что можно посмотреть на его творения:

Для найденных объектов можно поискать их экземпляры. Делается это при помощи команды findjsinstances. Например, чтобы найти все console:

Найденные экземпляры можно препарировать ещё дальше. Для этого есть команда inspect:

Оказывается, экземпляры JavaScript объектов сделаны из… свойств. Внезапно.

Мораль

В общем, что работать с .NET Core дампами, что с NodeJS — разницы практически никакой. Что там, что там — стеки, память,  и т.п. Но SOS плагин для .NET Core, правда, нёс с собой больше команд. Для работы с тем же сборщиком мусора и Just-in-time компиляции, например. И то, и другое есть и в NodeJS/JavaScript, так что, видимо, диагностировать их нужно при помощи чего-то другого. Но для базовой работы с дампами llnode должно вполне хватить.

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

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