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

       

Первая секция журнала выглядит так:



Первая секция журнала выглядит так:

Microsoft (R) Windows 2000 (ТМ) Version 5.00 DrWtsn32

Copyright (С) 1985-1999 Microsoft Corp. All rights reserved.

Application exception occurred: 

App: (pid=252)

When: 9/4/1999 @ 16:43:56.173

Exception number: cOOOOOOS (access violation)

Информация заголовка сообщает причину аварийного останова. В данном случае — это исключительная ситуация (исключение), возникшая в приложении. Номера исключений для некоторых аварийных ситуаций невозможно перевести в удобочитаемое описание, например, такое, как показано в последней строке нашего заголовка ("access violation — нарушение доступа" для исключения с (шестнадцатеричным) номером С0000005). Все возможные значения номеров исключений можно найти в препроцессорных директивах #define STATUS_ файла WINNT.H. Значения, отмеченные в документации, как данные типа EXCEPTION_, возвращает функция GetExceptionCode, но реальные величины находятся в директивах #define STATUS_. Функция GetExceptionCode преобразует номер исключения в ЕХСЕРТION_-значение, а по ее документации можно найти более подробное описание данного сбоя.

Секция system information (сведения о системе) самоочевидна и не требует пояснений:



* - - -> System Information < - - -*

 Computer Name: PLATO 

User Name: John 

Number of Processors: 1

Processor Type: x86 Family 6 Model 6 Stepping 10 

Windows 2000 Version: 5.0 

Current Build: 2128

Service Pack: None 

Current Type: Uniprocessor Free

Registered Organization: Enter your company name here

Registered Owner: John Robbins

Секция Task List (список задач) выглядит примерно так:

* - - - > Task List < - - -*

0 Idle.ёхе

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 

920 4nt.exe 
940 taskmgr.exe

 956 tponscr.exe

 268 msdev.exe 

252 WDBG.exe 

828 NOTEPAD.exe

 416 drwtsn32.exe 

0 _Total.exe

В этой секции показан список процессов, которые выполнялись во время сбоя. К сожалению, Windows 2000 не показывает информацию о версиях, включенных в этот список файлов, поэтому в случае необходимости нужно получить эту информацию у пользователя. Числа слева от имен программных файлов — это десятичные идентификаторы процессов (PID — Program ID).

Следующий список содержит адреса загрузки всех модулей, находившихся в адресном пространстве во время аварии:

(00400000 - 0042D000) D:\Dev\Book\CD\SourceCode\Output\WDBG.pdb

(77F80000 - 77FF9000) E:\WINNT\symbols\dll\ntdll.dbg

(60000000 - 6001А000) D:\Dev\Book\CD\SourceCode\Output\BugslayerUtil.pdb

 (77Е80000 - 77F35000) E:\WINNT\symbols\dll\kernel32.dbg

 (77Е10000 - 77Е74000) E:\WINNT\symbols\dll\user32.dbg

 (77F40000 - 77F7C000) E:\WINNT\symbols\dll\gdi32.dbg 

(72950000 - 72967000) E:\WINNT\symbols\dll\dbghelp.dbg

 (78000000 - 78046000)

• (77DB0000 - 77Е07000) Е:\WINNT\symbols\dll\advapi32.dbg 

(77D30000 - 77DA2000) E:\WINNT\symbols\dll\rpcrt4.dbg 

(10200000 - 10264000)

(63100000 - 63108000) D:\Dev\Book\CD\SourceCode\Output\LocalAssist.pdb

 (62000000 - 6202В000) D:\Dev\Book\CD\SourceCode\Output\i386CPUHelp.pdb

 (63000000 - 63010000) D:\Dev\Book\CD\SourceCode\Output\LocalDebug.pdb

 (5F400000 - 5F4E5000)

(77ВЗ0000 - 77ВВА000) E:\WINNT\symbols\dll\comctl32.dbg

 (775А0000 - 777DE000) E:\WINNT\symbols\dll\shell32.dbg 

(77С50000 - 77С9А000) E:\WlNNT\symbols\dll\shlwapi.dbg 

(76В20000 - 76В5Е000) E:\WINNT\syitibols\dll\comdlg32.dbg 

(77АЗ0000 - 77В24000) E:\WINNT\symbols\dll\ole32.dbg 

(77990000 - 77А24000) E:\WINNT\symbols\dll\oleaut32.dbg

 (77СА0000 - 77D25000) 

(77850000 - 7788В000)

 (770В0000 - 770D3000)

(6В6Е0000 - 6B6FC000) E:\WINNT\symbols\dll\msdbi.dbg 



(68ED0000 - 68EDB000) E:\WINNT\symbols\dll\psapi.dbg

