Чтобы помочь читателям в решении подобных проблем, предлагаю несколько рекомендаций, на которые не имеющие большого опыта в области отладки администраторы смогут на первых порах опереться. Я проиллюстрирую эти советы на примере приложения, поддержкой которого мне приходится заниматься, Device Manager. Не стану утомлять читателей подробным описанием всего процесса отладки на уровне сборки той или иной проблемы; вместо этого я расскажу о некоторых базовых приемах отладки.

Совет 1. Откройте процесс в окне отладчика

Когда система не получает никаких сведений о проблеме, можно определить, как протекает процесс, с помощью отладчика (windbg.exe). Более подробные сведения о том, как приступить к работе с отладчиком, можно найти в статье «Диагностика неисправностей: рекомендации администраторам», опубликованной в «Windows IT Pro/RE» № 8 за 2009 год. Перед запуском процесса в отладчике нужно будет открыть командную строку, чтобы ввести в ней имя программы windbg и запустить этот процесс. Открыть командную строку можно с помощью программы Process Explorer (technet.microsoft.com/en-us/sysinternals/bb896653.aspx); чтобы получить доступ к командной строке, дважды щелкните на процессе, и вы увидите командную строку, отображенную на вкладке Image.

Открыв окно отладчика Windows из группы Debugging Tools for Windows меню Start, можно запустить диспетчер Device Manager, выбрав в меню File элемент Open Executable. Введите командную строку, которая обычно используется для инициализации процесса.

Совет 2. Получите максимум данных до начала процесса отладки

Перед тем как браться за отладчик, соберите базовые сведения относительно кода, который намереваетесь изучать. Поиски ответа на вопрос, с чего начинать отладку, часто начинаются вне программного отладчика. Необходимо каким-то образом выявить имена функций, имеющих отношение к рассматриваемой проблеме. Если, к примеру, ваше приложение сообщает об ошибке, поясняя, что не удалось открыть некий раздел реестра, требуется выяснить, какая именно функция ответственна за открытие этих разделов. А как можно определить функции, применяемые для решения различных задач? Иногда ответ содержится в названии функции, но можно воспользоваться MSDN, сетью для разработчиков на платформе Windows, чтобы выяснить, какие вызовы происходят. К примеру, быстрый поиск по ключевым словам registry functions позволит вам обнаружить документы MSDN с перечислением этих функций по адресу msdn.microsoft.com/en-us/library/ms724875(VS.85).aspx. И там вы увидите, что для открытия разделов реестра используется функция RegOpenKeyEx.

Для получения информации о соответствующих функциях можно воспользоваться бесплатно распространяемым средством Dependency Walker (depends.exe), доступным для загрузки по адресу www.dependencywalker.com. Программа Dependency Walker показывает, какие библиотеки DLL используются двоичным файлом, а также имена функций, применяемых файлом из DLL. Получить эти сведения просто: запустите программу depends.exe, затем откройте исследуемый двоичный файл с помощью команды open из меню File. После этого Dependency Walker отобразит имена функций, вызываемых данным приложением при его выполнении. Эта информация будет иметь большое значение при проведении операции отладки, поскольку она позволяет выявить интересные вызовы, которые, возможно, имеют отношение к рассматриваемой проблеме. Так, если ваше приложение выдает сообщение о том, что попытка сетевого соединения не удалась, нужно просмотреть выходные данные Dependency Walker и найти там имена функций, которые, по-видимому, имеют отношение к установлению сетевых соединений. Затем вы сможете с помощью отладчика расследовать соответствующие вызовы.

В качестве примера давайте воспользуемся Dependency Walker для открытия файла devmgr.dll. Этот бинарный файл содержит код, который программа mmc.exe задействует для создания оснастки Device Manager. Как видно на экране 1, Dependency Walker показывает, что файл devmgr.dll импортирует различные функции, связанные с перечислением устройств, из файла setupapi.dll. Если вас интересует вопрос, как я определил, что файл devmgr.dll представляет собой библиотеку DLL, применяемую для создания диспетчера Device Manager, поясняю, что файл devmgmt.msc фактически является XML-файлом, упоминающим файл devmgr.dll в тексте. Чтобы его открыть, можно воспользоваться редактором Notepad.

Экран 1. Просмотр в программе Dependency Walker функций, связанных с файлом devmgr.dll

Совет 3. Установите точки прерывания

Когда вы запустите процесс в отладчике, тот остановит выполнение кода в первой точке прерывания при инициализации процесса. Но, как правило, это не лучшее место для начала отладки. Выполнение программы обычно состоит из множества команд ассемблера и вызовов функций. Однако лишь небольшое их число может иметь отношение к рассматриваемой проблеме. Вы должны сделать так, чтобы отладчик позволял программе выполняться до появления тех функций, которые вы определили как имеющие отношение к проблеме (с помощью depends.exe). Чтобы выполнить это условие, нужно установить точки прерывания.

