Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Роббинс Д. - Отладка приложений для Microsoft .NET и Microsoft Windows - 2004

.pdf
Скачиваний:
350
Добавлен:
13.08.2013
Размер:
3.3 Mб
Скачать

ГЛАВА 8 Улучшенные приемы для неуправляемого кода с использованием WinDBG

351

 

 

Табл. 8-2. Состояния прерываний по исключению (продолжение)

 

 

 

 

Состояние

Описание

 

Ignore (игнорируется)

При возникновении исключения отладчик его игнорирует.

 

Сообщение не отображается.

 

 

 

 

Вы можете игнорировать группу элементов управления Continue в нижнем правом углу. Она важна, только если вы хотите производить различную обработ! ку точки прерывания, одиночного шага и недопустимых исключений. Если вы добавляете к списку собственную структурную обработку исключений, сохрани! те параметры группы Continue по умолчанию, Not Handled — без обработки. В результате каждый раз при возникновении исключения WinDBG будет коррек! тно передавать его прямо отлаживаемой программе. Вы же не хотите, чтобы от! ладчик съедал исключения кроме тех, которые он сам вызывает, таких как точка прерывания и одиночный шаг.

После выбора собственно исключения самой важной кнопкой в этом диало! говом окне является Commands (команды). Только имя может подсказать вам, что она делает. Щелчок этой кнопки выводит окно Filter Command (команда фильт! ра) (рис. 8!6). Первое поле ввода названо неправильно — оно должно называться First!Chance Exception.

Рис. 8 6. Диалоговое окно Filter Command

В окне Filter Command можно вводить команды WinDBG, исполняемые при возникновении в отлаживаемой программе конкретного исключения. Когда мы в разделе «Контроль исключений» главы 7 обсуждали диалоговое окно Exception в Visual Studio .NET, я показал, как устанавливать исключения C++, чтобы остано! виться на первом исключении, чтобы было можно контролировать, где ваши про! граммы вызывают throw, а после нажатия F10 — и catch. Проблема в том, что Visual Studio .NET останавливается всякий раз, когда вырабатывается исключительная ситуация C++, а вам приходится сидеть и каждый раз на этом месте нажимать F5, в то время как ваше приложение обрабатывает множество команд throw.

Что хорошо в WinDBG и в возможности ассоциировать команды с исключе! ниями, так это то, что вы можете применять эти команды для регистрации всей важной информации и эффективно продолжать исполнение без вашего вмеша! тельства в ход исполнения. Чтобы настроить обработку исключений C++, выбе! рите C++ EH Exception из списка исключений в диалоговом окне Event Filter и щелкните кнопку Commands. В диалоговом окне Filter Command (команда филь! тра) введите в поле ввода Command kp;g, чтобы WinDBG зарегистрировал состо! яние стека и продолжил выполнение. Теперь у вас будет состояние стека вызовов при каждом выполненном throw, а WinDBG продолжит корректное исполнение. И все же, чтобы увидеть последнее событие или исключение, происшедшее в процессе, дайте команду .LASTEVENT (Display Last Event — отобрази последнее событие).

352 ЧАСТЬ II Производительная отладка

Управление WinDBG

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

Простейшая, но чрезвычайно полезная команда — .CLS (Clear Screen — очис! тить экран) — позволяет очистить окно Command, чтобы начать вывод сначала. Так как WinDBG способен изрыгать огромные объемы информации, требующей место для хранения, время от времени полезно очищать «рабочее поле».

Если ваше приложение работает со строками Unicode, вам захочется настро! ить отображение указателей USHORT как строк Unicode. Команда .ENABLE_UNICODE (Enable Unicode Display — разрешить отображение Unicode), введенная с параметром 1, настроит все так, чтобы команда DT корректно отображала ваши строки. Если нужно настроить национальные параметры для корректного отображения строк формата Unicode, то команда .LOCALE (Set Locale — установить местный диалект) в качестве параметра принимает локализующий идентификатор. Если приходится работать с битами и вы хотите видеть значения битов, команда .FORMATS (Show Number Formats

— отобразить форматы чисел) покажет значения передаваемых параметров всех числовых форматов, в том числе двоичных.

