
Роббинс Д. - Отладка приложений для Microsoft .NET и Microsoft Windows - 2004
.pdfГЛАВА 8 Улучшенные приемы для неуправляемого кода с использованием WinDBG |
341 |
|
|
текущую функцию, вы используете последовательность команд, приведенную ниже. Для перемещения контекста обратно к вершине стека дайте команду .frame 0:
.frame 1
dv
Команда DV возвращает достаточно информации, чтобы предоставить вам суть происходящего с локальными переменными. Следующий вывод получен в резуль тате исполнения команды DV при отладке программы PDB2MAP.EXE из главы 12.
cFuncFMT = CResString
cIM = CImageHlp_Module szBaseName = Array [260]
pMark = cccccccc dwBase = 0x400000 bEnumRet = 0xcccccccc
argc = 2
argv = 00344e18 fileOutput = 00000000
szOutputName = Array [260] iRetValue = 0
bRet = 1
hFile = 000007c8 cRS = CResString
Увидеть больше позволяет команда DT (Display Type — отобразить тип), кото рая может выполнять проход по связанным спискам и перемалывание массивов. К счастью, вы можете задать в DT параметр ?, чтобы быстро получить справку, находясь в центре боевых действий.
Еще DT может производить поиск типов символов. Вместо передачи ей имени или адреса переменной вы указываете параметр в формате module!type, где type — либо полное имя типа, либо содержит звездочку для поиска подвыражений. Так, увидеть типы, начинающиеся с «IMAGE» в PDB2MAP, позволяет dt pdb2map!IMAGE*. Указав тип полностью, вы увидите все поля этого типа, если это класс или струк тура, либо лежащий в основе базовый тип, если это typedef.
Последняя из команд вычисления — ?? (Evaluate C++ Expression — вычислить выражение C++) — служит для проверки арифметики указателей и управления другими потребностями в вычислениях C++. Внимательно прочтите документа цию по работе с выражениями, так как этот процесс не так прост, как кажется. Теперь, когда вы можете просмотреть и вычислить все свои переменные, самое время обратиться к исполнению, проходу по шагам и остановке программ.
Исполнение, проход по шагам и трассировка
Как вы, наверное, уже поняли, нажатие F5 продолжает исполнение после его пре рывания в WinDBG. Вы не могли этого заметить, но нажатие F5 просто выполня ет команду G (Go — дальше). Совершенно ясно, что в качестве параметра коман ды G вы можете указать адрес команды. WinDBG использует этот адрес как одно разовую точку прерывания, и, таким образом, вы можете запустить исполнение

342 ЧАСТЬ II Производительная отладка
до этого места. Замечали ли вы, что нажатие Shift+F11 (команда Step Out), выпол няет команду G, дополненную адресом (иногда в форме выражения)? Этот адрес есть адрес возврата на вершину стека. Вы можете проделать то же самое в окне Command, но вместо ручного вычисления адреса возврата можно использовать псевдорегистр $ra в качестве параметра, чтобы WinDBG сам вычислил адрес воз врата. Имеются и другие псевдорегистры, но не все из них применимы в пользо вательском режиме. Задайте «Pseudo Register Syntax» в справочной системе WinDBG, чтобы найти остальные псевдорегистры. Заметьте: эти псевдорегистры WinDBG характерны только для WinDBG и не используются в Visual Studio .NET.
Для управления трассировкой и движением по шагам служат команды T (Trace)
иP (Step) соответственно. Напомню, что трассировка будет проходить внутрь любой встреченной функции, тогда как прохождение по шагам — сквозь вызовы функ ций. Один аспект, отличающий WinDBG от Visual Studio .NET, состоит в том, что WinDBG не переключается автоматически между движением по шагам в тексте исходного кода и в командах ассемблерного кода только потому, что вы случай но переключаете фокус ввода между окнами Source (исходный код) и Disassembly (дизассемблированный код). По умолчанию WinDBG движется по шагам в стро ках исходного текста, если они загружены из места размещения исполняемого файла. Если вы хотите идти шагами по ассемблерным командам, то либо сними те флажок Source Mode (режим исходного текста) в меню Debug, либо дайте команду
.LINES (Toggle Source Line Support — переключить поддержку исходного кода) с параметром –d.
Как и G, команды T и P делают то же самое, что и нажатие кнопок F11 (или F8)
иF10 в окне Command. Вы можете также указать или адрес, до которого должна выполняться трассировка, или движение по шагам, или количество шагов, кото рое необходимо выполнить. Это пригодится, так как иногда это проще, чем уста навливать точку прерывания. В сущности это команда «run to cursor» (исполняй до курсора), выполняемая вручную.
Две относительно новые команды для движения по шагам и трассировки: TC (Trace to Next Call — трассировать до следующего вызова) и PC (Step to Next Call — шаг до следующего вызова). Разница между ними в том, что они выполняют движе ние по шагам или трассировку, пока не попадется следующий оператор CALL. При выполнении PC, если указатель команд находится на команде CALL, исполнение будет продолжаться, пока не произойдет возврат из подпрограммы. TC сделает шаг внутрь подпрограммы и остановится на следующей команде CALL. Я нахожу TC и PC по лезными, когда хочу пропустить часть функции, но не выходить из нее.
Трассировка данных и наблюдение за ними
Одна из самых больших проблем при выявлении проблем производительности программ (быстродействия) в том, что почти невозможно прочесть и точно уви деть, что происходит на самом деле. Так, код Standard Template Library (стандарт ной библиотеки шаблонов, STL) создает одну из самых больших проблем быст родействия при отладке приложений других программистов. В результате в код впихивается столько inline функций (а код STL вообще почти невозможно читать), что анализ путем чтения просто нереален. Но, так как STL негласно выделяет для себя столько памяти и производит всякие блокировки там и сям, жизненно важ

