
Роббинс Д. - Отладка приложений для Microsoft .NET и Microsoft Windows - 2004
.pdf
ГЛАВА 19 Утилита Smooth Working Set |
683 |
|
|
Если счетчик вызовов для этой записи не равен 0.
{
Если размер этой записи > размер наилучшей записи по числу вызовов.
{
Обозначить эту запись как наилучшее соответствие по числу вызовов.
}
}
}
}
}
Если наилучшее соответствие по числу вызовов не найдено.
{
Считать наилучшей записью по числу вызовов наилучшую запись в целом.
}
Вывести имя функции, наилучшей по числу вызовов в PRF6файл. Присвоить значению оставшегося объема страницы размер всей
страницы.
}
}
Закрыть все временные файлы
}
Что после SWS?
Как я уже говорил, SWS обеспечивает довольно хорошую возможность оптимиза ции программ. Если вам хочется сделать ее еще лучше, вот несколько полезных советов.
Напишите программу начала и прекращения сбора данных. В функции _penter я выполняю проверку того, находится ли событие в сигнальном состоянии. Вы можете написать отдельную программу, которая будет генерировать событие, управляющее сбором данных утилитой SWS. Просто создайте событие с име нем SWS_Start_Stop_Event и генерируйте его, когда хотите прекратить накопле ние данных.
Реализуйте упомянутые мной возможности исключения символов, чтобы их число в ваших SWS файлах было минимальным.
Если вы по настоящему честолюбивы, создайте для просмотра данных и оп тимизации программу с графическим интерфейсом. Работать с ней будет го раздо удобней, чем с утилитой, основанной на командной строке.

684 ЧАСТЬ IV Мощные средства и методы отладки неуправляемого кода
Резюме
Чрезмерный объем кода не имеет никаких оправданий, поэтому в качестве за ключительной оптимизации приложения всегда следует выполнять упорядочение и максимальное уплотнение двоичных файлов. SWS позволяет посадить файлы на диету и уменьшить рабочий набор относительно безболезненно, особенно если использовать ее вместе с программой SettingsMaster, автоматизирующей управление конфигурацией проектов.

Ч А С Т Ь V
ПРИЛОЖЕНИЯ

П Р И Л О Ж Е Н И Е
A
Чтение журналов Dr. Watson
Я надеюсь, что для облегчения отладки приложений вы будете включать в них возможность создания минидампов (см. главу 13) и вам не придется изучать жур налы Dr. Watson. Однако, если у вас есть готовое приложение или ваши клиенты не могут высылать вам двоичные минидампы по электронной почте, Dr. Watson всегда сможет указать вам на время и место возникновения проблемы.
Пожалуй, доктора Ватсона следовало бы назвать доктором Джекилом и мисте ром Хайдом. В режиме доктора Джекила вы получаете информацию об ошибке на машине пользователя, легко находите место проблемы и быстро ее исправля ете. В режиме мистера Хайда вы получаете лишь еще один ни о чем не говоря щий набор чисел. В этом приложении я объясню работу с журналами Dr. Watson, что позволит вам реже встречаться с мистером Хайдом и чаще с доктором Дже килом.
На следующих страницах я рассмотрю полный журнал Dr. Watson, объясняя по ходу дела всю важную информацию (релевантные данные в конкретном раз деле выделены полужирным начертанием). Этот журнал был создан в результате одной из ошибок ранней версии WDBG.EXE — отладчика, написанного мной для главы 4.
После знакомства с этой книгой ничто в журнале Dr. Watson не должно быть для вас незнакомым. Что до различий между журналами Dr. Watson в Microsoft Windows 2000, Windows XP и Windows Server 2003, то их немного. Однако, как вы увидите ниже, версии Dr. Watson в двух последних ОС несколько лучше.
Для получения журналов запустите Dr. Watson (DRWTSN32.EXE). В списке Appli cation Errors (ошибки приложения) вы увидите недавние ошибки. Если этот спи сок пуст, возможно, Dr. Watson не задан в качестве отладчика по умолчанию. Что бы сделать Dr. Watson отладчиком по умолчанию, запустите его с ключом –i, т. е. введите выражение drwtsn32 –i. Для генерирования тестовой ошибки запустите


