понедельник, 8 марта 2010 г.

Pro Debugging: А вы умеете?

Привет, Друзья!

Сегодня мы отвлечемся от Silverlight'a и пройдемся по самому популярному инструменту .NET разработчика: по отладчику Visual Studio, в контексте языка C#. Прежде чем вы подумаете, что я окончательно спятил, если собираюсь рассказывать очевидные вещи, позвольте возразить. Мы, разработчики ПО, склонны останавливаться на выученном, после того, как думаем, что знаний достаточно для выполнения работы. В то же время, копнув глубже, можно найти более эффективные способоы решения проблемы. Думаю, это одно из отличительных свойств настоящих Специалистов - желание всегда копать глубже, чем большинство, не останавливаясь на достигнутом.  

Итак, приятного вам "копания". Да не испугает вас размер скроллбара - тут картинок больше, чем текста :).

Содержание


Для вашего удобства, вот список всех полезностей, рассмотренных в этом посте:

Наблюдение за экземплярами классов или Make Object ID


Когда вы работаете с несколькими экземплярами класса, как можно отличить один от другого? Если это неуправляемый код, можно посмотреть на адрес объекта. Если вы в управляемом коде, то можно попросить GetHashCode() у объекта, и надеятся, что его реализация не подкачает. В отладчике есть более простой способ: правый клик на объекте в окне Watch/Locals/Autos -> Make Object ID.

Context menu: Make Object Id

После этого объекту присваивается уникальная метка {1#}, {2#}, и т.д. Метка дается объекту на все время жизни, и именно так объект будет появляться во всех окошках, в том числе, в Immediate Window, и даже в тултипах.

1. Делаем Object ID:

Make object id

2. Объект получает идентификатор:

Got Id

3. Теперь всюду, где встречается "меченный" объект, студия даст нам знать об этом:

Id everywhere

Что еще более интересно, так это возможность использовать метку для ссылки на объект. Т.е. можно написать в окне Watch 1# и получить мгновенный доступ к содержимому меченного объекта. Также метку можно использовать в условных (conditional) точках останова. Например, условие this == 1# остановит выполнение программы только если это наш меченный объект.

Улучшаем читабельность в Watch window


По умолчанию, значение объекта в окне Watch - имя его типа. Хочешь больше - раскрывай дерево. Но ведь для списков мы видим число элементов в списке. Значит, должен быть способ повысить читабельность нашего класса Customer?

Default Watch Value

ToString() - подумаете вы, и будете практически правы. Можно перекрыть метод ToString() и выводить отладочную информацию, которая будет показана в Watch. Но ToString() не всегда является лучшим выбором. Например, WPF использует его для вывода элементов без определенного DataTemplate на UI. Более удобным методом будет использование атрибута DebuggerDisplay.

[System.Diagnostics.DebuggerDisplay("Customer: {Name}")]
public class Customer
{
  public string Name { get; set; }
  // ...
}        

Перекомпилируем приложение, и под отладчиком получаем:

I Can Read This

Когда определены и атрибут DebuggerDisplay и ToString(), отладчик использует атрибут. Однако будьте аккуратны, в некоторых случаях вы можете получить интересное насекомое, зарегистрированное под именем Гейзенбаг.


Поймать все исключения? - Легко!


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

try
{
  DoSomethingBuggy();
}
catch
{
  // Sorry. 
}
Все, что нужно для остановки, это зайти в меню Debug -> Exceptions... и выбрать  на чем именно вам интересно было бы остановиться:

Catch them all

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


Исключение, ты кто? - $exception.


В предыдущем примере, когда мы останавливались на всех исключениях, вы могли встретить код следующего типа:
try
{
  // Do something
} catch
{
  // Catch 
}
Как теперь быть, если вы хотите посмотреть на исключение в окне Watch? Экземпляр исключения не указан в блоке catch, что написать? Конечно, можно добавить переменную исключения, перекомпилировать код, и посмотреть на красавца. Но ни в коем случае не делайте так. Просто введите в окно Watch строку $exception:

No Name

PS: Какие еще псеводпеременные поддерживает отладчик? Для C# мне известна только $user, которая показывает текущего пользователя. Для неуправляемого кода их чуть-чуть больше.


Debugger.Break(), или Стоп машина!


Чтобы мгновенно провалиться в отладчик, даже когда программа запущена не в режиме отладке, и даже когда она запущена в Release версии, достаточно вызвать Debugger.Break().


Поток, тебя как зовут?


Когда создаете новый поток, не поленитесь дать ему имя (Thread.Name). Так будет легче отличать потоки друг от друга во время отладки: на скриншоте ниже Happy thread получил имя.

Thread Name


Позвольте к вам прикрепиться: Attach to Process.


Расставили все брякпоинты, прошли первые десять шагов багорепа, и вдруг вспомнили, что программа запущена не под отладчиком? Ничего страшного. Выбираете в меню Debug -> Attach to Proces или просто Alt + D, P.

Attach to process

После этого, выбираем процесс, который хотим отладить, и жмем Attach. Если все загруженные символы соответсвуют прикрепленному процессу, вы попадаете в мир полноценной отладки.


Отладка без отладки: Design Time Debugging.


Immediate Window (Ctrl + Alt + I, или Debug -> Windows -> Immediate) позволяет выполнить метод даже если программа не запущена. А если еще поставить точку останова в методе, то мы попадем в отладчик:

 Design Time Debugging

Для того, чтобы отладить функцию Sum() достаточно ввести в Immediate Window'e выражение ?Sum(x, y), и нажать Enter. Эта фича называется "Отладка в режиме дизайна", и работает во всех редакциях студии, кроме Express'a.


Хочу остановиться здесь, если только...


Не можете понять как эта переменная становится null'ом? Или как этот счетчик выходит за пределы? Установите условный breakpoint. Для этого нужно добавить точку останова и тыкунть правой кнопкой мыши по ней. В контекстном меню выбираем "Condition...":

Conditional Context

И указываем при каких условиях должна сработать точка останова:

Debug Conditional Set

В следующий раз, когда вы войдете в функцию, и x будет меньше сработает точка останова.

Совместно с метками объектов условные точки останова представляют отличное подспорье, когда вы пытаетесь отладить конкретный экземпляр коллекции. Если вы хотите, чтобы точка останова срабатывала только для данного элемента коллекции достаточно пометить элемент коллекции и использовать его метку в условии. Например условие 1# == this остановит выполнение только в случае если текущей объект обладает меткой 1#.


Вывод информации, без вывода информации: Trace Points.


Приходилось ли вам когда-нибудь писать следующий код для упрощения отладки?
private static int Sum(int x,  int y)  int y)
{
  Trace.WriteLine(string.Format("Enetring function Sum(). X = {0}; Y = {1}; Time = {2}", x, y, DateTime.Now));
  // ...
}
Да? - Поздравляю, вы делали ненужную работу. Я тоже так делал :). Ведь это очень полезно, особенно когда ищешь баги, связанные с фокусом ввода - переключение в отладчик влечет за собой потерю фокуса, и усложняет отладку. А так - написал и смотришь в Output Window, что там куда приходит. Но есть способ сделать это легче.