А вот команда .SHELL (Command Shell) позволяет запустить программу MS!DOS из отладчика и перенаправить ее вывод в окно Command. Отлаживая на той же машине, на которой выполняется отлаживаемая программа, конечно же, проще переключиться с помощью Alt+Tab, но красота .SHELL в том, что при выполнении удаленной отладки, программа MS!DOS выполняется на удаленной машине. .SHELL можно использовать также для запуска единственной внешней программы, с пе! ренаправлением ее вывода в окно Command. После выдачи .SHELL окно Command в строке ввода будет отображать INPUT> для обозначения, что программа MS!DOS ожидает ввода. Для завершения программы MS!DOS и возврата к окну Command, используйте либо команду MS!DOS exit, либо, что предпочтительнее, .SHELL_QUIT (Quit Command Prompt), так как она прекратит исполнение программы MS!DOS, даже если она заморожена.

Последнюю мета!команду, о которой я упомяну, я искал в отладчике много лет, но обнаружил лишь теперь. При написании обработчиков ошибок вы обычно знаете, что к моменту обработки ошибок в вашем процессе возникли серьезные неприятности. Вам также известно в 9 случаях из 10, что если происходит обра! ботка какой!то ошибки, то, вероятно, вам нужно посмотреть значения каких!то переменных или состояние стека вызовов, а кроме того, вы захотите записать конкретную информацию. Я всегда хотел иметь способ закодировать то, что нужно выполнить прямо в моем процессе обработки ошибки. Сделав это, команды бу! дут исполняться, позволяя программистам службы сопровождения и мне отлажи! вать быстрее. Моя идея была такова: так как вызовы OutputDebugString проходят через отладчик, можно было бы встроить команды в OutputDebugString. Вы могли бы сказать отладчику, что искать в начале текста OutputDebugString, а что все остальное после этого будут команды, подлежащие исполнению.

Именно так работает команда WinDBG .OCOMMAND (Expect Commands from Tar! get — ожидать команды от отлаживаемой программы). Вы вызываете .OCOMMAND, ука! зывая искомый префикс строки в начале вызовов OutputDebugString. Если этот пре!

ГЛАВА 8 Улучшенные приемы для неуправляемого кода с использованием WinDBG

353

 

 

фикс присутствует, WinDBG исполнит остальную часть текста как командную стро! ку. Очевидно, что вам нужно быть осторожным при использовании строк, а то WinDBG сойдет с ума, пытаясь исполнить вызовы OutputDebugString во всей вашей программе. В качестве такой строки мне нравится WINDBGCMD: — я разбрасываю командные строки WinDBG во всех своих программах.

При использовании.OCOMMAND необходимо в конце каждой команды добавлять «;g», иначе WinDBG остановится по завершении команды. В следующей функции все команды завершаются «;g», чтобы выполнение продолжалось. Чтобы они на! чали работать, я выдаю команду .ocommand WINDBGCMD: при запуске программы:

void Baz ( int )

{

//Чтобы это воспринималось как команды WinDBG, выполните команду

//".ocommand WINDBGCMD:" внутри WinDBG.

OutputDebugString ( _T ( "WINDBGCMD: .echo \"Hello from WinDBG\";g" )); OutputDebugString ( _T ( "WINDBGCMD: kp;g" ) ) ;

OutputDebugString ( _T ("WINDBGCMD: .echo \"Stack walk is done\";g")) ;

}

Магические расширения

Теперь вы знаете достаточно команд (представляющих лишь малую толику воз! можных), чтобы у вас голова пошла кругом, и вы, возможно, удивляетесь, почему я трачу так много времени на обсуждение WinDBG. WinDBG труднее в использо! вании, чем Visual Studio .NET, и кривая обучения не только крута — она почти вер! тикальна! Вы увидели, что WinDBG предлагает классные возможности по точкам прерывания, но вы все еще, возможно, удивляетесь, почему игра стоит свеч.

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

Я хочу сосредоточиться на наиболее важных. Найдите время на чтение доку! ментации об остальных расширениях. В разделе Reference\Debugger Extension Commands документации к WinDBG имеются два ключевых раздела: General Exten! sions (общие расширения) и User!Mode Extensions (расширения пользовательского режима).

