Сообщений 0    Оценка 190        Оценить  
Система Orphus

Автоматизация разбора дампов падения приложений

Автор: Алексей Дмитриев
Источник: RSDN Magazine #4-2005
Опубликовано: 30.12.2005
Исправлено: 10.12.2016
Версия текста: 1.0
Преамбула
Как все начиналось
Терминология
Теория
Общая структура системы
Хранилище символов
Сервер разбора дампов
Библиотеки сбора дампов
Реализация
Хранилище символов
Сервер разбора дампов
Клиентские библиотеки
Требования к программным компонентам
Заключение
Использованная литература

Преамбула

В настоящее время, когда компании обращают большое внимание на качество своих продуктов, нестабильные программы становятся неконкурентоспособными на современном рынке. Многократные нагрузочные, регрессионные и 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 входят различные утилиты для облегчения процесса отладки.

Теория

Общая структура системы

Для начала нужно определиться с функциями системы обработки дампов. Система должна уметь:

  1. Принимать дампы падений (в дальнейшем просто дампы) от клиентов и производить их обработку.
  2. Принимать дампы падений от отделов тестирования и контроля качества с возможностью немедленной реакции программиста на ошибку.
  3. Проводить обработку дампов с целью поиска программиста, ответственного за компонент, вызвавший ошибку, и перенаправлять дамп ему.
  4. Заносить записи об ошибках в систему контроля ошибок (bug tracking system).
  5. Иметь возможность систематизировать обработку дампов отделом тестирования.


Таким образом, система должна состоять из следующих модулей:

  1. Хранилище символов. Хранит в себе как собственные символы от программных компонентов, так и символы внешних библиотек и приложений, в том числе входящих в состав ОС.
  2. Сервер разбора дампов. Использует в работе хранилище символов. Сервер прослушивает входящие сетевые соединения, а также периодически проверяет электронную почту на наличие новых дампов. Разбор дампов производится с помощью WinDBG и различных его расширений.
  3. Библиотека сбора дампов. Собирает дамп падения приложения и пересылает его Серверу разбора дампов. В локальном (intranet) режиме пересылает их напрямую на сервер и, в зависимости от результатов ответа сервера, реализует различные модели поведения. В удаленном (extranet) режиме собирает дамп падения и пересылает его по электронной почте.

Рассмотрим теперь все компоненты подробнее.

Хранилище символов

В момент сбора бинарного файла для него строится также и файл символов. В этом файле хранятся адреса функций, локальных и глобальных переменных, таблицы смещений (relocation table) и т.д. Для проверки совместимости между бинарным файлом и файлом символов компоновщик вносит в них GUID, которые должны быть идентичны в обоих файлах. Если они различаются, отладчики довольно часто отказываются подключать символы. Кроме того, нужно учесть, что потенциально любое изменение проекта вызывает полную несовместимость между файлов символов и компонентом. Поэтому файлы символов и бинарные файлы нужно строить одновременно.

Теперь у нас есть бинарные файлы и файлы символов. Что с ними делать?

Чтобы понять, что с ними делать, нужно рассмотреть типичный сценарий разработки приложения:

  1. Программист создает компонент.
  2. Компонент проходит тестирование.
  3. Компонент передается клиентам.
  4. Программист продолжает разработку компонента.

