Суперсилы WinDbg для .NET-разработчиков

Продолжая серию публикаций по докладам на конференциях, мы остановились на лучшем докладе DotNext 2016 Moscow, в которой Саша goldshtn Гольдштейн рассказывает о возможностях WinDbg для отладки .NET приложений. Этот действительно мощный инструмент позволяет решать задачи, с которыми не справляется встроенный отладчик Visual Studio.

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

Windbg считается очень сложным инструментом, который могут использовать только хардкорные C++ разработчики. Однако в этом докладе я покажу вам, в каких случаях он может быть полезен для .NET-разработчиков и где WinDbg может использоваться для отладки .NET-приложений при решении действительно сложных задач.

Некоторые из вас уже использовали WinDbg ранее, но я надеюсь, вы почерпнете из этого доклада для себя что-то новое. А тем, кто еще не встречался с WinDbg, я надеюсь показать, что именно данный инструмент может для вас сделать.

  • Начну с некоторых советов, как сделать WinDbg более дружелюбным, чуть более простым для .NET-разработчиков.
  • Поговорю о силе скриптов и точек останова (breakpoints), которые во многих случаях помогают мне решать проблемы, как мне кажется, иначе не решаемые, особенно в Windows.
  • Расскажу о некоторых полезных расширениях. WinDbg отличается хорошей моделью расширений — вы можете загружать дополнения, которые запускают произвольный код, помогающий вам разобраться в тех вещах, с которыми сам отладчик не справляется.
  • Немного поговорим об удаленной отладке, поскольку в WinDbg этот сценарий также очень прост.

Отладчик Visual Studio, который большинство из нас использует каждый день, не справится примерно с 90% материала, который я собираюсь показать. Именно поэтому я показываю вам все это с помощью WinDbg (а вовсе не потому, что это такой забавный инструмент). В нем действительно есть сила, которой нет у Visual Studio.

Когда нельзя обойтись Visual Studio


Можно ли Visual Studio назвать мощным отладчиком?

Он неплох. Но это игрушка для людей, которым нравится пользоваться мышью или горячими клавишами. По сравнению с действительно мощными отладчиками, как JDB с IDEA, DDD или WinDbg, Visual Studio — это своего рода игрушка. В нем больше нет макросов, как в Visual Studio 2012, поэтому очень сложно расширить отладчик собственными сценариями; фактически нет расширений для самого механизма отладчика. Кроме того, Visual Studio отлично подходит, если у вас есть исходный код. Но если для конкретной вещи исходного кода у вас нет, сделать что-либо будет намного сложнее. Вы почти ничего не сможете сделать.

Поэтому мне нравится Visual Studio, но есть ряд случаев, когда нужен более мощный инструмент.

Наверное, некоторые из вас видели оригинал этой диаграммы на Reddit в редакции для Linux-дистрибутивов.
0be7962f1e1644a88049f0f4d387d794.png

Здесь Visual Studio — это самая короткая борода. Настоящие гики используют WinDbg, cdb, который является консольной версией WinDbg, и так далее. Иными словами, всегда можно найти хардкор, которому поучиться.

Итак, давайте разберем некоторые вещи, которые в первую очередь сделают WinDbg немного проще в использовании.

Делаем WinDbg менее пугающим


Интерактивное меню


Одна из причин, почему WinDbg настолько страшный — это необходимость запоминания огромного количества команд. Здесь есть несколько меню и ярлыков, но чаще приходится набирать много текстовых команд вручную.
Однако у WinDbg есть команда .cmdtree, которая создает для вас это приятное меню:

ae07ce567b734caabc212bf57ecf847b.png

По меню вы можете перемещаться при помощи мыши, запуская полезные команды. В одном из моих репозиториев есть пример файла интерактивного дерева полезных команд, сгруппированных по категориям. Подобное меню немного упрощает работу с WinDbg для начинающих.

Ссылки в выводе


Предполагаю, что многие не любят WinDbg еще и за то, что вывод команд настолько страшен. И почти всегда приходится использовать copy-paste — брать результат выполнения одной команды и передавать ее другой команде.