Физически расширения являются файлами DLL, экспортирующими особые имена функций для своей работы. В каталоге Debugging Tools For Windows есть несколько каталогов, таких как W2KFRE (Windows 2000 Free Build — свободная поставка для Windows 2000) и WINXP. Эти каталоги содержат команды расшире! ния для разных ОС. Как писать свои расширения, вы можете прочитать в файле README.TXT, прилагаемом к примеру EXTS в каталоге <Debugging Tools for Windows Dir>\SDK\SAMPLES\EXTS.

Загрузка расширений и управление ими

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

354 ЧАСТЬ II Производительная отладка

получить справку из расширения. Загруженные расширения покажет команда .CHAIN (List Debugger Extensions — список расширений отладчика). Она выведет и поря! док поиска команд сверху вниз на дисплее, и то, как WinDBG ищет библиотеки DLL расширений. Под Windows 2000 отображение для четырех библиотек расши! рений пользовательского режима (DBGHELP.DLL, EXT.DLL, UEXT.DLL и NTSDEXTS.DLL) выглядит так (зависит от расположения каталога Debugging Tools for Windows):

0:000> .chain

Extension DLL search Path:

G:\windbg\winext;G:\windbg\pri;G:\windbg\WINXP;G:\windbg;

Extension DLL chain:

dbghelp: image 6.1.0017.1, API 5.2.6, built Sat Dec 14 15:32:30 2002 [path: G:\windbg\dbghelp.dll]

ext: image 6.1.0017.0, API 1.0.0, built Fri Dec 13 01:46:07 2002 [path: G:\windbg\winext\ext.dll]

exts: image 6.1.0017.0, API 1.0.0, built Fri Dec 13 01:46:07 2002 [path: G:\windbg\WINXP\exts.dll]

uext: image 6.1.0017.0, API 1.0.0, built Fri Dec 13 01:46:08 2002 [path: G:\windbg\winext\uext.dll]

ntsdexts: image 5.2.3692.0, API 1.0.0, built Tue Nov 12 14:16:20 2002 [path: G:\windbg\WINXP\ntsdexts.dll]

Загрузка расширений проста — укажите имя библиотеки DLL (без расширения

.DLL) как параметр команды .LOAD (Load Extension DLL — загрузить DLL расшире! ния). Для выгрузки укажите имя библиотеки DLL в качестве параметра команде

.UNLOAD (Unload Extension DLL — выгрузить DLL расширения).

Принято, что все команды расширения вводятся в нижнем регистре и в отли! чие от обычных и мета!команд они чувствительны к регистру. Кроме того, команды в библиотеке расширения называются так же: например, команда help предназ! начена для того, чтобы быстро информировать, что имеется в этой библиотеке DLL расширения. При загруженных расширениях по умолчанию ввод команды !help не показывает всю доступную справку. Чтобы вызвать команду расширения кон! кретной библиотеки DLL расширения, добавьте имя DLL и точку для команды расширения: !dllname.command. Следовательно, чтобы увидеть справку о NTSD! EXTS.DLL, нужно ввести !ntsdexts.help.

Важные команды расширения

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

Первая важная команда — !analyze –v — позволяет быстро проанализировать текущее исключение. Я специально показал эту команду с параметром –v, потому что без него вы не увидите большую часть информации. Команда !analyze не раз! решает все ваши ошибки — ее идея заключается в том, что она предоставляет вам ту информацию, которую вы обычно хотите видеть во время аварийного завер! шения, такую как запись исключения и стек вызовов.

ГЛАВА 8 Улучшенные приемы для неуправляемого кода с использованием WinDBG

355

 

 

Так как критические секции являются облегченными объектами синхрониза! ции, многие программисты пользуются ими. WinDBG предлагает две команды расширения для заглядывания внутрь критической секции, чтобы узнать состоя! ние блокировок объектов и какие потоки владеют ими. Если у вас есть адрес кри! тической секции, можно применить команду !critsec, передавая ей как параметр адрес секции. А увидеть все заблокированные критические секции позволяет !locks. Все критические секции процесса она покажет с параметром –v. В Windows XP/ Server 2003 дополнительный параметр –o покажет сиротские критические секции.