Числа в круглых скобках в левой части этого списка указывают начальный и максимальный адреса загрузки каждого модуля. Намного полезнее было бы показать в этой секции фактические имена модулей, загруженных в адресное пространство. К сожалению, как видно по именам, расположенным справа от адресов загрузки, здесь показаны только каталоги, в которые Dr. Watson загрузил файлы отладочных символов соответствующего модуля. Незаполненные входы означают, что никакой отладочной информации для данного модуля загружено не было.

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

Ниже показана первая часть дампа состояния потока (с идентификационным номером Ox2fc):

State Dump for Thread Id 0x2fс

eax=00000000 ebx=7ffdfOOO ecx=008c67cO edx=0000033c esi=00134c78 edi=0012fd74 

eip=0040bd2d esp=0012fb98 ebp=0012fbc4 iopl=0 nv up ei pi nz na pe nc

 cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202

function: CWDBGProjDoc::HandleBreakpoint

0040bdll push esi

0040bdl2 push edi

0040bdl3 mov eax,Oxcccccccc

0040bdl8 mov [ebp+OxeO],eax ss:00b4dl9a=????????

0040bdlb mov [ebp+0xe4],eax ss:00b4dl9a=????????

0040bdle mov [ebp+0xe8],eax ss:00b4dl9a=????????

0040bd21 mov [ebp+Oxec],eax ss:00b4dl9a=????????

0040bd24 mov [ebp+Oxf0],eax ss:00b4dl9a=????????

0040bd27 mov [ebp+OxeO],ecx ss:00b4dl9a=????????

0040bd2a mov eax,[ebp+Oxc] ss:00b4dl9a=????????

FAULT ->004Obd2d mov ecx,[eax+0x4] ds:00ald5d6=????????

0040bd30 cmp dword ptr [ecx],0x80000003 ds:008c67cO=0041b714 

0040bd36 jz CArray<COneShotBP,COneShotBP>::SetSize+Ox25d (0041485d)

0040bd38 mov  esi,esp



0040bd3a push 0x382

0040bd3f push 0x420030

0040bd44 push 0x420064

0040bd49 push 0x0

0040bd4b call dword ptr [_imp_DiagAssertA (00423ad4)]

0040bd51 cmp esi,esp

0040bd53 call _chkesp (00416b5a)

0040bd58 test eax,eax

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

В регистровой части дампа показано содержимое всех регистров во время сбоя. Важно следить за регистром EIP (указателем машинных команд). В данном примере используются отладочные символы, так что можно видеть, какую функцию выполнял этот поток во время сбоя, однако довольно часто журналы этой программы не могут пользоваться отладочными символами1. Нет ничего страшного, если в журнале нет имени функции. В этом случае нужно загрузить для своего приложения проект программы CrashFinder, рассмотренной в главе 8, нажать клавиши <Ctrl>+<F> и ввести в открывшееся диалоговое окно Find EIP-адрес этого потока.

 Тогда вместо имени функции в журнале указывается значение <nosymbols>. — Пер.

Так уж случилось, что сбой произошел в этом потоке. Единственным индикатором этого события в журнале является указатель FAULT-> в середине кода дизассемблера. Иногда этот указатель не выводится. Тогда, для того чтобы определить, что происходило с потоком во время сбоя, нужно просмотреть (по журналу) состояние каждого потока и ввести соответствующий EIP-адрес в CrashFinder.

Код дизассемблера рассмотрен в главе 6. Новыми элементами являются значения, показанные справа от команд. Дизассемблер программы Dr. Watson пытается найти самый эффективный адрес для ссылки на команду. Справа от команды можно увидеть, с каким еще адресным значением работала команда. Метка ss: указывает на адреса сегмента стека, а ds: — на адреса сегмента данных.

Единственный эффективный адрес в коде дизассемблера, который с гарантией является корректным, — это адрес в указателе команд. Другие адреса могут быть некорректны, потому что значение, на которое ссылается команда, может быть изменено.


Например, пусть первая команда, дизассемблированная в дампе состояния потока, использовала (в качестве аргумента) ссылку на ячейку памяти из регистра ЕВХ. Если сбой дала команда, расположенная на 10 инструкций позже, то одна из промежуточных команд может легко изменить содержимое ЕВХ. Однако когда Dr. Watson выполняет дизассемблирование, то для эффективного преобразования адреса он использует текущее значение ЕВХ (т. е. то, которое этот регистр содержал во время сбоя). По этой причине эффективный адрес, показанный в коде дизассемблера дампа состояния потока, может быть неправильным. Для того чтобы убедиться, что в дампах журнала отображены корректные адреса, тщательно проверьте, не могли ли какие-нибудь команды, предшествующие сбою, изменить значения регистров.