ГЛАВА 8 Улучшенные приемы для неуправляемого кода с использованием WinDBG |
343 |
|
|
но иметь способ увидеть, что на самом деле вытворяют функции, использующие STL. К счастью, WinDBG имеет ответ на эту головоломку — и в этом главное отли чие WinDBG от Visual Studio .NET — команду WT (Trace and Watch Data — трасси ровать данные и наблюдать за ними).
WT показывает в иерархическом виде вызовы всех функций в вызове одной функции. В конце трассировки WT показывает точно, какие функции вызывались и сколько раз вызывалась каждая. Кроме того (и это важно при решении проблем быстродействия), WT показывает, сколько было сделано переходов в режим ядра. Для повышения быстродействия главное исключить побольше переходов в режим ядра, поэтому то, что WT — один из немногих способов увидеть такую информа цию, делает ее ценной вдвойне.
Как вы догадываетесь, вся эта трассировка может генерировать в окне Command тонны хлама, которые, возможно, вы захотите сохранить в виде файла. К счастью, программисты WinDBG удовлетворили требования полного сохранения всей си стемы регистрации. Открыть файл регистрации очень просто — укажите имя файла регистрации как параметр команды .LOGOPEN (Open Log File — открыть файл ре гистрации). Вы также можете добавлять к существующему файл регистрации ко мандой .LOGAPPEND (Append Log File — добавить файл регистрации). При заверше нии отладки вызовите .LOGCLOSE (Close Log File — закрыть файл регистрации).
Эффективное использование WT для получения поддающегося интерпретации вывода без всего лишнего, через что придется продираться, требует планирова ния. WT трассирует, пока не попадется адрес возврата из текущей подпрограммы. А значит, вам нужно тщательно позиционировать указатель команд за один два шага до применения WT. Первое место — непосредственный вызов функции, ко торую вы хотите исполнить. Это нужно делать на уровне ассемблерного кода, поэтому вам понадобится установить точку прерывания прямо на команде вызо ва подпрограммы или установить движение по шагам на уровне ассемблерного кода и дойти поэтапно до команды вызова. Второе место — на первой команде функции. Вы можете шагать до команды PUSH EBP или установить точку прерыва ния на открывающей фигурной скобке функции в окне Source (исходный код).
Прежде чем перейти к параметрам WT, я хочу обсудить ее вывод. Для простоты я написал WTExample — маленькую программу с несколькими функциями, вызы вающими самих себя (вы найдете ее среди примеров на CD). Я устанавливаю точку прерывания на первую команду в wmain и даю WT для получения результатов под Windows XP SP1, как показано в листинге 8 1 (заметьте: я сократил некоторые пробелы и перенес некоторые строки, чтобы листинг поместился на странице).
Листинг 8-1. Вывод команды wt WinDBG
0:000> wt
Tracing WTExample!wmain to return address 0040139c
30 [ 0] WTExample!wmain
30 [ 1] WTExample!Foo
30 [ 2] WTExample!Bar
3 |
0 |
[ 3] |
WTExample!Baz |
|
3 |
0 |
[ |
4] |
WTExample!Do |
3 |
0 |
[ |
5] |
WTExample!Re |
см. след. стр.

