среда, 1 сентября 2010 г.

Анализ дампов приложений, написанных на Delphi

   Приложения могут содержать ошибки. Некоторые проявляются воспроизводятся легко и, как правило, не выходят за пределы отдела программирование (другими словами - не попадают к заказчику/клиенту). А некоторые появляются при весьма экзотических внешних условиях.


   Обычно ошибки "проявляются" в виде исключений. Обычно в программу встраивается один из механизмов сохранения появляющихся ошибок в файл (лог-файл) для того, чтобы была возможность отправить эту информацию разработчикам для исправления. В лог может сохраняться информацию о возникшем исключении, стек вызов, список загруженных модулей, информация о системе и т.п. В Delphi наиболее известны EurekaLog, MadExcept и JclDebug (Project JEDI).

   Но есть ошибки, которые не могут быть обработаны и сохранены - т.н. необработанные исключения, когда приложение вдруг показывает сообщение об ошибке, а при закрытии этого сообщение - выгружается из памяти. А может вообще тихо закрыться. В Windows в такой ситуации может сохранить дамп процесса перед его уничтожением (т.н. посмертный дамп, [1]).

   Для анализа дампов можно использовать Visual Studio и Windbg (Debugging Tools for Windows). Лично я предпочитаю Windbg.

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

   Только существует одна проблема - Windbg/VS не понимают формата отладочной информации для бинарников, скомпилированных Delphi (*.map, *.jdbg). И скорее всего никогда не научится их понимать :)
   А без символов анализировать дампы не особо захватывающее занятие. Нельзя сказать, что совершенно невозможное, но все таки с символами оно как-то приятнее.

   Но, как оказалось, есть и решение - Map2Dbg [2]. Map2Dbg - это небольшая утилита, которая конвертирует .map-файлы (Delphi, CBuilder) в формат, понимаемый Windbg и VS - Microsoft .dbg-файлы (codeview symbols) [3]. Конвертирование .jdbg в .dbg пока на стадии реализации. Да, формат .dbg уступает .pdb в плане детальности и информативности, но полученных таким образом файлов достаточно для получение более-менее читаемого и достоверного стека вызовов. Ну и можно посмотреть значение глобальных переменных.

   Для примера возьмем дамп зависшего тестового приложения. После открытия дампа в Windbg видим, что в программе два потока: один ждет освобождения критической секции, а второй спит.
.  0  Id: ac0.dcc Suspend: 1 Teb: 7ffde000 Unfrozen
ChildEBP RetAddr  
0012f50c 7c90df5a ntdll!KiFastSystemCallRet
0012f510 7c91b24b ntdll!ZwWaitForSingleObject+0xc
0012f598 7c901046 ntdll!RtlpWaitForCriticalSection+0x132
0012f5a0 00425629 ntdll!RtlEnterCriticalSection+0x46
WARNING: Stack unwind information not available. Following frames may be wrong.
0012f5b4 00456d68 Project22+0x25629
0012f5bc 00456d5c Project22+0x56d68
0012f5c4 004381da Project22+0x56d5c
0012f708 0043bb7c Project22+0x381da
0012f748 00426f69 Project22+0x3bb7c
0012f774 0043bccc Project22+0x26f69
0012f8cc 0043bb7c Project22+0x3bccc
0012f90c 0044cc80 Project22+0x3bb7c
0012f934 0043b2a3 Project22+0x4cc80
0012f964 0041b802 Project22+0x3b2a3
0012f97c 7e418734 Project22+0x1b802
0012f9a8 7e418816 user32!InternalCallWinProc+0x28
0012fa10 7e42927b user32!UserCallWinProcCheckWow+0x150
0012fa4c 7e4292e3 user32!SendMessageWorker+0x4a5
0012fa6c 773f7354 user32!SendMessageW+0x7f
0012fa8c 773f7436 comctl32!Button_NotifyParent+0x3d
0012faa8 773f973b comctl32!Button_ReleaseCapture+0xd7
0012fb38 7e418734 comctl32!Button_WndProc+0x887
0012fb64 7e418816 user32!InternalCallWinProc+0x28
0012fbcc 7e42a013 user32!UserCallWinProcCheckWow+0x150
0012fbfc 7e42a998 user32!CallWindowProcAorW+0x98
0012fc1c 0043bc78 user32!CallWindowProcA+0x1b
0012fd9c 0043bb7c Project22+0x3bc78
0012fddc 00426f69 Project22+0x3bb7c
0012fe1c 0041b802 Project22+0x26f69
0012fe34 7e418734 Project22+0x1b802
0012fe60 7e418816 user32!InternalCallWinProc+0x28
0012fec8 7e4189cd user32!UserCallWinProcCheckWow+0x150
0012ff28 7e4196c7 user32!DispatchMessageWorker+0x306
0012ff38 004549fd user32!DispatchMessageA+0xf
0012ff54 00454a1f Project22+0x549fd
0012ff78 00454d14 Project22+0x54a1f
0012ffa8 0045876a Project22+0x54d14
0012ffc0 7c817077 Project22+0x5876a
0012fff0 00000000 kernel32!BaseProcessStart+0x23

   1  Id: ac0.f88 Suspend: 1 Teb: 7ffdd000 Unfrozen