Если вы программируете защищенные приложения Win32, очень трудно по! нять, какая текущая информация безопасности применена к текущему потоку. Команда !token (Windows XP/Server 2003) или !threadtoken (Windows 2000) пока! жет состояние заимствования прав текущего потока и остальную информацию бе! зопасности, такую как идентификация пользователя и групп, плюс отобразит в тек! стовом виде все привилегии, ассоциированные с потоком.

Есть одна команда, которая сохранила мне бессчетное количество часов от! ладки, — !handle. Как можно понять из названия, она делает что!то с описателями в процессе. Если просто ввести !handle, вы увидите значения описателей, тип объек! та, содержащегося в описателе, и секцию, подводящую итоги, сколько объектов каждого типа имеется в процессе. Некоторые из этих типов могут показаться вам бессмысленными, если вы не программировали драйверы или не читали книгу Дэвида Соломона и Марка Руссиновича «Inside Microsoft Windows 2000»1. Табл. 8!3 предлагает переводы (трансляцию) с языка команды !handle на язык терминов пользовательского режима некоторых типов.

Табл. 8-3. Трансляция (перевод) типов описателей

Термин команды

 

!handle

Термин пользовательского режима

Desktop

Win32 desktop (рабочий стол Win32)

Directory

Win32 object manager namespace directory (каталог рабочего про!

 

странства менеджера объектов Win32)

Event

Win32 event synchronization object (объект синхронизации события

 

Win32)

File

Disk file, communication endpoint, or device driver interface (диско!

 

вый файл, конечная точка коммуникационной связи или интер!

 

фейс драйвера устройства)

IoCompletionPort

Win32 IO completion port (порт завершения ввода/вывода Win32)

Job

Win32 job object (объект задания Win32)

Key

Registry key (раздел реестра)

KeyedEvent

Non!user!creatable events used to avoid critical section out of memory

 

conditions (созданные не пользователем события, используемые

 

предотвращения выхода критической секции за пределы памяти)

Mutant

Win32 mutex synchronization object (объект синхронизации мью!

 

текс Win32)

 

см. след. стр.

1Соломон Д., Руссинович М. Внутреннее устройство Microsoft Windows 2000. — М.: «Русская Редакция», 2001. — Прим. перев.

356

ЧАСТЬ II

Производительная отладка

 

 

Табл. 8-3.

Трансляция (перевод) типов описателей (продолжение)

 

 

Термин команды

 

!handle

 

Термин пользовательского режима

Port

 

Interprocess communication endpoint (конечная точка межпроцесс!

 

 

ного взаимодействия)

Process

 

Win32 process (процесс Win32)

Thread

 

Win32 thread (поток Win32)

Token

 

Win32 security context (контекст защиты Win32)

Section

 

Memory!mapped file or page!file backed memory region (отображаемый

 

 

на память файл или страничный файл выгрузки региона памяти)

Semaphore

Win32 semaphore synchronization object (объект синхронизации се!

 

 

мафор Win32)

SymbolicLink

NTFS symbolic link (символьная связь NTFS)

Timer

 

Win32 timer object (объект таймер Win32)

WaitablePort

Interprocess communication endpoint (конечная точка межпроцесс!

 

 

ного взаимодействия)

WindowStation

Top level of window security object (объект защиты окна верхнего

 

 

уровня)

 

 

 

Даже просмотр описателей замечателен, но, указав параметр ? в !handle, вы увидите, что команда способна на большее. Чтобы появилось больше информа! ции об описателе, можно задать в первом параметре значение описателя, а во втором — битовое поле, указывающее, что вы хотите узнать об этом описателе. В качестве второго параметра вы всегда должны задавать F, так как в результате вам будет показано все. Например, я отлаживаю программу WDBG из главы 4, описа! тель 0x1CC является событием. Вот как получить детальную информацию об этом

описателе:

 

0:006> !handle

1cc f

Handle 1cc

 

Type

Event

Attributes

0

GrantedAccess

0x1f0003:

Delete,ReadControl,WriteDac,WriteOwner,Synch

QueryState,ModifyState

HandleCount

3

PointerCount

6

Name

