Сообщений 0 Оценка 190 Оценить |
Преамбула Как все начиналось Теория Реализация Заключение Использованная литература |
В настоящее время, когда компании обращают большое внимание на качество своих продуктов, нестабильные программы становятся неконкурентоспособными на современном рынке. Многократные нагрузочные, регрессионные и unit-тестирования улучшают приложения, однако сбои в работе все же случаются. Различные книги предлагают разные умные подходы к написанию защищенного кода, однако рискну предположить, что приложения падали, падают и будут падать. Хорошо, если они падают в тестовой лаборатории, но они же могут падать и у клиентов. А иногда программы падают по непонятным причинам из-за фатального стечения обстоятельств, которое тестер не может и не сможет повторить… Кстати, об облегчении труда тестеров тоже стоит подумать - они тоже люди, и им тяжело подбирать самый короткий путь к воспроизведению сбоя.
Причиной написания данной статьи явилось то, что не имеется достойных систем обработки дампов. Поэтому многие программисты просят рассказать подробнее, как реализовать такую систему. Именно для них и написана данная статья. В ней я хочу рассказать об опыте нашей компании в создании у себя такой автоматизированной системы.
Однако сразу оговорюсь – я не могу выложить в открытый доступ исходные файлы всех компонентов, упоминающихся в статье, так как система, описанная ниже, является собственностью моей компании.
Итак, если вы все еще полны энтузиазма – начнем!
Некоторое время назад мы решили использовать в одном из своих продуктов сторонние библиотеки одной компании. Подключили, проверили – все работало как часы. Но в один черный день начались единичные письма, потом единичные звонки, а потом начался шквал сообщений об ошибках, о том, что наше приложение нестабильно. Мы начали тестировать это у себя, но у нас ничего не воспроизводилось...
Потом начались поездки к клиентам и просьбы записать порядок действий, приводящих к сбою. Спустя некоторое время виновный был найден. Да-да, это оказалась именно та библиотека (но на ее месте могла бы быть любая другая, в том числе и наша). Оказалось, что она частично несовместима с программными продуктами сторонних фирм. Три недели, потраченных на разбирательства, мы жили в режиме аврала…
Это стало для меня уроком. К сожалению, та падающая программа оказалась моей, из-за чего я тут же занялся проблемой создания средств обнаружения ошибок. Готовых решений я не нашел, поэтому занялся разработкой своего.
Дамп падения (в дальнейшем дамп) – специальный файл, собираемый с помощью библиотеки dbghelp.dll, содержащий в себе информацию о стеке приложения в момент падения. Также содержит в себе информацию о загруженных модулях, хендлах (handles), потоках, участках памяти и т.д. В подавляющем большинстве случаев помогает разобрать стек падения приложения. Release-версия файла (сокращенно release) – бинарный исполняемый файл, собираемый из исходников проекта. Довольно сильно отличается от отладочной версии (например, наличием оптимизации и отсутствием инициализации данных специальными отладочными значениями). Существует заблуждение, что release-файлы отлаживать нельзя. На самом деле их отлаживать можно, просто по умолчанию MS Visual Studio не включает поддержку символов в release-версиях. В документации Microsoft сказано, что включение поддержки символов незначительно увеличивает размер бинарных файлов. Однако, по моему личному опыту, размер файла увеличивается (и значительно). В среднем файл увеличивается на 100 килобайт. На больших проектах это не заметно, но маленькие проекты после этого сильно увеличиваются в размерах. |
СОВЕТ Для включения генерации символов в release-версиях нужно в настройках компилятора выбрать значение «Generate Program Database» для опции «Debug Information Format» (опция компилятора /Zi). Если вы забудете сделать это, файл символов будет построен не полностью и отлаживаться по нему будет невозможно. |
PDB-файл (файл символов или просто символы). При компиляции проекта компоновщик строит исполняемый модуль. Фирмы-производители программного обеспечения уже давно разработали разные методы сохранения информации о строках исходных файлов в модулях символов. В настоящее время наиболее широко (речь идет о Microsoft) используется формат PDB версии 2 (MS Visual Studio 6.0) и PDB 7.0 (MS Visual Studio 7.0+). Данные форматы обеспечивают возможность получения расширенной информации об исполняемых модулях, в том числе возможность разбора стека, получения локальных переменных и т.д. WinDBG. Один из отладчиков приложений Microsoft. Мое мнение заключается в том, что небольшие системы у себя на компьютере очень удобно отлаживать в Visual Studio, однако как только приложение становится распределенным и сложным, или необходима удаленная отладка в сложных условиях, самое удобное средство – это WinDBG. Повторюсь, что это мое личное мнение. Кроме того, в состав WinDBG входят различные утилиты для облегчения процесса отладки. |
Для начала нужно определиться с функциями системы обработки дампов. Система должна уметь:
Таким образом, система должна состоять из следующих модулей:
Рассмотрим теперь все компоненты подробнее.
В момент сбора бинарного файла для него строится также и файл символов. В этом файле хранятся адреса функций, локальных и глобальных переменных, таблицы смещений (relocation table) и т.д. Для проверки совместимости между бинарным файлом и файлом символов компоновщик вносит в них GUID, которые должны быть идентичны в обоих файлах. Если они различаются, отладчики довольно часто отказываются подключать символы. Кроме того, нужно учесть, что потенциально любое изменение проекта вызывает полную несовместимость между файлов символов и компонентом. Поэтому файлы символов и бинарные файлы нужно строить одновременно.
Теперь у нас есть бинарные файлы и файлы символов. Что с ними делать?
Чтобы понять, что с ними делать, нужно рассмотреть типичный сценарий разработки приложения:
Если после этапа 4 у клиента возникают ошибки, то существующий файл символов (построенный после передачи компонента клиентам) уже ничем не помогает, так как он уже не соответствует клиентскому компоненту. Поэтому в момент передачи компонентов клиентам бинарный файл и файл символов должны быть сохранены. Но где? Можно их сохранять в отдельной папке на диске, но в таком случае придется каждый раз выяснять, что за версия приложения стоит у клиента, после чего копировать файлы в какую-нибудь папку на диске, откуда их и использовать. Такой подход удобен, когда файлов мало. Но представьте, что вы разрабатываете несколько компонентов. А теперь представьте десятки компонентов. А теперь представьте десятки компонентов за пару месяцев работы. И сообщения об ошибках идут не только от клиентов (у которых заранее известные версии), а еще и от отдела тестирования, от технической поддержки, от отдела контроля качества… Поэтому нужно искать автоматизированное решение.
Microsoft предлагает создать специальный каталог на диске, который называется «хранилищем символов». В данном каталоге создается дерево папок с названиями, совпадающими с названиями компонентов. В каждой из этих папок находятся вложенные папки, имеющие специальные названия, получаемые методом хеширования бинарных файлов. Это обеспечивает быстрый поиск необходимого компонента, по ограниченной информации о нем (например, из дампа падения). Для создания такой папки используется специальная утилита, symstore. Она сканирует папки с компонентами и добавляет новые компоненты в хранилище, откуда их может выбирать любой клиент. Кроме того, имеется возможность расширения стандартного механизма поиска файлов путем написания своей реализации протокола поиска.
Пример, приведенный ниже, был получен при разборе тестового приложения на чистой машине, где изначально символов не было. Ниже приведена структура папок, полученная на локальном диске после разбора дампа с закачкой символов из Интернета. Как видно из примера, WinDBG автоматически загрузил необходимые библиотеки для разбора (список директорий и их названия могут отличаться между компьютерами в зависимости от версий операционных систем).
Directory of c:\WINDOWS\symbols Folder MFC71D.pdb Folder ntdll.dll Folder ntdll.pdb Folder ole32.dll Folder ole32.pdb Folder user32.dll Folder user32.pdb Folder MFC71D.pdb C3F56483650F4CFC90A303850ABF082D1 MFC71D.pdb Folder ntdll.dll 411096B4b0000 ntdll.dll Folder ntdll.pdb 36515FB5D04345E491F672FA2E2878C02 ntdll.pdb Folder ole32.dll 42E5BE9313d000 ole32.dll Folder ole32.pdb 683B65B246F4418796D2EE6D4C55EB112 ole32.pdb Folder user32.dll 4226015990000 user32.dll Folder user32.pdb EE2B714D83A34C9D88027621272F83262 User32.pdb |
Программные продукты Microsoft (Visual Studio 6.0, 7.0+, WinDBG) знают формат такого каталога и позволяют удобно с ним работать. Но возникает вопрос: вы создали свое хранилище символов, положили туда компоненты, однако падение произошло глубоко внутри библиотек операционной системы, например, в kernel32.dll. У вас нет символов от операционной системы Windows, поэтому вы не можете разобрать стек падения (т.к. отсутствие символов для любой функции, фреймов стека которой присутствует в дампе, препятствует дальнейшему разбору списка вызовов), поэтому весь дамп останется не разобранным. Однако существует специальный сервер в Интернет, который представляет из себя хранилище всех символов Microsoft (или почти всех) за последние несколько лет. Стандартный протокол поиска символов работает с этим сервером. Например, удобно настроить Visual Studio так, чтобы она искала символы сначала у вас на компьютере, потом на главном сервере в компании, затем, если символы все еще не найдены, то закачивала бы их с сервера Microsoft. При этом система скачивания достаточно интеллектуальна, чтобы скопировать скачанные из Интернета символы и на центральный сервер, и на вашу личную машину.
ПРИМЕЧАНИЕ В документации написано, что такая система корректно работает только в однопользовательском режиме, но у нас за все время не было ни одной проблемы с многопользовательским режимом. При больших объемах хранилища символов, поиск по ним существенно замедляется, поэтому фирмой Microsoft была введена новая структура хранилища символов, оптимизированная под большие объемы данных. MS Visual Studio 7.0+ и WinDBG способны работать с новой структурой хранилища, однако MS Visual Studio 6.0 такого формата не знает, поэтому вам придется решить, что за формат вы будете использовать. Если вы все же хотите использовать MS Visual Studio 6.0, то вам нужно положить файл flat.txt в корень хранилища символов, тогда дерево символов будет создаваться в формате, совместимом с форматом MS Visual Studio 6.0. |
Однако, решение, требующее ручного вмешательства, чревато ошибками. Каждый раз запускать эту утилиту, да еще с кучей параметров командной строки, да еще проследить за каждым программистом в отдельности – трудоемкая задача. Нужна автоматизация этого процесса. В Интернете существует утилита «CruiseControl». Данная утилита предназначена для того, чтобы сканировать различные источники данных, в том числе папки на дисках, каталоги CVS, Subversion и т.д., и в случае изменений производить какие-либо действия. Однако она это делает для других целей (автоматизация тестирования или «continuos integration» по Фаулеру), но важно то, что ее можно использовать для собственных целей. А для нас она может выполнять сканирование архива бинарных компонентов при его изменении.
Таким образом:
Итак, каким-то образом получен дамп падения. Возникает вопрос: «Что теперь с ним делать?».
Сначала разберем его руками, чтобы понять, что из него можно узнать. Для этого запустим WinDBG, где проделаем следующие шаги:
Я написал маленькое MFC-приложение, которое по нажатию кнопки вызывает функцию CLSIDFromString с неверным указателем, что приводит к падению приложения. Код функции приведен ниже:
void Cfault_applicationDlg::OnBnClickedOk ()
{
CLSID cls;
CLSIDFromString ((LPOLESTR) 0x1234567, &cls);
}
|
Рассмотрим разобранный стек падения:
0:000> !analyze -v ******************************************************************************* * * * Exception Analysis * * * ******************************************************************************* FAULTING_IP: ole32!wCLSIDFromString+20 7750cb66 668b01 mov ax,[ecx] EXCEPTION_RECORD: ffffffff -- (.exr ffffffffffffffff) ExceptionAddress: 7750cb66 (ole32!wCLSIDFromString+0x00000020) ExceptionCode: c0000005 (Access violation) ExceptionFlags: 00000000 NumberParameters: 2 Parameter[0]: 00000000 Parameter[1]: 01234567 Attempt to read from address 01234567 DEFAULT_BUCKET_ID: APPLICATION_FAULT PROCESS_NAME: fault_application.exe ERROR_CODE: (NTSTATUS) 0xc0000005 - The instruction at "0x%08lx" referenced memory at "0x%08lx". The memory could not be "%s". READ_ADDRESS: 01234567 BUGCHECK_STR: ACCESS_VIOLATION THREAD_ATTRIBUTES: LAST_CONTROL_TRANSFER: from 00413694 to 7750cb66 STACK_TEXT: 0013f4d4 00413694 01234567 0013f5b4 0013f884 ole32!wCLSIDFromString+0x20 0013f5d4 7c23abcc 00000000 7c175dd1 7c32cf04 fault_application!Cfault_applicationDlg::OnBnClickedOk+0x34 [c:\c++\test\fault_application\fault_application\fault_applicationdlg.cpp @ 155] 0013f600 7c23b209 0013fe3c 00000001 00000000 mfc71d!_AfxDispatchCmdMsg+0x9c 0013f65c 7c235cc1 00000001 00000000 00000000 mfc71d!CCmdTarget::OnCmdMsg+0x289 0013f698 7c22ff89 00000001 00000000 00000000 mfc71d!CDialog::OnCmdMsg+0x21 0013f6fc 7c22f067 00000001 00170572 00000000 mfc71d!CWnd::OnCommand+0x169 0013f804 7c22efde 00000111 00000001 00170572 mfc71d!CWnd::OnWndMsg+0x57 0013f824 7c22c820 00000111 00000001 00170572 mfc71d!CWnd::WindowProc+0x2e 0013f89c 7c22ccfe 0013fe3c 00070682 00000111 mfc71d!AfxCallWndProc+0xe0 0013f8bc 7c29d8ea 00070682 00000111 00000001 mfc71d!AfxWndProc+0x9e 0013f8ec 77d48734 00070682 00000111 00000001 mfc71d!AfxWndProcBase+0x4a 0013f918 77d48816 7c29d8a0 00070682 00000111 user32!InternalCallWinProc+0x28 0013f980 77d4b89b 00000000 7c29d8a0 00070682 user32!UserCallWinProcCheckWow+0x150 0013f9bc 77d4b903 006acee0 006da718 00000001 user32!SendMessageWorker+0x4a5 0013f9dc 77d7fc7d 00070682 00000111 00000001 user32!SendMessageW+0x7f 0013f9f4 77d764e8 006cd008 00000000 006cd008 user32!xxxButtonNotifyParent+0x41 0013fa10 77d577de 001676cc 00000001 00000000 user32!xxxBNReleaseCapture+0xf8 0013fa94 77d6b05a 006cd008 00000202 00000000 user32!ButtonWndProcWorker+0x6d5 0013fab4 77d48734 00170572 00000202 00000000 user32!ButtonWndProcA+0x5d 0013fae0 77d48816 77d6b00e 00170572 00000202 user32!InternalCallWinProc+0x28 0013fb48 77d489cd 00000000 77d6b00e 00170572 user32!UserCallWinProcCheckWow+0x150 0013fba8 77d48a10 00163b08 00000000 0013fbdc user32!DispatchMessageWorker+0x306 0013fbb8 77d5e097 00163b08 00163b08 00163b10 user32!DispatchMessageW+0xf 0013fbdc 77d6c6ab 00070682 006cd008 0013fec4 user32!IsDialogMessageW+0x572 0013fbfc 7c2397c0 00070682 00163b08 77d4b970 user32!IsDialogMessageA+0xfd 0013fc14 7c23438c 00163b08 0013fe3c 0013fc3c mfc71d!CWnd::IsDialogMessageA+0x70 0013fc24 7c235c8d 00163b08 0013fe3c 0013fe3c mfc71d!CWnd::PreTranslateInput+0x6c 0013fc3c 7c2310d9 00163b08 0013fe3c 00070682 mfc71d!CDialog::PreTranslateMessage+0xed 0013fc50 7c23bf8d 00070682 00163b08 00164eac mfc71d!CWnd::WalkPreTranslateTree+0x89 0013fc6c 7c23ccf3 00163b08 00429ab0 0013fc8c mfc71d!AfxInternalPreTranslateMessage+0x4d 0013fc7c 7c23c001 00163b08 00429ab0 0013fcac mfc71d!CWinThread::PreTranslateMessage+0x23 0013fc8c 7c23be5f 00163b08 00164eac 001665c0 mfc71d!AfxPreTranslateMessage+0x21 0013fcac 7c23d00c 00429ab0 0013fcc4 7c23bead mfc71d!AfxInternalPumpMessage+0xdf 0013fcb8 7c23bead 00429ab0 0013fcec 7c23456f mfc71d!CWinThread::PumpMessage+0xc 0013fcc4 7c23456f 00000000 00000000 0013fe3c mfc71d!AfxPumpMessage+0x1d 0013fcec 7c236bd3 00000004 0013fec4 0013fd54 mfc71d!CWnd::RunModalLoop+0x1cf 0013fd4c 00412c51 7c915b4f 00000040 7ffda000 mfc71d!CDialog::DoModal+0x193 0013fed0 7c235a67 7c97e4c0 7c8021b5 7c802011 fault_application!Cfault_applicationApp::InitInstance+0x91 [c:\c++\test\fault_application\fault_application\fault_application.cpp @ 58] 0013fef4 0041f688 00400000 00000000 00161f2e mfc71d!AfxWinMain+0x77 0013ff0c 00416502 00400000 00000000 00161f2e fault_application!WinMain+0x18 [f:\vs70builds\3077\vc\mfcatl\ship\atlmfc\src\mfc\appmodul.cpp @ 25] 0013ffc0 7c816d4f 7c915b4f 00000040 7ffda000 fault_application!WinMainCRTStartup+0x1f2 [f:\vs70builds\3077\vc\crtbld\crt\src\crtexe.c @ 390] 0013fff0 00000000 004119c4 00000000 00000000 kernel32!BaseProcessStart+0x23 FOLLOWUP_IP: fault_application!Cfault_applicationDlg::OnBnClickedOk+34 [c:\c++\test\fault_application\fault_application\fault_applicationdlg.cpp @ 155] 00413694 3bf4 cmp esi,esp SYMBOL_STACK_INDEX: 1 FOLLOWUP_NAME: MachineOwner SYMBOL_NAME: fault_application!Cfault_applicationDlg::OnBnClickedOk+34 MODULE_NAME: fault_application IMAGE_NAME: fault_application.exe DEBUG_FLR_IMAGE_TIMESTAMP: 437a5e7b STACK_COMMAND: .ecxr ; kb BUCKET_ID: ACCESS_VIOLATION_fault_application!Cfault_applicationDlg::OnBnClickedOk+34 Followup: SloNN |
Как видите из стека – поток выполнения прошел через MFC, попал в fault_application!Cfault_applicationDlg::OnBnClickedOk, откуда был сделан вызов в ole32!wCLSIDFromString с неправильным указателем 0x1234567. Таким образом, причина падения сразу становится очевидна. Я не буду дальше углубляться в разбор стека (списки литературы приведены в конце статьи), а сосредоточусь на архитектуре системы.
Вернемся к строке «followup_owner». У WinDBG есть расширение «triage». Оно предназначено для обнаружения человека, ответственного за компонент, в котором произошло падение. Откроем файл «WinDBG\triage\triage.ini». В этом файле перечислены различные возможные фрагменты стека, а после знака равенства указывается программист, ответственный за эту строку. У вас в файле, скорее всего, находится ряд строк вида:
*SharedIntelSystemCall=ignore hal*=ignore nt!*=ignore nt!_KiCallbackReturn=ignore nt!_KiSystemService=ignore nt!_KiTrap*=ignore nt!_PopInternalError=ignore nt!CommonDispatchException=ignore nt!DbgBreakPoint*=ignore nt!DbgUserBreakPoint=ignore nt!ExpInterlockedPopEntrySListFault=ignore nt!ExpRaiseHardError=ignore nt!ExpSystemErrorHandler=ignore nt!ExpWorkerThread=ignore nt!IoAllocateMdl=ignore nt!IoBuildAsynchronousFsdRequest=ignore nt!IoFreeIrp=ignore mymodule!CoolClass=programmer1 mymodule!AlienCoolClass*=programmer2 mymodule!AlienCoolClass1::print*=programmer2 mymodule!*=senior_programmer fault_application=SloNN |
Эти строки означают следующее. Ключевое слово «ignore» означает, что этот элемент стека необходимо пропустить при поиске виноватого. Ведь если сбой произошел где-то в ntdll.dll, это же не обязательно значит, что упала Windows, скорее, это означает неправильный указатель, испорченную память и т.д. Поэтому разумно такие фреймы стека пропустить.
Ниже идут уже более осмысленные строки. В них указывается модуль, затем после знака «!» указывается класс и метод класса, включая специальные символы «*» и «?». С помощью такого синтаксиса можно разобрать стек с точностью до стекового фрейма функции. Например, строка «mymodule!AlienCoolClass1::print*» соответствует компоненту mymodule, классу AlienCoolClass1 и всем методам, начинающимся с print в этом классе. Более подробно о синтаксисе triage.ini можно прочитать в документации по WinDBG. После знака «=» идет имя программиста, ответственного за эту часть компонента.
В примере, приведенном выше, видно, что в поле Followup находится значение SloNN, которое и указано в файле triage.ini.
Таким образом, нужно сформировать этот файл так, чтобы файлы разбирались с помощью WinDBG, затем определялся виновник падения, которому затем уходило бы описание сбоя.
Теперь, я надеюсь, с серверной частью все стало понятно, перейду к клиентским библиотекам.
Итак, что нужно, чтобы получить дамп падения? Нужно написать библиотеку, подключая которую к нашим компонентам, можно будет получать дампы падений от клиентов автоматически. Нужно ли еще что-нибудь? Да. Нужно еще предусмотреть возможность, что версия Windows, куда устанавливаются компоненты, может не собрать дамп. Обычно это случается, когда версия библиотеки “dbghelp.dll” оказывается более старой, чем нужно (в Win2k и Win98). Поэтому нужно еще сделать альтернативный сборщик информации о падении. Некоторые компоненты в процессе работы, к сожалению, пишут информацию (которая впоследствии может понадобиться) в DebugOut, поэтому нужна поддержка DebugOut. Кроме того, компоненты могут писать свои отладочные сообщения в лог-файлы, которые могут помочь разобраться в причинах возникновения дампов. Поэтому файлы также нужно забрать. Возможно, еще нужно забрать файлы конфигурации приложения и части реестра приложения.
Возникает вопрос: как получить данные от клиента? Вариантов немного:
Теперь рассмотрим эти подходы:
Но есть еще одна тонкость. Часто падения приложений случаются в отделе тестерования. Было бы глупо упускать такую важную информацию. Поэтому тестеры тоже будут отсылать дампы падений. Если падение произошло в отделе тестировании вашей компании, можно сделать так, чтобы сервер выдавал тестеру сообщение с рекомендацией остановить работу и вызвать разработчика, ответственного за данную часть кода.
Кстати, если ваши программисты пишут драйвера, то можно еще внутри компании предусмотреть следующее: в политике домена перенаправить дампы падения на ваш внутренний сервер. На этом сервере Windows автоматически будет создавать папки для всех упавших драйверов (и программ) с дампами падений. А потом CruiseControl заберет их оттуда для обработки.
Во предыдущих частях показано, для чего нужны все компоненты системы разбора дампов, и приведены примерные описания шагов при создании такого решения. В этой части я приведу некоторые советы по созданию работающей системы.
Рассмотрим структуру хранилища символов подробнее. Нашим корпоративным стандартом хранения бинарных компонентов является CVS, вы можете использовать другие системы, это не критично.
После выкладывания бинарных компонентов в хранилище нужно запустить процедуру обновления хранилища символов, чтобы подключить новые/измененные файлы. Для этой цели мы используем CruiseControl.Net. Изначально CruiseControl.Net является системой «постоянной сборки», то есть он постоянно проводит сканирование хранилища исходников и осуществляет сборку компонентов. Нам нужно ровно то же самое, только без сборки.
Нужно настроить конфигурационный файл CruiseControl.Net для проверки CVS и запуска symstore с ключами обновления хранилища символов.
Конкретизирую задачи сервера разбора дампов. Он должен:
Основная обработка дампа производится в функции AnalyzeCrashDump, ее код на псевдоязыке приведен ниже:
string GetOwner (InputStream _instream, string buffer) { string line = ""; string owner = ""; while ((line = _instream.ReadLine()) != null) { if (line.StartsWith("followup:")) { owner = ParseFollowup(line); } buffer.Append(line); } return owner; } string AnalyzeCrashDump(string SymbolsPath, string CrashFilename) { string cmdline = "cdb -z \"" + CrashFilename + "\" " + "-y \"" + SymbolsPath + "\" " + "-c \"!owner;!analyze -v;.ecxr;!for_each_frame dv /i /t;q\""; string OutputBuffer = ""; Process CDB = new Process(cmdline); return GetOwner(CDB.OutputStream, ref OutputBuffer); } |
Как видите, основная обработка происходит в функции AnalyzeCrashDump, которая запускает консольную версию WinDBG - “cdb” - для разбора дампов, считывает консольный вывод и пытается получить имя «владельца» дампа. Для этого используется функция GetOwner, которая сканирует консольный вывод процесса cdb в поисках строки, начинающейся с “followup”.
Что такое “followup”? Я уже упоминал о расширении WinDBG «!owner». Оно позволяет сопоставить с определенными компонентами конкретных программистов, ответственных за их разработку. Мы решили это использовать для обработки стеков падений. Для работы расширения «!owner» нужен заранее подготовленный файл «triage.ini».
Теперь рассмотрим клиентскую библиотеку для снятия дампов и ее модификацию для внутреннего использования.
На данный момент у нас уже есть полная серверная инфраструктура для разбора дампов. Теперь необходимо написать клиентский код, который будет встраиваться в приложения.
Для этого нужны 2 компонента:
Почему их два? Представьте себе ситуацию, когда в приложении перехвачено фатальное исключение, например, переполнение стека. Да, можно работать даже при таком исключении, специальным способом подготовив себе стек или запустив новый поток. Но нужно ли это? Проще всего (и надежней) запустить отдельный процесс, который возьмет на себя всю работу по отсылке дампов, не подверженный влиянию приложения, в котором произошел сбой (кстати, в случае сервисов это особенно полезно, т.к. сервис не всегда имеет доступ на рабочий стол текущего пользователя).
Теперь по поводу снятия дампов. Я не буду углубляться в эту тему: статей об этом написано очень много. Скажу лишь, что для работы нужно установить обработчик «необработанных исключений» вызовом SetUnhandledExceptionFilter. Практически полное решение этого вопроса предоставляет Ганс Дитрих (Hans Dietrich) в своей серии статей «XCrashReport» части 1 – 4 http://www.codeproject.com/debug/XCrashReportPt1.asp.
Первое, что надо сделать – до связи с сервером нужно приостановить приложение, чтобы выполнение приложения не продолжалось до принятия решения о дальнейших действиях. Для этого создается отдельный поток, в котором будет происходить связь с сервером, а остальные потоки приложения переводятся в Suspend-режим. После получения ответа от сервера, в зависимости от текущих действий, либо возобновляется исполнение всех остановленных потоков, либо уничтожается текущий процесс. Получить список потоков можно с помощью библиотеки ToolHelp.
Новый поток отправляет на сервер дамп и получает оттуда результат разбора.
Я выделил 4 режима дальнейшей работы клиента:
К данному моменту мы построили работающее решение для разбора дампов. Но есть одна проблема. Она заключается в том, что в случае отсутствия информации о символах, некорректных символах и т.д. это решение является бесполезным. Поэтому необходимо написать код, анализирующий хранилище бинарных компонентов с целью проверки его следующим требованиям:
Теперь подробнее о каждом из требований:
После проверки компонент отправляет письмо человеку, выложившему компонент в CVS и его руководителю. Для реализации периодичной проверки используется тот же самый CruiseControl, который заносит символы в хранилище.
Ввиду того, что исходных кодов к статье не прилагается – я охотно отвечу на все ваши вопросы по e-mail – slonn1@mail.ru.
Сообщений 0 Оценка 190 Оценить |