344 ЧАСТЬ II Производительная отладка
3 |
0 |
[ 6] |
WTExample!Mi |
3 |
0 |
[ 7] |
WTExample!Fa |
3 |
0 |
[ 8] |
WTExample!So |
3 |
0 |
[ 9] |
WTExample!La |
3 |
0 |
[10] |
WTExample!Ti |
6 |
0 |
[11] |
WTExample!Do2 |
3 |
0 |
[12] |
kernel32!Sleep |
3 |
0 |
[13] |
kernel32!SleepEx |
18 |
0 |
[14] |
kernel32!_SEH_prolog |
15 |
18 |
[13] |
kernel32!SleepEx |
16 |
0 |
[14] |
ntdll! |
|
|
|
RtlActivateActivationContextUnsafeFast |
20 |
34 |
[13] |
kernel32!SleepEx |
15 |
0 |
[14] |
kernel32!BaseFormatTimeOut |
26 |
49 |
[13] |
kernel32!SleepEx |
3 |
0 |
[14] |
ntdll!ZwDelayExecution |
2 |
0 |
[15] |
SharedUserData! |
|
|
|
SystemCallStub |
1 |
0 |
[14] |
ntdll!ZwDelayExecution |
31 |
55 |
[13] |
kernel32!SleepEx |
3 |
0 |
[14] |
kernel32!SleepEx |
14 |
0 |
[15] |
ntdll! |
|
|
|
RtlDeactivateActivationContextUnsafeFast |
4 |
14 |
[14] |
kernel32!SleepEx |
36 |
73 |
[13] |
kernel32!SleepEx |
9 |
0 |
[14] |
kernel32!_SEH_epilog |
37 |
82 |
[13] |
kernel32!SleepEx |
4 |
119 |
[12] |
kernel32!Sleep |
8 |
123 |
[11] |
WTExample!Do2 |
2 |
0 |
[12] |
WTExample!_RTC_CheckEsp |
11 |
125 |
[11] |
WTExample!Do2 |
2 |
0 |
[12] |
WTExample!_RTC_CheckEsp |
13 |
127 |
[11] |
WTExample!Do2 |
5 |
140 |
[10] |
WTExample!Ti |
2 |
0 |
[11] |
WTExample!_RTC_CheckEsp |
7 |
142 |
[10] |
WTExample!Ti |
5 |
149 |
[ 9] |
WTExample!La |
2 |
0 |
[10] |
WTExample!_RTC_CheckEsp |
7 |
151 |
[ 9] |
WTExample!La |
5 |
158 |
[ 8] |
WTExample!So |
2 |
0 [ 9] |
WTExample!_RTC_CheckEsp |
|
7 |
160 |
[ 8] |
WTExample!So |
5 |
167 |
[ 7] |
WTExample!Fa |
2 |
0 |
[ 8] |
WTExample!_RTC_CheckEsp |
7 |
169 |
[ 7] |
WTExample!Fa |
5 |
176 |
[ 6] |
WTExample!Mi |
2 |
0 |
[ 7] |
WTExample!_RTC_CheckEsp |
7 |
178 |
[ 6] |
WTExample!Mi |
5 |
185 |
[ 5] |
WTExample!Re |
|
|
|
|