\BaseNamedObjects\WDBG_Happy_Synch_Event_614

Object Specific Information

Event Type

Manual Reset

Event is Waiting

Вы видите не только предоставленные права, но также имя и, что важнее, что событие находится в состоянии ожидания (т. е. в занятом состоянии). Так как !handle покажет эту информацию для всех типов, теперь вы легко увидите взаимные бло! кировки, поскольку вы можете проверить состояния всех событий, семафоров и мьютексов, чтобы понять, кто из них блокирован, а кто нет.

ГЛАВА 8 Улучшенные приемы для неуправляемого кода с использованием WinDBG

357

 

 

Вы можете посмотреть подробную информацию для всех описателей процес! са, передавая два параметра 0 и F. Если вы работаете над большим процессом, вывод может занять кучу времени на перемалывание всех деталей. Чтобы узнать о кон! кретном классе описателей, укажите два первых параметра 0 и F, а третий — имя класса. Например, чтобы увидеть все события, введите !handle 0 f Event.

Выше я касался применения !handle для просмотра состояний событий, чтобы сделать вывод, почему ваше приложение взаимоблокируется. Другое замечатель! ное использование !handle — оценка потенциальной утечки ресурсов. Так как !handle показывает общее количество всех текущих описателей процесса, вы можете легко сравнить результаты !handle до и после. Если вы видите, что общее количество описателей изменилось, вы точно скажете, утечка какого типа описателей проис! ходит. Так как отображается детальная информация, такая как разделы реестра и имя описателя, вы легко видите, утечка какого из описателей происходит.

Я выследил массу утечек ресурсов и взаимных блокировок с помощью !handle — это единственный способ получить информацию об описателях в процессе от! ладки, так что стоит потратить немного времени на ознакомление с ней и выво! димыми с ее помощью данными.

Общий вопрос отладки

Функции Win32 API, такие как CreateEvent, создающие описатели, имеют необязательный параметр «имя». Должен ли я назначать имена моим описателям?

Абсолютно, безусловно, ДА! Команда !handle может показать состояния каж! дого из ваших описателей. Но это лишь малая часть того, что необходимо для поиска проблем. Если описатели не именованы, очень трудно сопоста! вить сами описатели с происходящим в отладчике, скажем, при взаимных блокировках. Не дав имена своим описателям, вы делаете свою жизнь за! метно сложнее, чем она должна быть.

Однако вы можете просто пойти и начать давать сногсшибательные имена

вэтом необязательном поле. Когда вы создаете событие, например, имя, даваемое этому событию, такое как «MyFooEventName», глобально для всех процессов, выполняемых на машине. Хотя можно подумать, что второй процесс, вызывающий CreateEvent, дает ему уникальное имя внутренними средствами, на самом деле CreateEvent вызывает OpenEvent и возвращает вам описатель глобально именованного события. Теперь допустим, что у вас два исполняющихся процесса и в каждом из них есть поток, ожидающий со! бытия MyFooEventName. Когда один из процессов сигнализирует о событии, этот сигнал будут видеть оба процесса и начнут исполняться. Очевидно, что если вы подразумеваете, что сигнал воспринимается только одним процес! сом, то вы просто создаете сверхтрудную для отлова ошибку.

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

вWDBG в главе 4: я добавлял идентификатор процесса или потока к имени для обеспечения уникальности.

358 ЧАСТЬ II Производительная отладка

Другие интересные команды расширения

Прежде, чем перейти к управлению файлами вывода, хочу отметить несколько команд расширения, которые вы найдете интересными в критических ситуаци! ях, например, когда нужно найти какую!то действительно вызывающую ошибку. Первая — !imgreloc — просто просматривает все загруженные модули и сообща! ет, были ли все модули загружены в предпочитаемые вами адреса. Теперь у вас нет оправдания за то, что вы не проверили. Вывод команды выглядит так (ОС пере! местила второй модуль TP4UIRES):

0:003> !imgreloc

 

 

 

00400000

tp4serv

— at preferred address

00c50000

tp4uires

RELOCATED from 00400000

5ad70000 uxtheme

— at preferred address

6b800000

S3appdll

at preferred address

76360000

WINSTA —

at preferred address