Устанавливаем breakpoint, кликаем по нему правой кнопкой, выбираем "When Hit..." и в появившемся окошке пишем, что бы мы хотели увидеть:

Trace points

Когда отладчик войдет в эту функцию, в окошке Output появится следующее сообщение:
Entering ConsoleApplication1.Program.Sum(int, int); X == 0; Y = 1; Time = {19.02.2010 8:30:34}
Как видите, можно выводить не только значения переменных, но и значения вызовов функций или выражений. Безусловный плюс - не нужно перекомпилировать приложение, если хотите изменить что-либо.

PS: Это не значит, что Trace.WriteLine() становится вовсе бесполезным. Он все так же бесценен для сопровождения приложений.


Когда очень плохо: Отладка .NET framework'a.


Я люблю копаться в framework'e. Эта страсть к археологии позволяет не только лучше понять работу Библиотеки но и понять почему вызов этого метода не привел к ожидаемым результатам. Для отладки  .NET Framework вам нужны символы, флажок и желание поискать на форумах, почему имея первые два пункта вы до сих пор не можете отлаживать исходники .NET'a.

В самом простом случае, достаточно установить флажок в настройках студии Enable .NET Framework source stepping (Tools -> Options -> Debugging -> General):

Enable .NET Source Code

Я также вЫключил флаг Requre source files to exactly match the original version. Это увеличивает шансы на успешный вход в исходники .NET, в случае если загруженные символы по какой-то прчине отличаются от загруженной библиотеки.