ГЛАВА 8 |
Улучшенные приемы для неуправляемого кода с использованием WinDBG |
345 |
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
0 |
[ 6] |
WTExample!_RTC_CheckEsp |
|
7 |
187 |
[ 5] |
WTExample!Re |
|
5 |
194 |
[ 4] |
WTExample!Do |
|
2 |
0 |
[ 5] |
WTExample!_RTC_CheckEsp |
|
7 |
196 |
[ 4] |
WTExample!Do |
|
5 |
203 |
[ 3] |
WTExample!Baz |
|
20 [ 4] WTExample!_RTC_CheckEsp
7 |
205 |
[ |
3] |
WTExample!Baz |
5 |
212 |
[ |
2] |
WTExample!Bar |
20 [ 3] WTExample!_RTC_CheckEsp
7 |
214 [ 2] |
WTExample!Bar |
5221 [ 1] WTExample!Foo
20 [ 2] WTExample!_RTC_CheckEsp
7 223 [ 1] WTExample!Foo
6230 [ 0] WTExample!wmain
2 |
0 [ 1] |
WTExample!_RTC_CheckEsp |
8232 [ 0] WTExample!wmain
240 instructions were executed in 239 events (0 from other threads)
Function Name |
Invocations MinInst MaxInst AvgInst |
|||||
SharedUserData!SystemCallStub |
|
1 |
2 |
2 |
2 |
|
WTExample!Bar |
|
1 |
7 |
7 |
7 |
|
WTExample!Baz |
|
1 |
7 |
7 |
7 |
|
WTExample!Do |
|
1 |
7 |
7 |
7 |
|
WTExample!Do2 |
|
1 |
13 |
13 |
13 |
|
WTExample!Fa |
|
1 |
7 |
7 |
7 |
|
WTExample!Foo |
|
1 |
7 |
7 |
7 |
|
WTExample!La |
|
1 |
7 |
7 |
7 |
|
WTExample!Mi |
|
1 |
7 |
7 |
7 |
|
WTExample!Re |
|
1 |
7 |
7 |
7 |
|
WTExample!So |
|
1 |
7 |
7 |
7 |
|
WTExample!Ti |
|
1 |
7 |
7 |
7 |
|
WTExample!_RTC_CheckEsp |
|
13 |
2 |
2 |
2 |
|
WTExample!wmain |
|
1 |
8 |
8 |
8 |
|
kernel32!BaseFormatTimeOut |
|
1 |
15 |
15 |
15 |
|
kernel32!Sleep |
|
1 |
4 |
4 |
4 |
|
kernel32!SleepEx |
|
2 |
4 |
37 |
20 |
|
kernel32!_SEH_epilog |
|
1 |
9 |
9 |
9 |
|
kernel32!_SEH_prolog |
|
1 |
18 |
18 |
18 |
|
ntdll! |
|
|
|
|
|
|
RtlActivateActivationContextUnsafeFast |
1 |
16 |
16 |
16 |
||
ntdll! |
|
|
|
|
|
|
RtlDeactivateActivationContextUnsafeFast |
1 |
14 |
14 |
14 |
||
ntdll!ZwDelayExecution |
|
2 |
1 |
3 |
2 |
|
1 system call was executed |
|
|
|
|
|
|
Calls |
System Call |
|
|
|
|
|
1 |
ntdll!ZwDelayExecution |
|
|
|
|
|

346 ЧАСТЬ II Производительная отладка
Начальная часть вывода (отображение иерархического дерева) — это инфор мация о вызовах. Перед каждым вызовом WinDBG отображает три числа: первое — количество ассемблерных команд, исполняемых функцией до вызова следующей функции, второе не документировано, но похоже на полное число исполненных ассемблерных команд при трассировке до возврата, последнее число в скобках — это текущий уровень вложенности иерархического дерева.
Вторая часть вывода — отображение итогов — немного менее понятна. В до полнение к подведению итогов вызовов каждой функции она отображает счет чик вызовов каждой функции, а также минимальное количество ассемблерных команд, вызываемых при выполнении функции, максимальное количество команд, вызываемых при выполнении функции, и среднее количество вызванных команд. Последние строки итогов показывают количество системных вызовов. Вы може те увидеть, что WTExample иногда вызывает Sleep для обращения к режиму ядра. Сам факт, что вы располагаете количеством обращений к ядру, потрясающе крут.
Как вы можете себе представить, WT может дать огромный вывод и замедлить ваше приложение, так как каждая строка вывода требует парочки межпроцессных передач информации между отладчиком и отлаживаемой программой. Если вы хотите увидеть крайне важную итоговую информацию, то параметр –nc команды WT подавит вывод иерархии. Конечно, если вы интересуетесь только иерархией, укажите параметр –ns. Чтобы увидеть содержимое регистра возвращаемого зна чения (EAX в языке ассемблера x86), задайте –or, а чтобы увидеть адрес, исходный файл и номер строки (если это доступно) для каждого вызова — –oa. Последний параметр — –l — позволяет установить максимальную глубину вложенности ото бражаемых вызовов. Параметр –l полезен, если вы хотите увидеть только глав ные моменты того, что исполняется, или сохранить в выводе только функции вашей программы.
Я настоятельно советую вам посмотреть ключевые циклы и операции в своих программах с помощью WT, чтобы точно знать, что происходит за кулисами. Не знаю, сколько проблем производительности, неправильного использования язы ков и технологий я выследил с ее помощью!
Общий вопрос отладки
Некоторые имена в моих программах на C++ огромны. Как использовать WinDBG, чтобы не заработать туннельного синдрома?
К счастью, WinDBG теперь поддерживает текстовые псевдонимы (aliases). Определить пользовательский псевдоним и эквивалент расширения позво ляет команда AS (Set Alias — установить псевдоним). Например, команда as LL kernel32!LoadLibraryW назначит строку «LL» для расширения ее до kernel 32!LoadLibraryW везде, где вы ее вводите в командной строке. Увидеть назна ченные вами псевдонимы позволяет команда AL (List Aliases — список псев донимов), а удалить — AD (Delete Alias — удалить псевдоним).
Есть еще одно место, указанное в документации, где вы можете опреде лить псевдонимы с фиксированными достаточно странными именами, — это команда R (Registers — регистры). Псевдонимы с фиксированными име нами — $u0, $u1, …, $u9. Чтобы определить псевдоним с фиксированным