Вы можете установить точку прерывания против функции с помощью команды bp (set breakpoint). Далее можно воспользоваться командой g (go), чтобы возобновить выполнение потоков процесса до тех пор, пока другие обстоятельства не заставят отладчик вновь приостановить процесс. Ниже приводятся соответствующие команды и выходные данные:

0: 000> bp setupapi! CM_Get_Device_ID_
List_ExW
0: 000> g
Breakpoint 0 hit

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

В предыдущем фрагменте кода отладчик проинформировал нас о том, что мы дошли до точки прерывания «ноль». Составить список этих точек прерывания можно с помощью команды bl (breakpoint list). У нас имеется лишь одна точка прерывания, и она имеет номер ноль.

0: 000> bl
0 e 770 edf2 d
0001 (0001)
0:****
setupapi! CM_Get_
Device_ID_List_
ExW

Итак, каким же образом искать имена функций, против которых целесообразно поставить точки прерывания? Команда x (examine symbols) может использовать информацию о символах для получения функций и других данных, соответствующих шаблону. В примере, приведенном на экране 2, перечисляются все символьные данные, соответствующие шаблону *Devices* из модуля devmgr. Затем вы сможете установить точки прерывания против любой из этих функций.

Экран 2. Выполнение команды отладчика x

Если файл devmgr.dll еще не загружен в процесс, эта команда вызовет ошибку. В таких случаях необходимо предписать отладчику прекратить работу при загрузке заданного модуля. Следующая команда вызовет остановку отладчика при загрузке модуля setupapi.dll:

0: 000> sxe ld: setupapi
0: 000> g
ModLoad: 770 e0000 771 e8000 c:\
windows\system32\setupapi.dll

Совет 4. Определите поток вызовов

При достижении точки прерывания вы сможете выяснить, какая команда вызвала данную функцию и какую процедуру упомянутая функция вызывает (т. е. определить поток вызовов), проанализировав стек с помощью команды kC (отобразить обратную трассировку стека). В нашем примере я выполнил команду kC по достижении точки прерывания, которую я выставил на setupapi! PNP_GetDeviceList. Приращение стеков происходит снизу вверх. Это значит, что в начале списка отображается функция, которая вызывалась последней. В результате выполнения команды kC будет отображен стек, образовавшийся при достижении точки прерывания, которая была установлена на setupapi! PNP_GetDeviceList. Модуль devmgr.dll вызвал файл setupapi.dll для формирования списка устройств.

Для определения вызовов, осуществленных функцией, а также для регистрации ее выполнения можно использовать одну из самых мощных команд, реализованных в отладчике Windows, команду wt (watch trace). Данную команду можно выполнять с момента начала вызова функции; тогда на экране будут отображены все вызовы, выполненные этой функцией. В примере, показанном на экране 3, я использовал параметр -l2 для ограничения глубины выходных данных двумя уровнями. В этом примере функция setupapi! PNP_Get-DeviceList вызвала функцию setupapi! NdrClientCall2, которая, в свою очередь, вызвала функцию rpcrt4! NdrClientCall2.

Экран 3. Данные, возвращенные командой wt

Совет 5. Определите, была ли после вызова функции возвращена ошибка

Допустим, точка прерывания, установленная вами для некоей функции, достигнута. Как определить, возвратили ли эти функции сообщение об ошибке? Нужно запустить команду gu (go up), чтобы вернуться из функции, а затем применить команду r, чтобы исследовать возвращенное значение.

Команда gu возобновляет выполнение до возвращения результата текущей функцией. В данном случае команда gu запускает функцию PNP_GetDeviceList, а затем останавливает исполнение непосредственно по завершении выполнения функции. Команда r (register) возвращает содержимое регистров. Переменная $retreg представляет регистр возврата, который можно использовать для определения того, закончилось ли выполнение функции успешно или она вернула сообщение об ошибке. Мы получили сообщение об ошибке с кодом 0x1d от функции PNP_Get-DeviceList(). Я обнаружил, что возвращенное значение для функции PNP_GetDeviceList было задокументировано в файле, размещенном по адресу msdn.microsoft.com/en-us/library/cc239018(PROT.10).aspx: An error occurred during an attempt to read the registry.

Заключительные шаги

Проблема диспетчера устройств была решена с использованием команды p (step), выполняющей трассировку процесса выполнения функции. Трассировка в ходе отладки показала, что функция setupapi! PNP_GetDeviceList осуществила удаленный вызов процедуры, направленный на интерфейс 8d9f4e40-a03d-11ce-8f69-08003e30051b. С помощью монитора процессов я обнаружил, что на этот удаленный вызов процедуры ответила функция umpnpmgr.dll! PNP_GetDeviceList (), которая выполнялась в процессе services.exe. Этот вызов завершился с ошибкой NAME_NOT_FOUND вследствие повреждения реестра. Я перезагрузил систему, используя конфигурацию Last Known Good registry configuration. Проблема была решена!

Райан Мангипано — инженер по технической поддержке подразделения Microsoft Global Escalation Services. Специализируется на диагностике ядра Windows и новых методах отладки. Дополнительная информация по отладке Windows — по адресу blogs.msdn.com/ntdebugging