Можно вычислить, что в данном потоке произошел сбой, потому что команда:

Ox0040BD2D MOV ECX,[ЕАХ+Ох4]

пыталась получить доступ к ПУСТОМУ (NULL) указателю в ЕАХ (ЕАХ=00000000). Посмотрев внимательно на команду в предшествующей строке:

MOV ЕАХ, [ЕВР+ОхС],

можно предположить, что с положительным смещением от ЕВР, вероятно, расположена адресная ссылка на параметр функции. Величина смещения (ОхС) означает, что команда сослалась на второй параметр функции. Первой гипотезой при отладке этого сбоя должна быть такая: выполнение функции завершилось неудачей, потому что ей был передан неправильный второй параметр (все это показывает, как важно хорошо знать язык ассемблера для чтения журналов программы Dr. Watson!).

Ниже показана вторая часть дампа состояния потока, которая названа stack Back Trace (обратная трассировка стека) (). Заметьте, что длинные значения столбца Function Name (и только они) там, где они не поместились в строке, перенесены на следующую строку.

*——> Stack Back Trace <——*

FramePtr ReturnAd Paramtl Param#2 Param#3 Param#4 Function Name

0012FBC4 0040BCB5 0000033C 00000000 80000003 008C67CO

!CWDBGProj Doc::HandleBreakpoint 

0012FBEO 00405A9C 0000033C 010DFCOC 008C68BO 0012FCF4

!CWDBGProj Doc::HandleExceptionEvent 



0012FBF4 5F42F3AC 0000033C 010DFCOC 0012FD74 00134C78

!CDocNotifyWnd::HandleExceptionEvent

0012FCF4 5F42ECE8 00000502 0000033C 010DFCOC 0012FD10 !0rdinal4118 0012FD14 5F42C889 00000502 0000033C 010DFCOC 77F86-618 !0rdinal5076 0012FD88 5F42CD25 008C68BO 000602A4 00000502 0000033C !0rdinall045 0012FDB4 5F4905FD 000602A4 00000502 0000033C 010DFCOC !0rdinalll92 0012FDE4 77E135F8 000602A4 00000502 0000033C 010DFCOC !0rdinalll93 0012FE04 77E15FE8 5F4905B3 000602A4 00000502 0000033C

                            user32!UserCallWinProc

 0012FE20 77E1600E 004A9B70 00000502 0000033C 010DFCOC

                       user32!DispatchClientMessage (FPO: Non-FPO 

[5,1,0])

 0012FE48 77F9D8B7 0012FE58 00000018 004A9B70 00000502

                     user32!_fnDWORD (FPO: Non-FPO [1,4,0])

 0012FE68 77E15FB5 77E17BD9 00422250 00000000 00000000

                  ntdlliKiUserCallbackDiSpatcher (FPO: [0,0,0]) 

0012FE90 5F4396F8 00422250 00000000 00000000 00000000

                user32!DispatchClientMessage (FPO: Non-FPO [5,1,0])

0012FEB8 5F438E1D 77F86618 77F81A9B 7FFDFOOO 00000001 !0rdinal4239 0012FEDC 5F439AD4 00422218 0012FF08 5F43366E 77F86618 !0rdinal4409 0012FEE8 5F43366E 77F86618 77F81A9B 7FFDFOOO 00422218 !0rdinal4408 0012FF08 00417028 00400000 00000000 00133A73 00000001 !Ordinalll90 0012FF20 00416E53 00400000 00000000 00133A73 00000001 'WinMain 0012FFCO 77E9BC52 77F86618 77F81A9B 7FFDFOOO C0000005 !WinMainCRTStartup 

0012FFFO 00000000 00416CAO 00000000 OOOOOOC8 00000100



                 kernel32!BaseProcessStart (FPO: Non-FPO [1,8,3])

Данный пример журнала использует отладочные символы (вследствие чего в колонке Function Name отображены имена всех вызванных к моменту сбоя функций), однако, в пользовательском журнале этого, вероятно, не будет1. В колонке ReturnAd перечисляются адреса возврата функций, находящихся в стеке вызовов. Если журнал пользователя не содержит отладочных символов, то нужно загрузить каждый адрес из колонки ReturnAd в программу CrashFinder (чтобы вычислить последовательность вызовов функций, которая привела к сбою).

 Тогда вместо имени функции в журнале указывается значение <nosymbols>. — Пер.

Колонки Paramtl, #2, #3 и #4 показывают четыре первых возможных параметра функций в стеке вызовов. При высоко оптимизированном выпуском построении и без отладочных символов показанные здесь значения, вероятно, некорректны. Однако их можно использовать в качестве отправной точки для ручной прокрутки кода.