Далее соглашайтесь на все предложения студии. Запускайте программу в дебаге, и попробуйте войти внутрь вызова системного метода. Если в Output окне вы видите сообщение "Step into: Stepping over method without symbols '...'":

Step into failed

Значит нужно проверить, что символы загружены. Идем в Call Stack и смотрим на методы без символов (серые):

Load Symbols

Тыкаем правой кнопкой по любому из них и выбираем Load Symbols From -> Microsoft symbols server. После этого студия начинает скачивать символы:

Load symbols progress

Загрузить и проверить статус дополнительных символов можно так же из окна Modules (Debug -> Windows -> Modules). Итак, символы загружены, пробуем войти в функцию еще раз:

Hurray

Что самое приятное - можно прочитать комментарии разработчиков библиотеки, которые нельзя найти ни в одном Reflector'e и MSDN'e.

Напоследок хочу предупредить, что не для всех сборок .NET Framework Microsoft позволяет отлаживать исходники. Смотрите в Output Window.

Отладка запуска сервисов/приложений.


Иногда нужно отладить код запуска сервисов или приложений,  которые стартует другой процесс (например, installer). Нажать F5 для запуска сервиса вы не можете, а прикрепиться к процессу - уже слишком поздно. Как быть?

А быть надо с редактором реестра. Идем в раздел "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options" и добавляем новый подраздел, с названием процесса, который будем  отлаживать (e.g. explorer.exe). Затем, в этом разделе создаем строковый параметр с ключом debugger и значением - указывающм на отладчик:

HP Health Monitor

На картинке выше я решил отладить сервис HP Health Check (не потому, что у моей хапешки проблемы со здоровьем, тьфу-тьфу-тьфу, просто ради примера). Имя сервиса hphc_service.exe - именно так я назвал подраздел в Image File Execution Options. VSJitDebugger - это процесс запускающий отладчик Visual Studio. Теперь, если кто-то запустит процесс мы получим окошко предлагающее отладку:

Debug it

Кстати 1. Этот подход помогает не только отладить приложения, но и выследить какой засранец (извините) запускает исполняемые файлы вирусов...

Кстати 2. Если вы когда-нибудь устанавливали Process Explorer от SysInternals, и заменяли им стандартный диспетчер задач, то теперь вы знаете как он подменяет запуск taskmgr.exe :).


Отладка Silverlight приложений.


Мой основной браузер - Google Chrome, но разработку я веду под Firefox. Как же отлаживать Silverlight в таком случае? Один способ - это в настройках веб проекта указать каким браузером вы хотите запустить сайт, но я предпочитаю просто прикрепляться к процессу Firefox.

В четвертом Silverlight появилась удобная возможность отлаживать Out of Browser приложения. Достаточно указать в настройках Silverlight проекта какое именно приложение нужно отладить:

OOB Silverlight Debugging


Знайте свой инструмент, или блог Сары Форд.


Sara Ford - Program Manager CodePlex, автор книги MS Visual Studio Tips, очень жизнерадостная и веселая женщина. Она ведет потрясающий блог, огромная часть советов из которого и составила книгу. Если вы думаете, что отлично знаете Visual Studio - посетите ее блог, чтобы понять, насколько глубоко заблуждаетесь :).

Спасибо!


Друзья, спасибо вам, что прочитали или просто пролистали до этого места. Надеюсь, вы нашли что-нибудь новое для себя. Буду очень рад, если вы поделитесь своими открытиями, которые не вошли в пост. Напоследок, хотелось бы упомянуть слова мудреца: If debugging is the process of removing bugs, then programming must be the process of putting them in... Отличного настроения и программирования :)!

4 комментария:

  1. Супер! очень полезная инфа.
    Трассировка через брейкоинты, $exception, отладка в дизайн-тайм - незаменмые вещи.

    ОтветитьУдалить
  2. Отличная статья! Большое спасибо, давно не встречал такой высокой концентрации полезной информации в одной статье.

    Небольшой комментарий по поводу: "Эта фича называется "Отладка в режиме дизайна", и работает во всех редакциях студии, кроме Express'a." - в экспрессе эта функция тоже работает. Правда вызов ?Sum(x, y) у меня не выполнился, пришлось писать Program.Sum(x, y).

    ОтветитьУдалить
  3. Спасибо, Виталек, k0Zer, за добрые отзывы :). Рад, что оказалось полезным :)!

    ОтветитьУдалить
  4. в 64 процессе framework source debugging не работает.

    ОтветитьУдалить