Если после этапа 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, где проделаем следующие шаги:

  1. Выбираем “Open Crash Dump”.
  2. Указываем путь поиска символов, например, «srv*c:\windows\symbols*http://msdl.microsoft.com/download/symbols». Данный путь означает, что будет произведен поиск символов в папке c:\windows\symbols, если там символов не будет найдено – они будут закачаны из Интернета с сайта http://msdl.microsoft.com/download/symbols и кешированы в папке c:\windows\symbols
  3. Вводим в командной строке “!analyze –v”. После этого можно увидеть стек падения, причем правильность стека прямо пропорциональна вероятности наличия правильных символов компонентов (подробнее к разбору стека я вернусь ниже).
  4. Ниже находим строчку со словами «Followup». В ней находится имя человека, ответственного за данное приложение. Сейчас в ней находится “machine owner”. Это правильно. Пока ничего не настроено, здесь все время будет возникать это значение.
  5. Что будет дальше происходить с дампом, зависит от того, что в нем содержится (решения наиболее распространенных проблем можно найти в конференции news://msnews.microsoft.com/microsoft.public.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. Кроме того, компоненты могут писать свои отладочные сообщения в лог-файлы, которые могут помочь разобраться в причинах возникновения дампов. Поэтому файлы также нужно забрать. Возможно, еще нужно забрать файлы конфигурации приложения и части реестра приложения.

Возникает вопрос: как получить данные от клиента? Вариантов немного:

  1. Отправить на свой сервер (как делает Microsoft).
  2. Попытаться отправить по почте.
  3. Сохранить на диске и попросить каким-либо образом прислать вам, можно даже самовывозом.
  4. Отправить его на сервер Microsoft, откуда потом забрать.

Теперь рассмотрим эти подходы:

  1. Свой сервер. Мы живем в России, здесь Интернет есть пока не у всех клиентов, А если и есть, то зачастую ненадлежащего качества. Поэтому отправлять дампы на свой online-сервер не самое разумное решение (по крайней мере сегодня).
  2. Электронная почта. Это более приемлемый вариант. Наша библиотека создаст письмо и предложит отправить его разработчикам.
  3. Однако иногда бывает, что и электронной почты у клиента нет. В этом случае придется сохранять все в файле, файл копировать на видное место и попросить пользователя передать его представителям разработчиков.
  4. Если ваша компания является счастливым владельцем сертификата Thawte и подписывает им все свои компоненты, то вам повезло, вы можете просто забирать дампы прямо с серверов Microsoft. Кстати, для этого делать вообще ничего не надо, так как Windows сама отправит дамп падения при обнаружении ошибки. Правда, такой съемщик дампов падения не будет выделяться на фоне других падающих программ, из-за чего клиенты вам могут просто не отправить такой дамп, приняв его за дамп другого, часто падающего приложения.

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

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

Реализация

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

Хранилище символов

Рассмотрим структуру хранилища символов подробнее. Нашим корпоративным стандартом хранения бинарных компонентов является CVS, вы можете использовать другие системы, это не критично.

После выкладывания бинарных компонентов в хранилище нужно запустить процедуру обновления хранилища символов, чтобы подключить новые/измененные файлы. Для этой цели мы используем CruiseControl.Net. Изначально CruiseControl.Net является системой «постоянной сборки», то есть он постоянно проводит сканирование хранилища исходников и осуществляет сборку компонентов. Нам нужно ровно то же самое, только без сборки.

Нужно настроить конфигурационный файл CruiseControl.Net для проверки CVS и запуска symstore с ключами обновления хранилища символов.

Сервер разбора дампов

Конкретизирую задачи сервера разбора дампов. Он должен:

  1. Принимать электронную почту по протоколу POP3. Это необходимо для приема дампов от клиентов.
  2. Принимать дампы, пришедшие через локальную сеть. При падении приложения в локальной сети, дамп падения будет отправляться сервису для немедленного разбора.
  3. Производить разбор дампов с целью получения правильного стека и выявления ответственного за падение.
  4. Заносить записи о падении в систему отслеживания ошибок (в моем случае это ClearQuest).
  5. Рассылать электронную почту. После разбора дампа нужно отправить письмо программисту, ответственному за данную часть приложения, а также его руководителю. Это нужно для того, чтобы дамп падения не затерялся и не был отложен на неопределенное время.

Основная обработка дампа производится в функции 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 компонента:

  1. Библиотека, которая встраивается в exe-файлы и перехватывает исключения.
  2. Приложение, отсылающее дампы.

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

Теперь по поводу снятия дампов. Я не буду углубляться в эту тему: статей об этом написано очень много. Скажу лишь, что для работы нужно установить обработчик «необработанных исключений» вызовом SetUnhandledExceptionFilter. Практически полное решение этого вопроса предоставляет Ганс Дитрих (Hans Dietrich) в своей серии статей «XCrashReport» части 1 – 4 http://www.codeproject.com/debug/XCrashReportPt1.asp.

Первое, что надо сделать – до связи с сервером нужно приостановить приложение, чтобы выполнение приложения не продолжалось до принятия решения о дальнейших действиях. Для этого создается отдельный поток, в котором будет происходить связь с сервером, а остальные потоки приложения переводятся в Suspend-режим. После получения ответа от сервера, в зависимости от текущих действий, либо возобновляется исполнение всех остановленных потоков, либо уничтожается текущий процесс. Получить список потоков можно с помощью библиотеки ToolHelp.

Новый поток отправляет на сервер дамп и получает оттуда результат разбора.

Я выделил 4 режима дальнейшей работы клиента:

  1. Обычный (уничтожить текущий процесс).
  2. Снять полный дамп памяти и отправить его на сервер, который перешлет уведомление о получении такого дампа программисту.
  3. Снять мини-дамп памяти приложения и отправить его по e-mail.
  4. Оставаться в приостановленном состоянии до прихода разработчика (с возможностью выхода из приостановленного состояния).

Требования к программным компонентам

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

  1. Наличие корректной отладочной информации.
  2. Наличие цифровой подписи.
  3. Ко всем бинарным компонентам должен быть подключен код разбора дампов.

Теперь подробнее о каждом из требований:

  1. В случае отсутствия отладочной информации или несоответствия отладочной информации самому компоненту дамп разобрать не получится. Конечно, если использовать для выкладывания в CVS специальные расширения Visual Studio, теоретически, все должно работать хорошо, но ошибки все равно случаются. Наиболее частой ошибкой является то, что человек собирает бинарный компонент и символы к нему, после чего копирует все это в CVS, но по каким-либо причинам решает пересобрать компонент – и выкладывает в CVS только бинарный компонент, без символов. WinDBG проверяет соответствие бинарного компонента и символов по GUID в обоих файлах. Если пересобрать бинарный компонент, GUID изменится, из-за чего разобрать дамп станет на порядок сложнее. Для проверки такого соответствия я использую утилиту chkmatch, которая проверяет бинарный файл и файл символов и выдает вердикт.
  2. Цифровая подпись. Она нужна, когда вы работаете в операционных системах, начиная с Windows XP, которые проверяют цифровые подписи при запуске приложений и очень ругаются, если подписей нет. Побочными, но не менее приятными эффектами наличия цифровых подписей являются: проверка соответствия бинарных компонентов, наличие точной даты создания компонента и возможность получать дампы падений прямо с серверов Microsoft.
  3. Необходимо обеспечить, чтобы все бинарные компоненты содержали код снятия дампов. Я думаю, что это требование понятно.

После проверки компонент отправляет письмо человеку, выложившему компонент в CVS и его руководителю. Для реализации периодичной проверки используется тот же самый CruiseControl, который заносит символы в хранилище.

Заключение

Ввиду того, что исходных кодов к статье не прилагается – я охотно отвечу на все ваши вопросы по e-mail – slonn1@mail.ru.

Использованная литература

  1. Debugging Applications for Microsoft .NET and Microsoft Windows by John Robbins. Самая лучшая книжка по отладке компонентов. Настоятельно рекомендую всем программистам. В нашей фирме она является одной из обязательных для прочтения. Кстати, настоятельно советую все книги читать в оригинале, так как наши переведенные книги зачастую содержат дикие ошибки.
  2. news://msnews.microsoft.com/microsoft.public.windbg . Конференция, посвященная работе с WinDBG. В ней разбираются вопросы, связанные не только с WinDBG, но и с различными методиками работы с дампами. В конференции участвует гуру Ivan Bruglio, уделяющий огромное внимание помощи разработчикам в работе с дампами. В общем, куча информации, куча полезных советов. Очень полезный ресурс.
  3. www.debuginfo.com. Тоже полезный ресурс. На нем есть ряд утилит и полезных замечаний для работы с символами, в частности, утилита chkmatch, которую я использую для сканирования хранилища бинарных компонентов, написана автором сайта, Олегом Стародумовым.
  4. www.codeproject.com/debug/XCrashReportPt1.asp. Серия статей Ганса Дитриха «XCrashReport» части 1 – 4.


Эта статья опубликована в журнале RSDN Magazine #4-2005. Информацию о журнале можно найти здесь
    Сообщений 0    Оценка 190        Оценить