688 ЧАСТЬ V Приложения
Журналы Dr. Watson
Первый раздел журнала Dr. Watson имеет вид:
Исключение в приложении: Прил.: (pid=1796)
Время: 1/2/2003 @ 13:42:56.208
Номер: c0000005 (нарушение прав доступа)
Заголовок содержит информацию о причине ошибки: в моем примере это исклю чение в приложении. В случае некоторых ошибок номера исключений не всегда преобразуются в понятную людям форму, такую как «нарушение прав доступа» для исключения 0xC0000005. Все номера исключений вы можете узнать, отыскав в файле WINNT.H строки STATUS_. Коды ошибок указаны в документации как значе ния EXCEPTION_, возвращаемые функцией GetExceptionCode, однако реальные значе ния определяются в директивах #define STATUS_. После преобразования кода ошибки в значение EXCEPTION_ вы сможете просмотреть ее описание в документации к
GetExceptionCode.
Раздел System Information (сведения о системе) в объяснении не нуждается:
*——> Сведения о системе <——* Имя компьютера: HUME Имя пользователя: john
Число процессоров: 2
Тип процессора: x86 Family 15 Model 0 Stepping 10
Версия Windows 2000: 5.0
Текущая сборка: 2195
Пакет обновления: 3
Текущий тип: Multiprocessor Free
Зарегистрированная организация: Wintellect Зарегистрированный пользователь: John Robbins
Раздел Task List (список задач) выглядит так:
*——> |
Список задач <——* |
0 |
Idle.exe |
8 |
System.exe |
132 |
smss.exe |
160 |
csrss.exe |
156 |
winlogon.exe |
208 |
services.exe |
220 |
lsass.exe |
364 |
svchost.exe |
424 |
svchost.exe |
472 |
spoolsv.exe |
504 |
MWMDMSVC.exe |
528 |
MWSSW32.exe |
576 |
regsvc.exe |
592 |
MSTask.exe |
836 |
Explorer.exe |
904 |
tp4mon.exe |
912 |
tphkmgr.exe |
ПРИЛОЖЕНИE A Чтение журналов Dr. Watson |
689 |
|
|
920 |
4nt.exe |
940 |
taskmgr.exe |
956 |
tponscr.exe |
268 |
msdev.exe |
252 |
WDBG.exe |
828 |
NOTEPAD.exe |
416 |
drwtsn32.exe |
0_Total.exe
Вразделе Task List выводится список процессов, выполнявшихся в момент ошиб ки. К сожалению, в нем нет информации об их версиях, поэтому вам придется узнать у пользователя версии файлов всех процессов. В левом столбце указаны десятичные идентификаторы процессов (PID), выполнявшихся в момент ошиб ки. После ошибки они совершенно бесполезны.
Вразделе Module List (список модулей) указываются все модули, загруженные в момент ошибки в адресное пространство. Информация обо всех модулях име ет формат (адрес загрузки – максимальный адрес). В этом месте впервые появля ются различия между журналами Dr. Watson в Windows 2000, Windows XP и Windows Server 2003. В журналах Windows 2000 вы увидите только диапазоны адресов мо дулей и больше ничего. Если ваше приложение откомпилировано в Microsoft Visual Studio 6 и для него доступны символы, то после диапазона адресов будут указаны загруженные символы. Так как DBGHELP.DLL, поставляемая с Windows 2000, ни чего не знает о символах Microsoft Visual Studio .NET, вы никогда не увидите загру женные символы для двоичных файлов, откомпилированных в этой среде. Если в Windows 2000 список модулей имеет некоторые недостатки, то Dr. Watson в Win dows XP/Server 2003 достаточно умен, чтобы указывать после каждого диапазона адресов имя соответствующей DLL.
Список модулей в Windows 2000 (00400000 6 00460000) (77F80000 6 77FFB000) (63000000 6 6301B000) (77E10000 6 77E6F000) (77E80000 6 77F31000)
Список модулей в Windows XP
(0000000000400000 |
6 |
0000000000460000: d:\Dev\BookTwo\Disk\Output\WDBG.exe |
(0000000071c20000 |
6 |
0000000071c6e000: E:\WINDOWS\System32\NETAPI32.dll |
(0000000075a70000 |
6 |
0000000075b15000: E:\WINDOWS\system32\USERENV.dll |
(0000000075f40000 |
6 |
0000000075f5f000: E:\WINDOWS\system32\appHelp.dll |
(00000000763b0000 |
6 |
00000000763f5000: E:\WINDOWS\system32\comdlg32.dll |
Если вы желаете узнать, какие модули были загружены в Windows 2000, вам остается только гадать. Однако, как я несколько раз говорил, чрезвычайно важно знать, в какие области адресного пространства процесса загружаются ваши DLL. Скорее всего вы сможете узнать свои DLL по адресам загрузки. Чтобы получить сведения о других DLL на компьютере пользователя, можно написать утилиту, которая просматривала бы их и сообщала их имена, адреса загрузки и размер.
Следующий фрагмент представляет собой начало раздела состояния потока, состоящего из трех частей (из за размеров страницы мне пришлось удалить коды

690 ЧАСТЬ V Приложения
операций, расположенные после адресов дизассемблированных команд, и пере нести строки информации о регистрах).
*——> State Dump for Thread Id 0xe14 (Копия памяти для потока 0xe14) <——*
eax=00000000 ebx=00000000 ecx=011305d8 edx=00000a30 esi=00154b40 edi=0012fae4
eip=00410144 esp=0012faa8 ebp=0012faf0 iopl=0 |
|
|||||
nv up ei pl nz na pe nc |
|
|
|
|
||
cs=001b |
ss=0023 |
ds=0023 |
es=0023 |
fs=0038 |
gs=0000 |
efl=00000202 |
функция: WDBG!CWDBGProjDoc__HandleBreakpoint |
|
|||||
|
0041012b |
push |
esi |
|
|
|
|
0041012c |
push |
edi |
|
|
|
|
0041012d |
push |
ecx |
|
|
|
|
0041012e |
lea |
edi,[ebp60x40] |
|
|
|
|
00410131 |
mov |
ecx,0xd |
|
|
|
|
00410136 |
mov |
eax,0xcccccccc |
|
|
|
|
0041013b |
rep |
stosd |
|
|
|
|
0041013d |
pop |
ecx |
|
|
|
|
0041013e |
mov |
[ebp60x10],ecx |
|
|
|
|
00410141 |
mov |
eax,[ebp+0xc] |
|
|
|
СБОЙ > 00410144 |
mov |
ecx,[eax+0x4] |
ds:0023:00000004=???????? |
|||
|
00410147 |
cmp |
dword ptr [ecx],0x80000003 |
|
||
|
0041014d |
jz WDBG!CWDBGProjDoc__HandleBreakpoint+0x90 (004101a0) |
||||
|
0041014f |
mov |
[ebp60x14],esp |
|
|
|
|
00410152 |
mov |
[ebp60x18],ebp |
|
|
|
|
00410155 |
mov |
esi,esp |
|
|
|
|
00410157 |
push |
0x456070 |
|
|
|
|
0041015c |
push |
0x45606c |
|
|
|
|
00410161 |
mov |
edx,[ebp60x18] |
|
|
|
|
00410164 |
xor |
eax,eax |
|
|
|
|
00410166 |
push |
eax |
|
|
|
Dr. Watson отображает информацию о состоянии каждого потока, выполнявше гося в процессе в момент ошибки. Состояния потока содержат всю информацию, необходимую для обнаружения механизма и причин краха.
В разделе регистров указываются значения всех регистров в момент ошибки. Особое внимание следует уделить регистру EIP, указателю команд. Для моего при мера из Windows XP у меня имелись символы, поэтому вы можете видеть, какую функцию выполнял этот поток в момент ошибки, но в большинстве журналов Dr. Watson информации о символах не будет. Конечно, если Dr. Watson не сообщает вам имя функции, это не проблема. Просто загрузите в программу CrashFinder из главы 12 проект CrashFinder вашего приложения, введите адрес в поле Hexadecimal Address(es) (шестнадцатеричные адреса) и нажмите кнопку Find (найти).
Поток из нашего фрагмента оказался потоком, вызвавшим ошибку. Об этом свидетельствует только указатель FAULT > (СБОЙ >) в середине дизассемблиро ванного листинга. Пару раз я видел журналы Dr. Watson, в которых не было ука зателя FAULT >. Если вы не можете найти в журнале этот указатель, изучите со
ПРИЛОЖЕНИE A Чтение журналов Dr. Watson |
691 |
|
|
стояние каждого потока и введите каждый адрес EIP в CrashFinder, чтобы узнать, какую команду выполнял поток в момент ошибки.
Если вы читали главу 7, дизассемблированный листинг должен быть вам по нятен. Новыми элементами будут только значения, показанные после команд. Чтобы вы могли узнать, какие значения использовались командой, дизассемблер Dr. Watson пытается просмотреть эффективный адрес ссылки на память. Адреса, начинаю щиеся с букв ss, свидетельствуют о том, что происходил доступ к сегменту стека; ds — к сегменту данных. В Windows XP/Server 2003 эффективный адрес будет указан только после строки, на которую указывал регистр EIP в момент ошибки.
Вжурналах Dr. Watson из Windows 2000 эффективные адреса будут указаны после каждой ассемблерной команды. Однако при этом гарантируется правиль ность только того адреса, что указан в строке, на которой находился указатель команд. Другие адреса могут быть неверны, так как значения, используемые коман дой, могли измениться. Допустим, первая дизассемблированная команда в состо янии потока ссылалась на память при помощи регистра EBX. Если ошибка случи лась после выполнения еще 10 команд, то одна из промежуточных команд легко могла изменить EBX. Однако, когда Dr. Watson в Windows 2000 дизассемблирует программу, для преобразования эффективного адреса он использует текущее зна чение EBX — то, которое имело место в момент ошибки. Поэтому эффективный адрес, показанный в дизассемблированном коде, может быть неверным. Итак, прежде чем поверить в значения эффективных адресов, убедитесь, что нужные регистры не были изменены никакой командой.
Благодаря недавно приобретенным навыкам работы с ассемблером, вы долж ны легко узнать, почему этот поток потерпел крах. Читая ассемблерный листинг Dr. Watson (или отладчика), большинство программистов допускает серьезную ошибку: они изучают его сверху вниз. Настоящая хитрость в том, чтобы начать исследование с места ошибки и постепенно подниматься вверх в поисках коман ды, присвоившей значения регистрам, использованным в команде, вызвавшей ошибку.
Внашем случае поток потерпел крах на команде 00410144 MOV ECX, [EAX+0x4], при которой регистр EAX имел значение 0. В Microsoft Windows все адреса, располо женные ниже 64 кб, отмечены как не имеющие доступа, поэтому попытка чтения памяти по адресу 0x00000004 — не лучшая идея. Итак, мы должны найти коман ду, заносящую 0 в EAX. Поднявшись на одну строку, вы увидите команду MOV EAX, [EBP+0xC]. Помните, что второй операнд, источник, помещается в первый операнд, приемник (иначе говоря, помните про правило «от источника к приемнику»). Это значит, что в EAX было скопировано значение, находившееся по адресу [EBP+0xC]. Следовательно, по адресу [EBP+0xC] располагался 0.
Вэтот момент вы должны вспомнить еще одну хитрость, которую я описал в главе 7: «параметры располагаются по положительным смещениям»! Параметры располагаются по положительным смещениям от регистра EBP, причем первый находится по адресу [EBP+0x8], а каждый следующий отстоит от предыдущего на 4 байта. Так как 0xC на 4 байта больше, чем 0x8, я могу предположить, что ошиб ка была вызвана тем, что второй параметр этой функции был равен NULL (наде юсь, прочитав эти два абзаца, вы поняли, как важно знать ассемблер в достаточ ном объеме для чтения журналов Dr. Watson!).

692 ЧАСТЬ V Приложения
Ниже вы можете увидеть вторую часть состояния потока: раздел Stack Back Trace (обратная трассировка стека) Заметьте: я вывожу имена функций на двух строках, чтобы они помещались на странице. При помощи двух символов подчеркивания (__) Dr. Watson отображает операцию разрешения области видимости (::).
*——> Stack Back Trace (Обратная трассировка стека) <——*
ChildEBP RetAddr |
Args to Child |
|
||
0012faf0 |
004100cd |
00000a30 |
00000000 |
80000003 |
|
|
|
WDBG!CWDBGProjDoc__HandleBreakpoint +0x34 |
|
0012fb0c |
004075f1 |
00000a30 |
0164f8fc |
01130b68 |
|
|
|
WDBG!CWDBGProjDoc__HandleExceptionEvent+0x6d |
|
0012fb20 |
7c3422b2 |
00000a30 |
0164f8fc |
0000000d |
|
|
|
WDBG!CDocNotifyWnd__HandleExceptionEvent+0x21 |
|
0012fc28 |
7c341b2e |
00000502 |
00000a30 |
0164f8fc |
|
|
|
MFC71UD!CWnd__OnWndMsg+0x752 |
|
0012fc48 |
7c33f2f0 |
00000502 |
00000a30 |
0164f8fc |
|
|
|
MFC71UD!CWnd__WindowProc+0x2e |
|
0012fcc0 7c33f7ce |
01130b68 |
002502ca 00000502 |
||
|
|
|
MFC71UD!AfxCallWndProc+0xe0 |
|
0012fce0 |
7c3b072a 002502ca |
00000502 |
00000a30 |
|
|
|
|
MFC71UD!AfxWndProc+0x9e |
|
0012fd10 |
77d67ad7 002502ca |
00000502 |
00000a30 |
|
|
|
|
MFC71UD!AfxWndProcBase+0x4a |
|
0012fd3c 77d6ccd4 7c3b06e0 002502ca 00000502 |
||||
|
|
|
USER32!SetWindowPlacement+0x57 |
|
0012fda4 |
77d445bd |
00000000 |
7c3b06e0 |
002502ca |
|
|
|
USER32!DefRawInputProc+0x284 |
|
0012fdf8 |
77d447d4 |
00593330 |
00000502 |
00000a30 |
|
|
|
USER32!TranslateMessageEx+0x78d |
|
0012fe20 77fb4da6 0012fe30 00000018 00593330 |
||||
|
|
|
USER32!DefWindowProcA+0x209 |
|
0012fe64 |
7c34e8e1 00154af8 |
00000000 |
00000000 |
|
|
|
|
ntdll!KiUserCallbackDispatcher+0x13 |
|
0012fe90 7c34fb4c |
00455e30 |
0012feb8 7c34f407 |
||
|
|
|
MFC71UD!AfxInternalPumpMessage+0x21 |
|
0012fe9c |
7c34f407 |
00000001 |
00455e30 |
00154ac8 |
|
|
|
MFC71UD!CWinThread__PumpMessage+0xc |
|
0012feb8 |
7c34fe87 |
00455e30 |
00434ffa |
0040a66b |
|
|
|
MFC71UD!CWinThread__Run+0x87 |
|
0012fecc 7c34865a |
1020c034 |
102682d0 |
ffffffff |
|
|
|
|
MFC71UD!CWinApp__Run+0x57 |
|
0012fef0 |
00430008 |
00400000 |
00000000 |
00020c22 |
|
|
|
MFC71UD!AfxWinMain+0xda |
|
0012ff08 |
004284b8 |
00400000 |
00000000 |
00020c22 |
|
|
|
WDBG!wWinMain+0x18 |
|
0012ffc0 77e814c7 |
00140000 |
01f88550 |
7ffdf000 |
|
|
|
|
WDBG!wWinMainCRTStartup+0x1f8 |
|
0012fff0 |
00000000 |
004282c0 |
00000000 |
78746341 |
|
|
|
kernel32!GetCurrentDirectoryW+0x44 |