ГЛАВА 8 Улучшенные приемы для неуправляемого кода с использованием WinDBG |
347 |
|
|
именем, надо ввести точку перед u: r $.u0=kernel32!LoadLibraryA. Увидеть, какие псевдонимы назначены фиксированным именам, позволяет лишь команда
.ECHO (Echo Comment — вывести комментарий): .echo $u0.
Точки прерывания
WinDBG предлагает те же виды точек прерывания, что и Visual Studio .NET, плюс несколько уникальных. Важно, что WinDBG дает гораздо больше возможностей в момент срабатывания точек прерывания и позволяет увидеть, что происходит после этого. Прежние версии WinDBG имели хорошее диалоговое окно, где очень про сто было устанавливать точки прерывания. Увы, это диалоговое окно отсутствует в переписанной версии WinDBG, которой мы располагаем сейчас, поэтому при установке точки прерывания мы все должны делать вручную.
Общие точки прерывания
Первое, за что я хочу взяться в точках прерывания, — это две команды, устанав ливающие точки прерывания: BP и BU. Обе имеют одинаковые параметры и моди фикаторы. Можно считать, что версия команды BP — это строгая точка прерыва ния, всегда ассоциируемая в WinDBG с адресом. Если модуль, содержащий такую точку, выгружен, WinDBG исключает точку BP из списка точек прерывания. С дру гой стороны, точки прерывания BU ассоциированы с символом, поэтому WinDBG отслеживает символ, а не адрес. Если символ перемещается, точка BU также пере мещается. А значит, точка BU будет активна, но заблокирована, если модуль выг ружается из процесса, но будет немедленно реактивирована, как только модуль вернется в процесс, даже если ОС переместит модуль. Основная разница между точками прерывания BP и BU в том, что WinDBG сохраняет точки BU в рабочих пространствах WinDBG, а BP — нет. Наконец, при установке точки прерывания в окне Source путем нажатия F9 WinDBG устанавливает точку BP. Я рекомендую ис пользовать точки прерывания BU вместо BP.
Имеется ограниченное диалоговое окно Breakpoints (точки прерывания) — щелкните меню Edit, затем Breakpoints, — но я управляю точками прерывания из окна Commands, так как, по моему, это проще. Команда BL (Breakpoint List — спи сок точек прерывания) позволяет увидеть все активные сейчас точки прерывания. Вы можете прочитать документацию к выводу команды BL, но я хочу заметить, что первое поле — это номер точки прерывания WinDBG, а второе — буква, обозна чающая статус точки прерывания: d (disabled — запрещена), e (enabled — разре шена) и u (unresolved — неразрешима). Вы можете разрешить или заблокировать точки прерывания командами BE (Breakpoint Enable — разрешить точку прерыва ния) и BD (Breakpoint Disable — заблокировать точку прерывания). Указание звез дочки (*) в любой из этих команд будет разрешать/блокировать все точки пре рывания. Наконец, вы можете разрешить/заблокировать конкретные точки пре рывания, указанием номера точки прерывания в командах BE и BD.
Синтаксис команды для установки точки прерывания пользовательского ре жима x86 таков:
[~Thread] bu[ID] [Address [Passes]] ["CommandString"]