76f50000

wtsapi32

at preferred address

77c00000

VERSION

— at preferred address

77c10000

msvcrt —

at preferred address

77c70000

GDI32 —

at

preferred address

77cc0000 RPCRT4 —

at preferred address

77d40000

USER32 —

at preferred address

77dd0000 ADVAPI32

at preferred address

77e60000

kernel32

at preferred address

77f50000

ntdll —

at

preferred address

Если вы так ленивы, что не можете вызвать командную строку и вывести команду NET SEND для посылки сообщения другим пользователям, вы можете просто ввести !net_send. На самом деле это полезно, если вам нужно привлечь чье!то внимание в процессе удаленной отладки. Ввод просто !net_send покажет вам необходимые для посылки сообщения параметры.

Поскольку вы располагаете командой !dreg для вывода информации о регист! рах, вы также располагаете командой !evlog для отображения журнала событий. Если каждую из них просто ввести в командной строке, вы получите подсказку, как их использовать. Обе — прекрасные помощники для просмотра регистров или журналов событий. Если вы используете их, особенно при удаленной отладке, сюр! призов не ждите.

Если у вас проблемы с обработкой исключений, команда !exchain поможет просмотреть цепочки обработки исключений текущего потока и увидеть, какие функции имеют зарегистрированные обработчики исключений. Вот образец выво! да команды при отладке программы ASSERTTEST.EXE.

0012ffb0: AssertTest!except_handler3+0 (004027a0)

CRT scope 0, filter: AssertTest!wWinMainCRTStartup+22c (00401e1c) func: AssertTest!wWinMainCRTStartup+24d (00401e3d)

0012ffe0: KERNEL32!_except_handler3+0 (77ed136c)

CRT scope 0, filter: KERNEL32!BaseProcessStart+40 (77ea847f) func: KERNEL32!BaseProcessStart+51 (77ea8490)

ГЛАВА 8 Улучшенные приемы для неуправляемого кода с использованием WinDBG

359

 

 

Работу с кучами ОС (т. е. кучами, создаваемыми вызовами функции API Create Heap) облегчит команда !heap. Вы можете думать, что вы не используете никакие кучи ОС, но код ОС, исполняющийся внутри вашего процесса, к ним обращается. Вы можете испортить память в одной из этих куч (см. главу 17), а !heap покажет ее.

Наконец, я хочу коснуться очень интересной и недокументированной коман! ды !for_each_frame из расширения EXT.DLL. Как можно понять из ее имени2 , она исполняет командную строку, переданную в качестве параметра команды, для каждого кадра (фрейма) стека. Прекрасный вариант использования этой коман! ды — !for_each_frame dv, в результате чего будут выведены локальные переменные каждого кадра стека.

Работа с файлами дампа

Понимая, какие типы команд может исполнять WinDBG, вы можете перейти к последнему набору команд — командам файлов дампа. Как я уже упоминал в раз! деле «Основы», сильная сторона WinDBG — управление файлами дампа. Прелесть WinDBG и файлов дампа заключается в том, что почти все информационные коман! ды работают и с файлами дампа, причем почти так же, как и тогда, когда возни! кали проблемы.

Создание файлов дампа

Выполняя «живую» отладку, вы можете вызвать команду .DUMP (Create Dump File — создать файл вывода), чтобы создать файл дампа. Замечу, что при создании фай! ла дампа нужно указывать расширение в имени файла. .DUMP производит запись именно в тот файл, какой вы ей указали (полное имя файла и путь к нему) без добавления отсутствующего расширения. Вы всегда должны использовать расши! рение .DMP.

Оставив проблему расширения в стороне, я хочу обсудить некоторые общие возможности, предлагаемые .DUMP до того, как перейти к типам файлов дампа. Первый ключ — /u — добавляет дату, время и PID (идентификатор процесса) к имени файла, чтобы обеспечить уникальные имена файлов дампа без необходи! мости бороться с их именами. Так как файлы дампа являются столь замечатель! ным средством выполнения снимков сеанса отладки, позволяющим анализиро! вать поведение программы позже, /u заметно упрощает вашу жизнь. Чтобы обес! печить лучшее понимание, что происходило в конкретное время, ключ /c позво! ляет ввести комментарий, который будет отображаться, когда вы загрузите файл вывода. Наконец, если вы отлаживаете несколько процессов сразу, ключ /a запи! шет файлы дампа для всех загруженных процессов. Убедитесь, что вы используе! те /u совместно с /a, чтобы дать каждому процессу свое имя.

