Кросспост из моего блога http://the-gr8.cih.ms/2010/01/blog-post_7649.html Всем знакомое назойливое окошко позволяет вызвать just-in-time отладчик, но самое обидное, что этот отладчик может быть только один единственный. А мне вот захотелось иметь возможность выбора между отладчиками - запустить олли, WinDbg или же создать минидамп с помощью ntsd. Список отладчиков хранится в подключах HKLM\Software\Microsoft\Windows NT\AeDebug в виде подключей "0", "1", "2", ... default value в них - путь к отладчику, а значение Name - отображаемое имя. Добавить новый можно с помощью regedit. По умолчанию выбирается отладчик "0", имейте в виду, когда будете составлять список. Список параметров (мини-хелп): * -i установка. Старый отладчик ставится в список нулевым HKLM\Software\Microsoft\Windows NT\AeDebug\0 с названием "Default" (если оно свободно, если занято, то ничего не меняется - на случай установки поверх уже установленного). Так же это можно юзать, если какой-то другой отладчик "занял" место jitmgr, тогда он восстановит себя в качестве jit отладчика. Параметр Auto меняется на 1 - окно dwwin более не отображается (его заменяет окошко jitmgr с расширенной информацией и выбором отладчика). * -u uninstall. Нулевой отладчик из списка ставится на место jitmgr. * %ld %ld параметры для вызова как jit. Первый - Process ID, второй - Event Handle. Так же может быть третий параметр - --log. В этом случае jitmgr логирует свой собственный отладочный вывод DbgPrint в jitmgr.log в корень системного диска. Рекомендуется установить этот флажок - jitmgr иногда лажает с определением EXCEPTION_RECORD. Эти файлы в случае фейла jitmgr (покажет messagbox) прошу присылать мне: Отдельного внимания заслуживает поиск EXCEPTION_RECORD* указателя в рухнувшем процессе. Для этого вкратце опишу механизм работы стандартного UnhandledExceptionDispatcher: * если параметр Auto==0, то подгружается faultrep.dll и вызывается ее ф-я ReportFault * ReportFault создает секцию с информацией об исключении и запускает dwwin.exe -x -s SectionHandle, который отобржает окно "xxx has encountered a probled and needs to close" с кнопками Debug и Close (кнопка Send у меня отключена . * при нажатии Debug ReportFault() возвращает определенное значение, по которому UnhandledExceptionFilter запускает процесс по пути из реестра параметр AeDebug\Debugger, передавая в параметры PID и хендл евента, который отладчик должен сигнальнуть по окончании (видимо, для корректного завершения процесса). * UnhandledExceptionFilter делает ZwWaitForMultipleObject() на объектах процесса отладчика (на случай, если он завершится, так и не сигнальнув евент) и на евенте. По причине ожидания на процессе отладчика, jitmgr не завершается, пока не отработает запускаемый отладчик, иначе завершение jitmgr потенциально может убить рухнувший процесс, если отладчик решит его разморозить. Впрочем, он тогда и евент отсигналит, но в любом случае надо повисеть, пока дочерний отладчик не приаттачится, иначе он не успеет за завершающимся процессом. Поскольку UnhandledExceptionFilter thread-safe функция, во время ожидания на евенте и процессе отладчика в других потоках могут возникнуть каскадные исключения, являющимися следствиями первого или имеющими общую причину. Поэтому даже нажатие на Close в окне dwwin не всегда завершает процесс (многие замечали?). Вероятно, он просто сигналит евент и завершается. Для предотвращения этого у меня в jitmgr а) на время работы jitmgr оборачивается в ZwSuspendProcess/ZwResumeProcess, замораживая процесс и б) есть кнопка "Kill Process", которая насильно убивает процесс сразу же. Алгоритм поиска EXCEPTION_RECORD: * перебрать все потоки, найти потоки, у которых State == THREAD_STATE_WAIT * для каждого потока в состоянии ожидания получить контекст, прочитать содержимое стека по адресу Context.Ebp, которое в случае вызова из UnhandledExceptionFilter должно иметь следующую раскладку (подчеркиваю - содержимое по EBP. По ESP будет адрес возврата в ZwWaitForMultipleObjects): 1. (ebp+0) old EBP 2. (ebp+4) адрес возврата из UnhandledExceptionFilter в фильтр исключений SEH (блок __try/__except) функции BaseThreadStart или BaseProcessStart 3. (ebp+8) указатель на EXCEPTION_POINTERS - первый параметр UEF. * найти базу kernel32.dll в дочернем процессе через раскрутку SEH: я никогда не видел Address Space Layout Randomization (ASLR), поэтому предположил, что это сработает * проверить, что *(ebp+4) принадлежит кернелу (еще можно проверить, что *esp - адрес возврата в ZwWaitForMultipleObjects действительно указывает туда, но это, имхо, уже лишнее; также можно проверить, что *ebp и *(ebp+8) принадлежат стеку этого потока) * проверить, что по адресу *(ebp+4)-5 действительно расположен call UnhandledExceptionFilter * в случае успеха всех условий, прочитать **(ebp+8) - указатель на EXCEPTION_RECORD Замечания и критика приветствуются. Выводится инфа в OutputDebugString (смотреть в DbgView), в случае ошибки поиска EXCEPTION_RECORD инфа дублируется в jitmgr.log - прошу высылать этот файл, если будет такая ошибка. P.S. Раньше была кнопочка Minidump (между Debug и KillProcess), но была выпилена по причине, что проще добавить ntsd явно в список отладчиков. P.P.S. Была мысля добавить автодобавление нового отладчика при указании пути к exe и выборе типа отладчика (чтобы автоматом дописывались параметры для известных отладчиков). Исходники и EXE в аттаче
В догонку еще один кросспост из моего блога насчет отладчика ntsd и генерирования дампов: Все слышали про kd/windbg? Думаю, многие. Но немногие знают, что есть так же и хороший аналогичный отладчик ntsd (NT Symbolic Debugger) для юзермодных программ. Старая версия его даже входит в Windows по умолчанию (%systemroot%\system32\ntsd.exe). Нормальная версия поставляется в Debugging Tools for Windows вместе с kd/WinDbg. Интерфейс - мощный текстовый, как у kd. Команды аналогичны kd при работе в юзер моде. Одна из полезных команд - !analyze -v (как для ядра), делает анализ исключения. Вместе с возможностью аттачиться к процессам в режиме just-in-time и генерацией минидампа - весьма заманчиво. Отладчик поддерживает скрипты, что делает его еще более заманчивым. Есть так же поддержка отладки по сети (ключ -remote). Примеры: * ntsd -p PID - Аттачнуться к процессу по его PID. Так же можно добавить параметр -g (Auto 'Go' - автоматически выполнит Go, не брякаясь в процессе). * ntsd -p PID -e EVENTHANDLE - используется для Just-In-Time отладки, второй параметр - event handle, который нужно сигналить, чтобы "разморозить" упавший процесс, который ждет отладчика (там выполняется ZwWaitForMultipleObjects на хендлах евента и процесса отладчика). * Опция -с позволяет написать список команд для выполнения при старте. Например -c ".dump C:\Dumps\jit.dmp;q" сгенерирует минидамп по указанному пути и завершит отладчик. Можно использовать в just-in-time вместе с опцией -noio, которая подавляет показ консольного окна. В саму .dump можно добавить флаг /u, который сделает имя дампа уникальным в виде jit_046C_2010-01-06_03-15-26-921_06D8.dmp и /ma, которая будет генерировать расширенный дамп. Весьма полезно для тех случаев, когда комп тестится на машине тестера и нужно узнать инфу об исключении. * Ключики как в kd/WinDbg: -y указывает путь к символам, -z путь к крешдампу, который надо загрузить. Just-In-Time Как я уже говорил, его можно прописать в just-in-time отладку, чтобы упавшим программам генерился минидамп, или, например, выполнялся определенный скрипт - ограничения навязаны только полетом фантазии. Для этого надо прописать примерно следующее в HKLM\Software\Microsoft\Windows NT\CurrentVersion\AeDebug\Debugger: ntsd -p %ld -e %ld -g -noio -c ".dump /u /ma C:\jit.dmp;q" Можно и не делать это вручную, в JIT его прописывает свитч -iae. Поскольку хочется иметь одновременно еще и "гуйный" отладчик, например, Olly, нужно иметь возможность выбора JIT-Отладчика. Об этом мой следующий пост (про мою софтинку jitmgr). http://www.opferman.net/Text/ntsd.txt - некоторая инфа про отладку в нем.
n0name fault.exe делает mov dword [12345678], ABCDEF01 диалог просто сообщает, что она выполнила некорректную операцию. как окно dwwin, только информативнее и с возможностью выбора отладчика. Добавил картинку для сравнения (начало 1 поста)
Обновил софтинку. Changelog for v0.2: * изменен алгоритм поиска EXCEPTION_RECORD. Тут сказали, что в случае ASLR база длл в каждом процессе одинакова + я убрал проверку, что адрес возврата из UEF лежит в kernel32 - он может лежать в любом месте, поскольку можно установить сех и в обработчике вызвать UnhandledExceptionFilter - все равно будет запущен dwwin. Поэтому убрана эта проверка и добавлена проверка, что адрес возврата *esp == ZwWaitForMultipleObjects+0x0c (UEF вызывает ZwWaitForMultipleObjects для ожидания на объектах процесса отладчика и синхронизируещего евента). Код (Text): HMODULE hNtdll = GetModuleHandle ("ntdll.dll"); PVOID pZwWaitForMultipleObjects = GetProcAddress (hNtdll, "ZwWaitForMultipleObjects"); // Zw service has the following code: // [ZwXXX+00] MOV EAX, (ServiceNumber) // [ZwXXX+05] MOV EDX, 7FFE0300 ; ServiceCall // [ZwXXX+0A] CALL DWORD PTR DS:[EDX] // [ZwXXX+0C] RETN (argumentSize) // ReturnAddress should be ZwWaitForMultipleObjects+0c (address of RETN) ULONG_PTR ReturnAddress = 0; ReadProcessMemory (hProcess, (LPCVOID)ctx.Esp, &ReturnAddress, sizeof(ULONG_PTR), &BytesRead); if (ReturnAddress - 0x0c == (ULONG_PTR)pZwWaitForMultipleObjects) * поправлена небольшая ошибка с ведением лога