348 ЧАСТЬ II Производительная отладка
Если вы просто вводите BU, WinDBG устанавливает точку прерывания на месте текущего указателя команд. Модификатор потока (~Thread) — это просто номер потока WinDBG, который делает установки точки прерывания в конкретном по токе тривиальной. Если вы пожелаете указать номер точки прерывания WinDBG, укажите его сразу после BU. Если точка прерывания с таким номером уже суще ствует, то WinDBG заменит имеющуюся точку прерывания новой, которую вы устанавливаете сейчас. Поле адреса (Address) может содержать любое допустимое адресное выражение, которые я описывал в начале раздела «Ситуации при отлад ке». В поле проходов (Passes) вы указываете, сколько раз вы хотели бы пропустить эту точку останова до того, как произойдет останов. Сравнение в этом поле дей ствует по правилу «больше или равно», максимальное значение — 4 294 967 295. Так же как при отладке неуправляемого кода в Visual Studio .NET, поле Passes уменьшается только в случае исполнения программы «на полной скорости», а не при проходе по шагам или трассировке.
Последнее поле, которое можно использовать при установке точки прерыва ния, — это чудесная командная строка (CommadString). Это правда, что вы може те ассоциировать команды с точкой прерывания! Наверное, лучший способ про демонстрировать эту удивительную штуку — рассказать, как я применил эту тех нологию для устранения почти неразрешимой ошибки. Одна ошибка проявлялась только после нескольких длинных последовательностей данных, прошедших че рез определенный участок кода. Как обычно, это занимало уйму времени, чтобы выполнились все условия, а я не мог просто тратить день или неделю на просмотр состояний переменных при каждой остановке программы на точке прерывания (увы, я работал не на условиях почасовой платы!). Мне нужен был способ регис трировать все значения переменных, чтобы изучить поток данных в системе. Так как можно объединять массу команд с помощью точки с запятой, я постепенно построил огромную команду, которая выводила все переменные путем вызова DT и ??. Я также разбросал несколько команд .ECHO, чтобы видеть, где я был, и иметь общую строку, которая появлялась бы каждый раз, когда срабатывала точка пре рывания. Командную строку я завершил командой «;G», чтобы исполнение про граммы продолжалось после точки прерывания после полного дампа значений переменных. Я, конечно же, включил регистрацию и просто запустил процесс на исполнение, пока он не завершился аварийно. Просмотрев весь файл регистра ции, я сразу увидел образчик данных и быстренько исправил ошибку. Не будь в WinDBG такой прекрасной возможности расширения точек прерывания, я бы никогда не нашел эту ошибку.
Команда J (Execute If — Else — выполнить если — то) особенно хороша для применения в командной строке точки прерывания. Она позволяет исполнять команды по условию, основанному на частном выражении. Иначе говоря, J пре доставляет возможность использовать условные точки прерывания в WinDBG. Формат команды:
j expression 'if true command' ; 'if false command'
Выражение (expression) — это любое выражение, с которым может справить ся вычислитель WinDBG. Текст в одиночных кавычках — это командные строки для истинного (true) и ложного (false) значения выражения. Всегда заключайте командные строки в одиночные кавычки, так как вы получаете возможность вклю
ГЛАВА 8 Улучшенные приемы для неуправляемого кода с использованием WinDBG |
349 |
|
|
чать в командные строки точки с запятой для построения больших выражений. И, конечно же, вы можете включать подкоманды J в командные строки для true и false. Из документации не совсем ясно, что делать, если нужно оставить одно из условий (true или false) пустым, т. е. если вы не хотите исполнять никакие коман ды для этого условия, просто введите два символа одиночной кавычки рядом для пропущенного условия.
Точки прерывания по обращению к памяти
Помимо блестящих точек прерывания исполнения, WinDBG имеет феноменаль ную команду BA (Break On Access — прервать в случае обращения), позволяющую остановиться, если фрагмент памяти считывается или записывается вашим про цессом. Visual Studio .NET предлагает только точки прерывания по изменению состояния памяти, а вам нужно пользоваться классом аппаратных точек преры вания Майка Мореарти (Mike Morearty) для доступа ко всей мощи, предлагаемой аппаратными точками прерывания Intel x86. Однако WinDBG сам располагает всей этой мощью.
Формат точек прерывания по обращению к памяти для пользовательского режима Intel x86 таков:
[~Thread] ba[ID] Access Size [Address [Passes]] ["CommandString"]
Как видите, BA предлагает гораздо больше возможностей, чем просто останов ка при обращении к памяти. Так же, как и в случае команд BP и BU, вы можете при казать останавливаться, если только указанный поток «прикасается» к памяти, ус тановить счетчик проходов и ассоциировать эту удивительную командную стро ку с определенным типом обращения. Поле типа обращения (Access) — это оди ночный символ указывающий, хотите ли вы остановиться в случае чтения (r), записи (w) или исполнения (e). Поле размера (Size) указывает, сколько байт вы со бираетесь поставить под надзор. Так как BA использует Intel Debug Registers (ре гистры отладки Intel) для реализации своего волшебства, вы ограничены только возможностью надзора за 1, 2 или 4 байтами в каждый момент, вы также ограни чены четырьмя точками прерывания BA. Как и при установке точек прерывания по данным в Visual Studio .NET, надо помнить о проблемах выравнивания памяти, поэтому, если вы хотите надзирать за 4 байтами памяти, адрес этой памяти дол жен заканчиваться на 0, 4, 8 или C. Поле адреса (Address) — это адрес памяти, обращение к которой должно вызвать прерывание. Хотя WinDBG менее требова телен к переменным, я все же предпочитаю использовать реальные шестнадцате ричные адреса, чтобы быть уверенным, что точка прерывания установлена имен но там, где я хочу.
Чтобы увидеть BA в действии, можете воспользоваться программой MemTouch из числа файлов примеров к этой книге. Программа просто размещает локаль ный фрагмент памяти szMem, передаваемый одной из функций, которая «прикаса ется» к памяти, а другая функция просто читает эту память. Установите точку пре рывания на адрес szMem, чтобы указать место прерывания. Чтобы получить адрес локальной переменной, дайте команду DV. Получив этот адрес, вы можете указать это значение в BA. Чтобы узнать, что делать с командами, возможно, стоит вызвать «kp;g», в результате чего вы увидите время доступа, а затем продолжите испол нение.