WinDBG может создавать два типа файлов дампа: полный и краткий. Полный включает все о процессе, от стеков текущих потоков до состояния всей памяти (даже все загруженные процессом двоичные данные). Он указывается с помощью ключа /f. Иметь полный файл дампа удобно, так как в нем содержится всего зна! чительно больше, чем вам необходимо, однако он съедает огромный объем дис! ковой памяти.

2 For each frame — для каждого кадра. — Прим. перев.

360 ЧАСТЬ II Производительная отладка

Для создания файла минидампа достаточно указать ключ по умолчанию /m, если вы не указываете никаких ключей в .DUMP. Записанный таким образом файл ми! нидампа будет таким же, как и файл минидампа по умолчанию, создаваемый Visual Studio .NET, и будет содержать версии загруженных модулей, сведения о стеке для выполнения вызовов стека для всех активных потоков.

Вы также можете указать WinDBG добавить дополнительную информацию к минидампу, задавая флаги в ключе /m. Самый полезный — h (/mh) — в дополнение к информации по умолчанию для минидампа запишет информацию об активных описателях. Это значит, что вы сможете, используя команду !handle, просмотреть состояния всех описателей, созданных при записи дампа. Если понадобится ана! лизировать проблемы с указателями, можно указать i (/mi), чтобы WinDBG вклю! чил в файл вывода вторичную память. Этот ключ просматривает указатели на стек или страничную память и выводит состояние памяти, на которую ссылается ука! затель, в виде небольшого участка памяти около этого места. Таким образом, вы можете узнать, на что ссылаются указатели. Имеется множество других ключей краткого вывода, которые вы можете указать для записи дополнительной инфор! мации, но h и i я использую всегда.

Последний ключ, позволяющий сэкономить много дискового пространства, — /b — сожмет файл дампа в файл .CAB. Это замечательный ключ, но пропущенное расширение в файле дампа делает его использование проблематичным. Так как

.DUMP не добавляет автоматически расширение, то вам инстинктивно захочется добавить расширение .CAB к файлу дампа. Однако при указании расширения .CAB WinDBG создает временный .DMP!файл с именем <name>.CAB.DMP внутри реаль! ного .CAB!файла. К счастью, WinDBG прекрасно прочтет такой файл из .CAB!файла.

Несмотря на все эти мелкие проблемы с возможностью записи .CAB!файлов, мне все же очень нравится использовать ее. В дополнение к сохранению только

.DMP!файлов в .CAB!файлах можно указать ключ /ba, если вы хотите также сохра! нить и таблицу символов в .CAB!файле! Чтобы гарантированно сохранить все символы процесса, запустите команду ld * (load all symbols — загрузить все сим! волы) перед созданием файла дампа. Таким образом, вы можете быть уверены, что вы располагаете всеми корректными символами, когда переносите .CAB!файл на машину, которая может не иметь доступа к вашему хранилищу символов. Используя /b, помните, что WinDBG записывает файл дампа и создает соответствующий .CAB! файл в каталоге %TEMP% машины. Вы, конечно, понимаете, что, имея большой про! цесс, создавая полный дамп с помощью /f и задавая /ba для создания .CAB!файла с символами, вам понадобится огромный шмат свободного пространства на дис! ке в каталоге %TEMP%.

Открытие файлов дампа

Файлы дампа не принесут большой пользы, если вы не умеете открывать их. Про! ще всего сделать это из нового экземпляра WinDBG. В меню File выберите Open Crash Dump (открыть файл вывода аварийного завершения) или нажмите Ctrl+D для вызова диалогового окна Open Crash Dump, а затем найдите каталог, в кото! ром находится файл дампа. Интересно, хотя это и не описано в документации, что WinDBG также откроет .CAB!файл, содержащий .DMP!файл. После того как файл

Соседние файлы в предмете Программирование на C++