Для тех, кому это не нравится, есть опция, уже довольно давно включенная по умолчанию в версиях WinDbg под названием DML (DML — это язык разметки отладчика; пример я покажу вам позже). Благодаря этой опции в последних версиях отладчика выходные данные команд имеют ссылки. Вам достаточно просто нажать на ссылку, чтобы получить что-то еще.

В примере ниже я запустил команду под названием !name2ee, которая берет имя класса (в данном примере — класс C#) и дает мне некоторую информацию о классе, в частности, в какой сборке он находится.

eb40d160836d41b7b642bef7475a2681.png

В выводе команды есть ссылки, на которые я могу нажать. Например, если я нажму на ссылку рядом с полем EEClass, получу это:

592910632aa24ef28b96075bfabd3f27.png

Ссылка запускает другую команду, которая выводит информацию о типе. Как вы видите в нижней части скриншота, здесь есть Field, Type и дополнительная информация об этом поле. И снова есть ссылки. Если вы по ним перейдете, получите дополнительную информацию и так далее. Когда вы не помните всех команд, изучение результатов работы команды будет полезнее, если у вас есть этот инструментарий.

Автоматизация


Еще одна особенность, которая пугает в WinDbg — то, что некоторые простые вещи требуют ввода большого объема текста. Но есть еще одна полезная вещь, о которой многие не знают: можно запустить WinDbg, выполнить сразу несколько команд и в конце просто выйти (далее я покажу несколько примеров). По сути, WinDbg имеет ключ -c, который принимает командную строку.

Эту возможность проще продемонстрировать вживую на примере файла дампа какого-либо процесса, потерпевшего неудачу.

В папке C:\temp у меня сохранено несколько файлов дампов.
Также у меня есть WinDbg и cdb — как я сказал, это консольная версия WinDbg.
Открою при помощи WinDbg (cdb) один из дамп-файлов.

cdb.exe -z C:\temp\FileExplorer.exe.14804.dmp -c ".logopen C:\temp\crash.log; !analyze -v; .logclose; q"

-z — переключатель для дампа.
-c — позволяет сразу после открытия дампа запустить команду.

Главное — не забыть закрыть файл и выйти в конце.

".logopen C:\temp\crash.log; !analyze -v; .logclose; q" — эта командная строка анализирует дамп-файл, предоставляя мне некоторую полезную информацию.

.logopen открывает файл лога, куда и осуществляется вывод (лог-файлы использовать удобнее, поскольку мы можем проанализировать его позже, применяя поиск по строке внутри файла).

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

В результате мы получаем информацию о том, что произошло в дампе. В конце вывода содержатся данные об эксепшене, стеке вызова и прочие полезные детали.

Еще один пример. Здесь я запустил findstr в результатах вывода, чтобы найти имя упавшего процесса и какая именно функция вызвала exception.

7e93a49efc9d408db30a841ceef545c3.png

Также здесь есть информация об операционной системе, версии CLR — в общем, много полезных вещей.

И снова пример — метод, который я часто использую в поисках утечек памяти. Когда у вас есть утечка памяти, видно, что процесс становится все больше и больше. И вам необходимо периодически подсоединяться к нему, а затем отсоединяться. Вот как это можно сделать с WinDbg.

e0a2eb21284845b984b705e1bc407219.png

Я запускаю cdb (поскольку он мне нравится больше) с ключом -pn для процесса, в котором может быть утечка памяти (к которому хочу присоединиться). Далее я даю команду !dumpheap, которая выводит статистику объектов .NET heap. Ключ -min означает, что в данном случае мне нужны только объекты размером более 10000 байт. В конце с помощью qd завершаем работу и отключаемся.

После запуска этой команды WinDbg подключается к процессу, выводит топ объектов в heap размером более 10000 байт и отключается от процесса. Иными словами, это почти мгновенный способ выяснить, что происходит с памятью. То же самое можно запустить в пакетном режиме. Удачи тем, кто хотел бы сделать что-то подобное в Visual Studio.

af63b323111821ad8f739e1161e4a4ff.png

Скриптовый язык WinDbg


Я надеюсь, что приведенные примеры автоматизации доказывают, что когда у вас есть мощный отладчик, вы можете запустить его в автоматическом режиме, написать сценарий и использовать его многократно. Но чтобы действительно воспользоваться плюсами переиспользования, вам необходимо понять, как работает скриптовый язык — самая сложная и «ужасная» часть WinDbg.

WinDbg имеет встроенный язык сценариев, но этот язык никогда не разрабатывался целенаправленно. Он не похож на C#, дизайном которого управляет отдельный комитет. Это язык, который появлялся постепенно, кто-то добавлял в него что-то, кто-то другой — исправлял. Так получилось то, что мы имеем.

Давайте взглянем на этот скрипт (существует еще много примеров, есть соответствующие учебники, но мы не будем превращать эту беседу в учебный курс по скриптам в WinDbg).

7e93a49efc9d408db30a841ceef545c3.png

Здесь первая строка инициализирует переменную $t0 со значением 0. Это легко. Вторая строка помещает точка остановка в определенную функцию. Функция NtAllocateVirtualMemory — это API-интерфейс Windows, который выделяет память. Все распределение памяти так или иначе должно пройти через этот API.

Всякий раз, когда мы попадаем в точку останова (когда вызываем эту функцию), я выполняю команду внутри кавычек: увеличиваю переменную $t0 с помощью какого-то страшного выражения (здесь переменная rdx содержит объем выделенной памяти).
Затем я ввел g, чтобы приложение продолжало работать. После я использую команду .printf, чтобы распечатать текущее значение переменной $t0, которая сообщит мне общий объем выделенной виртуальной памяти.
Иными словами мы устанавливаем точку останова. Каждая точка останова увеличивает переменную на количество выделенных байт. Когда мне необходимо, я просто вывожу значение этой переменной, получая информацию о том, сколько памяти было выделено.

Breakpoints


Общий подход, заключающийся в том, чтобы где-то поставить брейкпоинт и сделать что-то для вас, чрезвычайно эффективен. В Visual Studio многие используют breakpoints, чтобы просто останавливать отладчик. Это отлично, я тоже так делаю. Но реальная сила точки останова в том, что она может работать на вас (а не вы на нее).

Вот пара примеров этого.

Скажем, у меня есть приложение, создающее файлы, но я не знаю, откуда эти файлы. Приложение продолжает создавать файлы, но не удаляет их, а блокирует. Таким образом, пока приложение работает, я не могу удалить эти файлы. Наверняка многие сталкивались с подобной ситуацией.

Предположим, я хочу выяснить, откуда эти файлы. Есть несколько способов сделать это. Самый простой — добавить брейкпоинт. Давайте поместим точку останова туда, где мы создаем файлы, и посмотрим, откуда они берутся.

9c2060eb130046bfa3f3fb8f2cea303f.png

В Windows API существуют две основные функции для открытия файлов: CreateFileW и CreateFileA.

Всякий раз, когда я вызываю одну из этих функций, я вывожу имя открываемого файла.
Здесь @esp — указатель стека для 32-битных процессов (x86), а @esp+4 — первый параметр функции (об этом помнить не обязательно, все это можно выяснить, зайдя в отладчик). Если вы вызываете CreateFileW, имя файла является строкой Unicode, поэтому я использую формат %mu, а если это CreateFileA, то имя файла — строка ANSI, и тогда я использую %ma.

Далее я печатаю имя файла и три черточки. Команда k показывает мне стек вызовов. В результате всякий раз, когда мое приложение открывает файл, я получаю сообщение в моем отладчике о том, какой файл я открываю (стек вызовов показывает как мы туда попали). Пример выше — это стек вызовов С++, но то же самое можно сделать и для приложения .NET. Рассмотрим пару дополнительных примеров.

Предположим, мое приложение жалуется на недостающие файлы — на то, что какой-то файл не удается открыть. Но оно не говорит мне, почему и где именно. Я получаю лишь глупое сообщение об ошибке «не могу открыть файл» или что-то в этом роде. Такое происходит довольно часто.

Просто поставим брейкпоинт, правда, немного более сложную. Она должна выявить, что произошла ошибка (я не смог открыть файл). Для этого мы помещаем её в CreateFileW (в примере ниже небольшая ошибка: смещения 0x61 там быть не должно, прошу его игнорировать).

9453d8d07aa04906b8385cca394a27cc.png

Итак, мы помещаем breakpoint в CreateFileW. Команда gu будет выполняться до возвращения из этой функции. После остановки мы проверяем регистр @eax. В 32-битной Windows @eax хранит результат функции. На 64-битных Windows по существу ту же нагрузку несет @rax. Если возвращаемое значение равно нулю, значит создать файл не удалось. Если отладчик видит, что вызов не сработал, я распечатываю, какой именно файл мне не удалось открыть (и снова я могу распечатать стек вызовов, где это произошло).

Опять отладчик работает на меня, а не наоборот. Я не пытаюсь найти в своем коде все места, где я мог бы открыть файл. Я просто говорю отладчику: «дай мне знать, когда встретится сбой открытия файла».

Вот реальный пример из StackOverflow.

685b8cbe54c54463bfb4f92791bc79eb.png

Какое длинное описание проблемы! Этот парень пытается что-то понять. И в конце он задает такой вопрос: «Это определение вызывает вопрос: кто же вызывает VirtualAlloc? Это диспетчер heap или рантайм .NET?»

Думаю, вы знаете, как справиться с этой задачей.

ab691b15f7bf4ac1b5fd93c24a86dfce.png

Мы устанавливаем точку останова в VirtualAlloc и выясняем все необходимое. Все довольно очевидно — вы хотите знать, кто вызывает какую-то функцию? Поместите туда точку останова и получите нужную информацию, что может быть проще? Итак, мы помещаем точку останова в VirtualAlloc, а затем выводим, сколько памяти выделено. Также мы печатаем — это пример, о котором я говорил, — стек вызовов управляемого кода (!clrstack).

Итак, в этом примере вы видите: я выделяю виртуальную память из сборщика мусора. В стеке gc_heap, grow_heap_segment, virtual_alloc_commit_for_heap, который был вызван XMLDictionaryReader для чтения содержимого.

Техника размещения брейкпоинта где-либо с последующей обработкой, возможно, самая важная вещь, которую я даю в рамках этого доклада. Это невероятно мощный инструмент, которого в Visual Studio просто нет. Visual Studio хорош, когда у вас есть исходный код, в который вы хотите вставить точку останова. Но когда приходится работать с пользовательскими действиями, условиями и т.п., в Visual Studio вы найдете только базовую поддержку. С WinDbg вы можете сделать гораздо больше! Также WinDbg намного лучше, если у вас нет исходного кода, в который вы хотите вставить точку останова (например, если вы планируете ставить её в вызовы Windows API или некоторые внутренние функции CLR). Я не могу сказать, что WinDbg очень дружелюбен, зато он точно очень мощный.

Переключимся на другой пример. Предположим, у вас есть эта огромная коллекция объектов, в которой есть один плохой объект. Чем объект плох? Например, он содержит символ a с маленькой точкой, ломающей кодировку. Этот «сломанный» объект я и хочу найти.

51dff06b33d44cc5aac6993c4eea598a.png

Не очень красиво, зато работает. Ближе к концу доклада я покажу другой способ решить эту задачу. Итак, сначала мы найдем, где находится класс: !name2ee OrderService!OrderService.Order.

Это тот класс, который я ищу — Order. Хорошо. Далее делаем дамп класса, чтобы выяснить, где находится нужное мне поле (относительно начала объекта).

Меня интересует поле Address. Как мы видим, здесь смещение 4 относительно начала объекта:

0a0e02125a514a2f88dd756a99cbf3d7.png

Все, что осталось сделать, — это найти в моем хипе объекты Order и обнаружить строку со смещением 4 от начала объекта. После этого я должен заглянуть внутрь строки и посмотреть, нет ли там «а с круглой точкой сверху».

Не буду лишний раз переписывать строку — это лишнее, однако упомяну несколько ключевых моментов:

  • Здесь есть цикл .foreach, который запускает отладчик. Это довольно «crazy». И в этом цикле .foreach есть дополнительный оператор .if — т.е. реальная программная логика. Не то, чтобы не существовало другого способа сделать это, но, возможно, самый простой способ реализовать подобное при помощи WinDbg — писать собственный код, чтобы проанализировать коллекцию объектов (вы можете это сделать, если у вас есть доступ к системе, которая исполняет процесс).
  • В результате у некоторого объекта поле адреса действительно имеет, а с точкой сверху.
  • Это и есть тот объект, который я искал:»233 Håmpton Street». Идея заключается в том, чтобы отладчик исполнял программу  для нас.

В последнем примере (перед тем, как перейти к расширениям) я покажу несколько крутых команд.

Существует таинственная команда wt, немногие знают, что она означает. Она трассирует исполнение кода. Вы берете функцию, запускаете wt и она распечатывает все вызовы, которые осуществляет эта функция. При этом вы можете ограничить глубину трассировки.

К примеру, мне было любопытно, что делает сборщик мусора во время mark_phase. Итак, давайте поместим точку останова в функцию сборщика мусора под названием mark_phase и запустим wt.

31759084d07a4cadaeb95e4a630c6e9f.png

В результате вы получите это красивое дерево (я ограничил глубину единицей) — вы получаете все функции, вызываемые mark_phase. Если вам интересно, как работает сборщик мусора, здесь есть много деталей (вызовы generation_size, GcScanRoots, scan_background_roots и кучи других функций). Полный вывод включает несколько страниц. В конце вы найдете отчет со всеми функциями и количеством выполненных команд.

8914786431068db8c1a07a90ee1864dc.png

Таким образом, вы можете увидеть наиболее «дорогие» функции с точки зрения количества инструкций: mark_through_cards_for_segment, mark_through_cards_for_large_...  и т.п.

Это похоже на профайлер прямо внутри отладчика. Важно подчеркнуть, что профилирование идет шаг за шагом по инструкциям программы. Это очень медленно, т.е. для профилирования как такового не подходит. Но это очень хорошо для понимания, что происходит в конкретном методе.

Расширения


В этой части разговора я покажу вам пару полезных расширений.

PyKD


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

51777d963ee44b98861d5457818a8055.png

Не так плохо, как сценарий, который вы видели раньше. Это довольно приятное расширение, получившее имя PyKD. Что делает PyKD? Оно позволяет запускать Python для автоматизации WinDbg — т.е. запускать команды WinDbg, анализировать выходные данные, использовать инструменты Python с WinDbg.

Пример выше не очень интересен. Здесь я просто пытаюсь исправить стек, если он «сломан». Однако идея состоит в том, что у вас есть API-интерфейс Python для большинства вещей, которые может сделать WinDbg. Один из способов не писать на ужасном языке WinDbg — использовать Python.

У меня есть, как мне кажется, неплохой скрипт, созданный с использованием PyKD, который я назвал heap_stat.py.

962a1fad11b74fd4be58971195798c81.png

На самом деле он не для .NET, а для приложения на C++, но он дает разработчикам C++ часть того функционала, который я показал раньше (просмотр heap и того, какие объекты там есть). Для .NET, как вы видели, что это довольно легко. Для C++ это немного сложнее.

Данное расширение — это скрипт Python, который работает с heap C++, находит объекты и выводит количество объектов этого типа. В некоторых случаях он также может вывести общий размер. Сделать это, используя только скриптовый язык WinDbg, было бы очень сложно, а с помощью Python получается даже приятно. Это же Python, в конце концов.

Модель расширений

Давайте посмотрим на некоторые другие расширения. В принципе модель расширения довольно проста.

72fe59698c7045f69adb21d2121a4d76.png

Каждое расширение для WinDbg — это просто dll. Вы можете написать ее на C++ или С# — на любом языке, который поддерживает экспорт функций. А затем вы передаете ваше расширение отладчику и просто запускаете каждую функцию в виде команды.

Расширение имеет доступ к API отладчика. Допустим, если ваше расширение захочет вывести что-то, например, чтобы посмотреть объекты в памяти, оно получает доступ к интерфейсу отладчика.

Вот простое расширение, которое я сконструировал, чтобы продемонстрировать, как в принципе они выглядят:

681ebbf0fd334ad0bd502048442770c0.png

Это расширение просматривает содержимое по URL-адресу. Из отладчика вы можете выполнить HTTP request и распечатать полученный HTML-код.

Расширение написано на C#, и оно довольно простое, давайте посмотрим, как как выглядит результат. Для этого я запускаю WinDbg и любой процесс, к которому можно присоединиться (например, Блокнот). После этого я загружаю мое расширение — теперь я могу пойти, например, на google.com. И если интернет-подключение работает, мы получаем такой результат.

83e031b1e0084efa930ba9e8e4639834.png

Вероятно, здесь русский язык (у меня нет кодировки).

Интересно, что в выводе прямо в отладчике присутствуют ссылки. Это делает его использование более открытыми для пользователя. К примеру, если я нажму здесь на Blogger, внизу экрана вы увидите, что отладчик выполняет другой запрос — на другую страницу. В итоге у нас есть супер простой текстовый браузер, встроенный в отладчик.

Выше я привел очень простой пример расширения отладчика, но, я надеюсь, он послужит для вас отправной точкой. Если вы заинтересованы в создании собственных расширений, которые делают что-то действительно мощное, можно использовать этот подход для разработки своих собственных расширений. Это не сложно, и вы можете создавать расширения на C#.

Давайте рассмотрим несколько существующих расширений (далеко не всегда нужно писать свои собственные).

CMKD


Вот пример типичной ситуации, с которой мне приходится сталкиваться при отладке 64-битного кода. 64-битный код в Windows имеет соглашение о вызове, из-за которого очень трудно получить аргументы функций при отладке оптимизированного кода. Возможно, вы не сталкиваетесь с этим каждый день, но время от времени придется упираться в то, что при отладке вам нужны аргументы, но получить их вы не можете — отладчик их не показывает. Так происходит и в Visual Studio, и в WinDbg.

72e104d03af742219df6cebf62152815.png

В этом примере присутствует функция WaitForMultipleObjects, которую вызывает мое приложение. И мне нужно знать, с какими аргументами она вызывается. Но отладчик говорит: «нет данных, нет данных, нет данных, нет данных». У функции 4 аргумента, поэтому, видимо, для ясности, он говорит «нет данных» 4 раза.

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

23442ab4877b43e38603aeb5d5411374.png

В данном случае мы загружаем расширение, а затем запускаем команду !stack. В качестве результата он выдает вызванную функцию WaitForMultipleObjects и ее аргументы. CMKD использует эвристику, поэтому нам нужно проверить его гипотезу.

Помимо самих аргументов, расширение сообщает о том, как именно был сделан такой вывод. В этом случае, например, расширение сообщает, что этот аргумент был в регистре rdx и функция, которую мы вызывали, фактически помещает этот аргумент в другой регистр с именем r13. r13 какие-то другие функции позже перемещают в стек, сохраняя в памяти. Желаю удачи тем, кто попытается сделать что-то подобное самостоятельно.

Это расширение просто возвращает аргументы, избавляя вас от очень большого количества раздражающей работы. Мне оно очень нравится, и я часто его использую для .NET-кода, для кода C++ или 64-битного кода в Windows (особенно если компилируется оптимизированный код, из которого действительно трудно восстановить значения аргументов).

SOSEX


Еще одно интересное и действительно важное расширение — SOSEX. Готов поспорить, многие из вас, кто использовал WinDbg раньше, уже использовали SOSEX — это именно то, что нужно для отладки .NET в WinDbg.

Расширение SOSEX было разработано парнем по имени Стив Джонсон. Он на самом деле является инженером в Microsoft, но это его собственный проект (работа по созданию SOSEX не является его основной задачей). И он продолжает развивать расширение.

Что мне действительно нравится в SOSEX — это индексация heap. Если вы хотите посмотреть на объекты в heap .NET, выяснить, как они ссылаются друг на друга, но объем heap при этом очень большой — допустим, 10 Гб — любая запущенная команда, будет выполняться очень медленно.

Что делает Стив? Сначала он создает индекс heap — подобно базе данных — с помощью которого многие вещи можно делать быстрее. Рассмотрим это на примере:

264eab0aa2604ff2ab5e14cd6c6b74e1.png

Здесь я использовал цикл — снова цикл .foreach — для каждого байта моего heap я хочу найти, кто на него ссылается (например, в поисках утечки памяти). Команда !bhi создает индекс, а команда !mroot использует индекс, чтобы сообщить мне, например, что на объект Byte ссылается объект Schedule, на который, в свою очередь, ссылается объект Employee.

Здесь дело в скорости. Сначала попробуем решить задачу без построения индекса — в SOSEX для этого есть !gcroot. Я запускаю его 30 раз (для 30 объектов) — это занимает около 5 секунд. В примере выше у меня было шесть тысяч таких объектов, поэтому я просто не мог дождаться, когда появятся результаты (поэтому мы запускаем команду всего для 30 объектов). В то же время выполнение !mroot для 30 объектов занимает 0 миллисекунд (я действительно не смог измерить его — похоже, нужно больше итераций, чтобы получить значимое время). Таким образом, индекс действительно очень ускоряет процесс. Если у вас большой heap, но вы хотите проанализировать его в WinDbg, ваш путь — это расширение SOSEX. Само построение индекса занимает немного времени — редко более пары минут даже для большого heap. И впоследствии индекс экономит вам много времени.

netext


Еще одно очень крутое расширение, которое я хочу вам показать, называется netext (https://netext.codeplex.com/). Это также создал инженер Microsoft из Бразилии — Родни Виана. И снова это его собственный, а не рабочий проект. Расширение было разработано для отладки приложений ASP.NET. И в нем есть действительно классные функции, связанные с поиском объектов в heap.

d2b4ee5c89c645b4ac7ff840dfe9d0c0.png

В этом примере я загрузил расширение, а затем запустил команду !wruntime, которая выводит информацию обо всех приложениях ASP.NET, которые у меня запущены. Это уже очень полезно.

Затем запускаю команду !whttp, которая выводит все HTTP-запросы, находящиеся в heap. Таким образом можно отследить выполнение HTTP-запросов, которые мой сервер обрабатывает прямо сейчас (или они только что завершены). Результат выполнения команды показывает мне, какие именно есть запросы, каково было время выполнения, статус, результат и так далее.

f7d4616104674a90825b888a491846f6.png

О каждом HTTP-запросе можно получить дополнительную информацию: когда был выполнен этот запрос, информацию об ответе (если уже есть ответ) и т.п. Если вы отлаживаете приложения ASP.NET, это расширение будет вам чрезвычайно полезно.

netext также имеет встроенный SQL-подобный синтаксис запросов. С его помощью вы можете запрашивать объекты из heap. Это не совсем SQL, но становится очевидно, откуда эта идея.

34086fda80544fc986085cce64faca9a.png

Итак, здесь мы из объектов HttpContext выбираем поля _request._rawUrl и _response._statusCode. Синтаксис позволяет ходить по управляемым объектам по принципу a.b.c. В данном случае он распечатывает все выполняющие HTTP-запросы.

Помните пример с поиском сломанного объекта (а с точкой сверху)?

5e79ed9ab7fc48af95f063d0b48e2bcc.png

Вот как мы можем решить задачу с netext. Это немного легче. Из всех объектов Order, у которых свойство Address имеет, а с точкой, мы берем $addr и фактическую строку адреса. В результате мы находим этот объект довольно быстро. Команда просто пробегает по всем объектам и применяет этот фильтр — задача решается с использованием некоторого более простого синтаксиса.

netext хорошо документирован, есть куча более полезных примеров на его сайте.

tracer


И последнее расширение, которое я хочу показать (или, по крайней мере, упомянуть) — мое собственное. Оно называется tracer. Это расширение WinDbg, которое я создал для отслеживания утечек.

Предположим, у вас есть подключения к базе данных, которые вы открыли, но забыли закрыть, или есть открытый, но не закрытый файл. tracer позволяет выяснить, сколько вещей и где (в терминах стека вызовов) вы открыли, но не закрыли — это его основная идея. Он позволяет не тратить время на то, чтобы исследовать все это вручную.

71fd48fd7668455f8e254500f1e46888.png

Удаленная отладка


Еще пару минут вашего времени я собираюсь потратить на удаленную отладку WinDbg, поскольку это еще одна функция, которая, на мой взгляд, делает этот инструмент гораздо более мощным и полезным, чем Visual Studio.

Если вы когда-либо занимались удаленной отладкой с помощью Visual Studio, самая большая проблема для вас была в том, что нужно было настроить аутентификацию Windows. Вы должны либо использовать домен, в котором находятся оба компьютера, либо установить локальных пользователей с одинаковыми именем и паролем на обеих машинах — совсем нетривиальный процесс. В WinDbg таких требований нет, вы можете использовать пароль, вы можете использовать различные транспорты, включая именованные каналы, TCP, SSL, просто HTTP — доступна уйма опций, среди которых можно выбирать.

Существует два основных способа отладки.
Рассмотрим первый способ.

b954fce3c0274f089723c906124ed68b.png

Я запускаю cdb в режиме сервера (мы будем использовать TCP-порт 5050 с паролем) и запускаю для отладки Блокнот.

411ccbbf855e4a12bd4086b38f7f2584.png

Открылся отладчик, который теперь ожидает соединения. После этого из другой командной строки я собираюсь перейти в папку x86 и выполнить:

cdb -remote tcp:server=localhost,port=5050

Таким образом сеансы отладки у нас синхронизированы: все, что я ввожу в одном окне с командной строкой, происходит в другом, и наоборот.

К этому сеансу я могу подключить более одного клиента. В Microsoft это часто используется для совместной отладки людьми, работающими удаленно. Вы можете заставить друзей подключиться к вашему отладчику и работать вместе. Очень полезно.

Еще один режим отладки WinDbg, — это режим Smart Client.

5de566f4394f46e2bcd88cb7794e66bf.png

В отличие от предыдущего примера, здесь вы не запускаете WinDbg с одной стороны и WinDbg с другой. Вы запускаете маленькую вещь под названием dbgsrv.exe, которая является просто тупым агентом, в фоновом режиме слушающим соединения.
В рамках этого подхода хорошо то, что к серверу могут подключаться несколько людей и отлаживать независимые процессы. К примеру, один может отладить Блокнот, а другой — приложение ASP.NET.

Можно отлаживать одно и то же приложение. Для этого надо, чтобы один клиент подключился к удаленному агенту, а другой клиент — к первому клиенту.

На этом я заканчиваю рассмотрение WinDbg. Надеюсь, что я смог убедить вас в том, что свой «игрушечный» отладчик вы можете продолжать использовать для простых вещей в рамках ежедневной отладки. Но если вы сталкиваетесь с чем-то действительно сложным, где ваш «игрушечный» отладчик не может помочь, стоит потратить немного времени на изучение чего-то более мощного. Точки останова, размещаемые в любом месте, чрезвычайно эффективны. Кроме того, существуют действительно волшебные расширения, которые могут делать за вас много вещей. Не забываем и об удаленной отладке, которую я показал вам только секунд назад. Она чрезвычайно полезна и не сопровождается такими сложностями, как в Visual Studio.

Несколько ссылок вместо заключения. Мои собственные расширения и скрипты WinDbg:

  • https://github.com/goldshtn/windbg-extensions

CLRMD может помочь в определенных сценариях при отладке команд синтаксического анализа:
  • https://github.com/Microsoft/clrmd

Дополнительные расширения:
  • https://netext.codeplex.com/
  • http://www.stevestechspot.com/
  • http://www.codemachine.com/tool_cmkd.html

msos — CLI-дебаггер, написанный на C#:
  • https://github.com/goldshtn/msos


20-го мая можно будет прослушать новый доклад Саши Гольдштейна — «The Performance Investigator’s Field Guide», который он представит в Питере на нашей DotNext 2017 Piter (сама конференция в этот раз двухдневная — 19–20 мая).

© Habrahabr.ru