350 ЧАСТЬ II Производительная отладка
Исключения и события
WinDBG предлагает продвинутые средства управления исключениями и событи ями. Исключения — это все аппаратные исключительные ситуации, такие как нарушение доступа, приводящее к аварийному завершению программы. События — это все стандартные события, передаваемые отладчикам средствами Microsoft Win32 Debugging API. Это значит, например, что вы можете установить прерывание WinDBG, когда загрузился модуль, и, таким образом, получить управление еще до того, как будет исполнена точка входа модуля.
Для управления исключениями и событиями из окна Command служат коман ды SXE, SXI, SXN (Set Exceptions — установить исключения), но они сильно сбивают с толку. К счастью, в WinDBG есть диалоговое окно Event Filters (фильтры собы тий) (рис. 8 5), доступное из меню Debug.
Рис. 8 5. Диалоговое окно Event Filters
Но даже оно все еще чуточку путает при попытке понять, что происходит при исключении, так как WinDBG использует странноватую терминологию в коман дах SX* и диалоговом окне Event Filters. Групповое поле Execution (исполнение) в нижнем правом углу указывает, как WinDBG будет управлять исключением (табл. 8 2). Поскольку поле Exceptions указывает, что вы хотите передавать функ ции API ContinueDebugEvent, напомню, что мы обсуждали ее в главе 4.
Табл. 8-2. Состояния прерываний по исключению
Состояние |
Описание |
Enabled (разрешено) |
При возникновении исключения (исключительной ситуации) |
|
оно исполняется и происходит прерывание в отладчик. |
Disabled (блокирована) |
При первом возникновении исключения отладчик игнори |
|
рует его, при повторном исполнение останавливается, |
|
и осуществляется выход в отладчик. |
Output (вывод сообщения) |
При возникновении исключения прерывание в отладчик не |
|
производится. Однако выводится информационное сообще |
|
ние об этом исключении. |