ChildEBP RetAddr  
00cbfef8 7c90d21a ntdll!KiFastSystemCallRet
00cbfefc 7c8023f1 ntdll!NtDelayExecution+0xc
00cbff54 7c802455 kernel32!SleepEx+0x61
00cbff64 00456ebd kernel32!Sleep+0xf
WARNING: Stack unwind information not available. Following frames may be wrong.
00cbff70 0041a17f Project22+0x56ebd
00cbffa0 0040484e Project22+0x1a17f
00cbffb4 7c80b729 Project22+0x484e
00cbffec 00000000 kernel32!BaseThreadStart+0x37

   К гадалке не ходи - критическая секция захвачена вторым потоком. Так что в приведенном дампе можно обойтись и без символов. Но это же просто пример - для демонстрации техники, так что я особо не извращался в создании дедлока :)

   После загрузки символов, картина становится красочнее :)
.  0  Id: ac0.dcc Suspend: 1 Teb: 7ffde000 Unfrozen
ChildEBP RetAddr  
0012f50c 7c90df5a ntdll!KiFastSystemCallRet
0012f510 7c91b24b ntdll!ZwWaitForSingleObject+0xc
0012f598 7c901046 ntdll!RtlpWaitForCriticalSection+0x132
0012f5a0 00425629 ntdll!RtlEnterCriticalSection+0x46
0012f5b4 00456d68 Project22!SyncObjs.TCriticalSection.Acquire+0x9
0012f5bc 00456d5c Project22!Unit22.TForm22.Procedure1+0x8
0012f5c4 004381da Project22!Unit22.TForm22.Button2Click+0x8
0012f708 0043bb7c Project22!Controls.TControl.Click+0x6a
0012f748 00426f69 Project22!Controls.TWinControl.WndProc+0x500
0012f774 0043bccc Project22!StdCtrls.TButtonControl.WndProc+0x71
0012f8cc 0043bb7c Project22!Controls.DoControlMsg+0x28
0012f90c 0044cc80 Project22!Controls.TWinControl.WndProc+0x500
0012f934 0043b2a3 Project22!Forms.TCustomForm.WndProc+0x558
0012f964 0041b802 Project22!Controls.TWinControl.MainWndProc+0x2f
0012f97c 7e418734 Project22!Classes.StdWndProc+0x16
0012f9a8 7e418816 user32!InternalCallWinProc+0x28
0012fa10 7e42927b user32!UserCallWinProcCheckWow+0x150
0012fa4c 7e4292e3 user32!SendMessageWorker+0x4a5
0012fa6c 773f7354 user32!SendMessageW+0x7f
0012fa8c 773f7436 comctl32!Button_NotifyParent+0x3d
0012faa8 773f973b comctl32!Button_ReleaseCapture+0xd7
0012fb38 7e418734 comctl32!Button_WndProc+0x887
0012fb64 7e418816 user32!InternalCallWinProc+0x28
0012fbcc 7e42a013 user32!UserCallWinProcCheckWow+0x150
0012fbfc 7e42a998 user32!CallWindowProcAorW+0x98
0012fc1c 0043bc78 user32!CallWindowProcA+0x1b
0012fd9c 0043bb7c Project22!Controls.TWinControl.DefaultHandler+0xdc
0012fddc 00426f69 Project22!Controls.TWinControl.WndProc+0x500
0012fe1c 0041b802 Project22!StdCtrls.TButtonControl.WndProc+0x71
0012fe34 7e418734 Project22!Classes.StdWndProc+0x16
0012fe60 7e418816 user32!InternalCallWinProc+0x28
0012fec8 7e4189cd user32!UserCallWinProcCheckWow+0x150
0012ff28 7e4196c7 user32!DispatchMessageWorker+0x306
0012ff38 004549fd user32!DispatchMessageA+0xf
0012ff54 00454a1f Project22!Forms.TApplication.ProcessMessage+0x101
0012ff78 00454d14 Project22!Forms.TApplication.HandleMessage+0xf
0012ffa8 0045876a Project22!Forms.TApplication.Run+0xb8
0012ffc0 7c817077 Project22!Project22.Project22+0x4e
0012fff0 00000000 kernel32!BaseProcessStart+0x23

   1  Id: ac0.f88 Suspend: 1 Teb: 7ffdd000 Unfrozen
ChildEBP RetAddr  
00cbfef8 7c90d21a ntdll!KiFastSystemCallRet
00cbfefc 7c8023f1 ntdll!NtDelayExecution+0xc
00cbff54 7c802455 kernel32!SleepEx+0x61
00cbff64 00456ebd kernel32!Sleep+0xf
00cbff70 0041a17f Project22!Unit22.TSomeThread.Execute+0x15
00cbffa0 0040484e Project22!Classes.ThreadProc+0x37
00cbffb4 7c80b729 Project22!System.ThreadWrapper+0x2a
00cbffec 00000000 kernel32!BaseThreadStart+0x37


   Для эпизодического анализа достаточно полученные .dbg-файлы положить рядом с дампом (в случае живой отладки в Windbg - в каталоге с бинарниками). Ну а для более-менее регулярного использования лучше завести сервер символов.


   И еще один пример - использование в Process Explorer. Стек вызовов спящего потока зависшего тестового приложения.


Без символов


С символами

Ссылки
[1] Specifying the Debugger for Unhandled User Mode Exceptions
[2] Map2Dbg is a mall tool to convert a .map file to a .dbg file
[3] Description of the .PDB files and of the .DBG files

Комментариев нет:

Отправить комментарий