Имена функций показаны в формате <модуль>! «функциях Функции с именами ! Ordinal#N являются функциями порядкового экспорта. Если у вас нет исходного кода DLL, которая выполняет экспорт функций по порядковым номерам (а не по именам), то вам просто не повезло. Однако, вследствие того, что библиотека классов MFC (Microsoft Foundation Classes) поставляется с исходными кодами, порядковые значения MFC-функций найти можно. Например, для программы WDBG известно, что библиотека MFC42D.DLL загружается по адресу Ox5F400000, так что есть возможность отыскать порядковые номера ее функций, потому что все MFC-функции экспортируются по своим порядковым номерам через DEF-файл компоновщика.

Существует одно предварительное условие для преобразования порядковых номеров MFC-функций в их имена: необходимо точно знать версию MFC DLL на аварийной машине. На моей машине, называемой \\PLATO, имелась MFC42D.DLL из системы Visual C++ 6 Service Pack 3.



Ниже приводится последовательность действий для преобразования порядковых номеров функций в их имена:

1. Откройте каталог \VC98\MFC\SRC\Intel системы Visual C++.

2. Выберите подходящий DEF-файл для того MFC-файла, который собираетесь посмотреть. Например, DEF-файлом для MFC42D.DLL является MFC42D.DEF.

3. Ищите порядковый номер. Чтобы найти имя функции с порядковым номером Ordinal4118 из предыдущего стека, я поискал бы в файле MFC42D.DEF строку с номером 4118. Она выглядит так:

? OnWndMsg@CWnd@@MAEHIIJPAJ @Z 4118 NONAME.

Имя, расположенное слева от подстроки "@ 4118 NONAME", является расширенным (декорированным) именем функции, экспортируемой по порядковому номеру. Чтобы преобразовать декорированное имя в обычное, используйте программу UNDNAME.EXE, которая поставляется в составе набора инструментальных средств Platform SDK. Для функции с порядковым номером 4118, обычно, именем функции является  CWND::OnWndMsg.

Третья, заключительная часть дампа состояния потока называется Raw stack Dump (дамп необработанного стека) и выглядит так:

*———> Raw Stack Dump <———*

0012fb98 74 fd 12 00 78 4c 13 00-00 fO fd 7f cO 67 8c 00 t...xL....g..

0012fba8 cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc ................

0012fbb8 e8 fc 12 00 2e 8a 41 00-ff ff ff ff eO fb 12 00 .....A......

0012fbc8 b5 be. 40 00 3c 03 00 00-00 00 00 00 03 00 00 80 ..@.<.......

0012fbd8 cO 67 8c 00 cc cc cc cc-f4 fb 12 00 9c 5a 40 00 .g........Z@.

0012fbe8 3c 03 00 00 Oc fc Od 01-bO 68 8c 00 f4 fc 12 00 <......h.....

0012fbf8 ac f3 42 5f 3c 03 00 00-Oc fc Od 01 74 fd 12 00 ..B_<....t...

0012fc08, 78 4c 13 00 00 fO fd 7f-09 00 00 00 bO 68 8c 00 xL.......h..

0012fcl8 04 00 00 00 00 00 00 00-00 00 8c 00 50 00 8c 00 .........P...

0012fc28 50 00 8c 00 01 00 00 00-ae 05 12 00 70 2d 00 00 P........p-..

0012fc38 01 00 00 00 fa 00 00 00-8c fc 12 00 8b 48 fb 77 ..........H.w

0012fc48 00 00 8c 00 00 00 9f 01-01 00 00 00 80 fc 12 00 ............

0012fc58 7c fc 12 00 84 fc 12 00-00 00 00 00 00 00 00 00 | ...........

0012fc68 00 00 8c 00 00 00 8c 00-90 fc 12 00 d5 19 49 5f ...........I_

0012fc78 04 00 00 00 18 66 f8 77-9b la f8 77 a4 fc 12 00 ...f.w...w...

0012fc88 d5 19 49 5f 04 00 00 00-18 66 f8 77 9b la f8 77 ..!_...f.w..w

0012fc98 00 fO fd 7f 48 el 4c 5f-48 4d 13 00 cc fc 12 00 ....H.L_HM...

0012fca8 45 16 49 5f cc fc 12 00-d5 19 49 5f d4 fc 12 00 E.I_...I_....

0012fcb8 d.8- fc 12 00 d5 19 49 5f-04 00 00 00 18 66 f8 77 ... I_.. f.

0012fcc8 50 22 42 00 00 00 00 00-Oa 00 00 00 00 a5 41 00 P"B........A.

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



Содержание раздела