Рихтер Дж., Назар К. - Windows via C C++. Программирование на языке Visual C++ - 2009
.pdf810 Часть V. Структурная обработка исключений
После конкатенации идентификатора процесса и описателя события WerFault.exe запускает отладчик вызовом CreateProcess с параметром bInheritHandles, установленным в TRUE, при этом процесс отладчика наследует описатель объекта-события. После этого процесс отладчика начинает работу и проверяет переданные ему через командную строку аргументы. Если задан ключ -р, отладчик подключается к процессу, заданному этим ключом, вызывая DebugActiveProcess:
BOOL DebugActiveProcess(DWORD dwProcessID);
После этого система начинает уведомлять отладчик о состоянии отлаживаемого процесса, сообщая, например, сколько в нем потоков и какие DLL спроецированы на его адресное пространство. На сбор этих данных отладчику нужно какоето время, в течение которого сбойный процесс должен находиться в режиме ожидания. Поток, обнаруживший необработанное исключение (этап 4), все еще ждет ALPC-уведомления от WerSvc (шаг 5). ALPC при этом блокирован на вызове WaitForSingleObjectEx с описателем процесса WerFault.exe, который ждет завершения своей работы. Заметьте, что здесь вместо WaitForSingleObject используется функция WaitForSingleObjectEx, которая переводит поток в состояние тревожного ожидания. Это позволяет обработать все АРС-вызовы, поставленные в очередь потока.
Заметьте, что в версиях Windows, предшествующих Vista, остальные потоки процесса не приостанавливались и могли продолжать работу даже в поврежденном контексте. В результате возникали все новые и новые необработанные исключения, приводившие процесс к летальному исходу: система просто «убивала» его без дополнительных уведомлений. Даже если другие потоки этого процесса не рухнут, их работа именит состояние приложения, которое будет зафиксировано в дампе краха. Все это осложнит выявление первопричины исключения при анализе отчета. По умолчанию в Windows Vista приостанавливаются все потоки сбойного процесса (единственное исключение — службы) и ни один из них не получит процессорного времени, пока WER не возобновит исполнение и не уведомит отладчик о необработанном исключении.
Закончив инициализацию, отладчик вновь проверяет командную строку — на этот раз он ищет ключ -е. Найдя его, отладчик считывает описатель события и вызывает SetEvent. Он может напрямую использовать этот наследуемый описатель события, поскольку процесс отладчика является дочерним по отношению к процессу, WerFault.exe, который и породил его.
По переходу события в свободное состояние (этап 11) WerFault.exe определяет, что к сбойному процессу подключился отладчик, готовый к приему уведомления об исключении. После этого WerFault.exe завершается, WerSvc обнаруживает это и позволяет продолжить работу ALPC (этап 12). Далее пробуждается поток отлаживаемого процесса, и отладчик получает информацию о необработанном исключении (этап 13). Эти операции дают тот же результат, что действия, выполняемые на третьем этапе работы функции Un-
Глава 25. Необработанные исключения, векторная обработка исключений и исключения
C++.docx 811 handledExceptionFilter. Получив сведения об исключении, отладчик загружает соответствующий файл исходного кода и переходит к команде, вызвавшей исключение (этап 14). Вот это действительно круто!
Кстати, совсем не обязательно дожидаться исключения, чтобы начать отладку. Отладчик можно подключить в любой момент командой «vsjitdebugger.exe -p PID», где PID — идентификатор отлаживаемого процесса. Task Manager еще больше упрощает эту задачу. Открыв вкладку Process, Вы можете щелкнуть строку с нужным процессом правой кнопкой мыши и выбрать из контекстного меню команду Debug. В ответ Task Manager обратится к только что рассмотренному разделу реестра и вызовет CreateProcess, передав ей идентификатор выбранного процесса. Но вместо описателя события Task Manager передаст 0.
Совет. В том же разделе HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug находится REG_SZ-параметр
Auto. Его значение указывает WER, следует ли дать пользователю выбор между завершением и отладкой сбойного процесса. Если Auto = 1, WER не дает пользователю никакого выбора и сразу же вызывает отладчик. Этот параметр рекомендуется включать на компьютере, на котором ведется разработка, чтобы не ждать вывода диалоговых окон WER, поскольку отлаживать приложение, в котором возникают необработанные исключения, вам все равно придется.
Кроме того, нет смысла отлаживать приложения-контейнеры, такие как svchost.exe, при крахе какой-нибудь из работающих в нем служб. В подобных случаях добавьте в раздел AeDebug подраздел AutoExclusionList, в котором должен быть DWORD-параметр названный по имени приложения, автоматическую отладку которого следует запретить, и значением 1. Чтобы выбрать, для каких приложений следует включить автоматическую отладку по требованию, а для каких — нет, оставьте параметру Auto значение 0 и до-
бавьте в раздел HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\Windows Error Reporting подраздел DebugApplications и в нем создайте
DWORD-параметры, названные по именам приложений, которые следует автоматически отлаживать при возникновении в них необработанных исключений; этим параметрам следует присвоить значение 1.
Программа-пример Spreadsheet
Эта программа (25-Spreadsheet.exe), демонстрирует, как передавать физическую память зарезервированному региону адресного пространства — но не всему региону, а только его областям, нужным в данный момент. Алгоритм опирается на структурную обработку исключений. Файлы исходного кода и ресурсов этой программы находятся в каталоге 25-Spreadsheet на компакт-диске, прилагаемом к книге. После запуска Spreadsheet на экране появляется диалоговое окно, показанное ниже.
814 Часть V. Структурная обработка исключений
В своих проектах я довольно часто пользуюсь виртуальной памятью и SEH. Как-то раз я решил создать шаблонный С++-класс CVMArray, который инкапсулирует все, что нужно для использования этих механизмов. Его исходный код содержится в файле VMArray.h (он является частью программы-примера Spreadsheet). Вы можете работать с классом CVMArray двумя способами. Вопервых, просто создать экземпляр этого класса, передав конструктору максимальное число элементов массива. Класс автоматически устанавливает действующий на уровне всего процесса фильтр необработанных исключений, чтобы любое обращение из любого потока к адресу в виртуальном массиве памяти заставляло фильтр вызывать VirtualAlloc (для передачи физической памяти новому элементу) и возвращать EXCEPTION_ CONTINUEEXECUTION. Такое применение класса CVMArray позволяет работать с разреженной памятью (sparse storage), не забивая SEH-фреймами исходный код программы. Единственный недостаток в том, что ваше приложение не сможет возобновить корректную работу, если по каким-то причинам передать память не удастся.
Второй способ использования CVMArray — создание производного С++- класса. Производный класс даст вам все преимущества базового класса, и, кроме того, вы сможете расширить его функциональность — например, заменив исходную виртуальную функцию OnAccessViolation собственной реализацией, более аккуратно обрабатывающей нехватку памяти. Программа Spreadsheet как раз и демонстрирует этот способ использования класса CVMArray.
Spreadsheet.cpp
/****************************************************************************** Module: Spreadsheet.cpp
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/
#include "..\CommonFiles\CmnHdr.h" /* см. приложение А */ #include <windowsx.h>
Глава 25. Необработанные исключения, векторная обработка исключений и исключения
C++.docx 815
#include <tchar.h>
#include "Resource.h" #include "VMArray.h" #include <StrSafe.h>
/////////////////////////////////////////////////////////////////////
HWND g_hWnd; // глобальный описатель окна, применяемого для SEH-отчетов const int g_nNumRows = 256;
const int g_nNumCols = 1024;
//определяем структуру ячеек таблицы typedef struct {
DWORD dwValue; BYTE bDummy[1020];
} CELL, *PCELL;
//объявляем тип данных для всей таблицы
typedef CELL SPREADSHEET[g_nNumRows][g_nNumCols]; typedef SPREADSHEET *PSPREADSHEET;
///////////////////////////////////////////////////////////////////////////////
// таблица является двухмерным массивом переменных типа CELL class CVMSpreadsheet : public CVMArray<CELL> {
public:
CVMSpreadsheet() : CVMArray<CELL>(g_nNumRows * g_nNumCols) {}
private:
LONG OnAccessViolation(PVOID pvAddrTouched, BOOL bAttemptedRead, PEXCEPTION_POINTERS pep, BOOL bRetryUntilSuccessful);
};
///////////////////////////////////////////////////////////////////////////////
LONG CVMSpreadsheet::OnAccessViolation(PVOID pvAddrTouched, BOOL bAttemptedRead,
PEXCEPTION_POINTERS pep, BOOL bRetryUntilSuccessful) {
TCHAR sz[200];
StringCchPrintf(sz, _countof(sz), TEXT("Violation: Attempting to %s"), bAttemptedRead ? TEXT("Read") : TEXT("Write"));
SetDlgItemText(g_hWnd, IDC_LOG, sz);
816 Часть V. Структурная обработка исключений
LONG lDisposition = EXCEPTION_EXECUTE_HANDLER; if (!bAttemptedRead) {
// возвращаем значение, определяемое базовым классом lDisposition = CVMArray<CELL>::OnAccessViolation(pvAddrTouched,
bAttemptedRead, pep, bRetryUntilSuccessful);
}
return(lDisposition);
}
///////////////////////////////////////////////////////////////////////////////
//это глобальный объект CVMSpreadsheet static CVMSpreadsheet g_ssObject;
//создаем глобальный указатель на весь регион, зарезервированный под
//таблицу
SPREADSHEET& g_ss = * (PSPREADSHEET) (PCELL) g_ssObject;
/////////////////////////////////////////////////////////////////////
BOOL Dlg_OnInitDialog(HWND hWnd, HWND hWndFocus, LPARAM lParam) {
chSETDLGICONS(hWnd, IDI_SPREADSHEET);
g_hWnd = hWnd; // сохраняем для SEH-отчетов
//инициализируем элементы управления в диалоговом окне значениями
//по умолчанию
Edit_LimitText(GetDlgItem(hWnd, |
IDC_ROW), |
3); |
|
Edit_LimitText(GetDlgItem(hWnd, |
IDC_COLUMN), 4); |
||
Edit_LimitText(GetDlgItem(hWnd, |
IDC_VALUE), |
7); |
|
SetDlgItemInt(hWnd, IDC_ROW, |
100, |
FALSE); |
|
SetDlgItemInt(hWnd, IDC_COLUMN, |
100, |
FALSE); |
|
SetDlgItemInt(hWnd, IDC_VALUE, |
12345, FALSE); |
||
return(TRUE); |
|
|
|
}
/////////////////////////////////////////////////////////////////////
void Dlg_OnCommand(HWND hWnd, int id, HWND hWndCtl, UINT codeNotify) {
int nRow, nCol;
Глава 25. Необработанные исключения, векторная обработка исключений и исключения
C++.docx 817
switch (id) { case IDCANCEL:
EndDialog(hWnd, id); break;
case IDC_ROW:
// пользователь изменил строку, обновляем данные в окне nRow = GetDlgItemInt(hWnd, IDC_ROW, NULL, FALSE); EnableWindow(GetDlgItem(hWnd, IDC_READCELL),
chINRANGE(0, nRow, g_nNumRows - 1)); EnableWindow(GetDlgItem(hWnd, IDC_WRITECELL),
chINRANGE(0, nRow, g_nNumRows - 1)); break;
case IDC_COLUMN:
// пользователь изменил колонку, обновляем данные в окне nCol = GetDlgItemInt(hWnd, IDC_COLUMN, NULL, FALSE); EnableWindow(GetDlgItem(hWnd, IDC_READCELL),
chINRANGE(0, nCol, g_nNumCols - 1)); EnableWindow(GetDlgItem(hWnd, IDC_WRITECELL),
chINRANGE(0, nCol, g_nNumCols - 1)); break;
case IDC_READCELL:
// пытаемся считать значение ячейки, выбранной пользователем
SetDlgItemText(g_hWnd, IDC_LOG, TEXT("No violation raised")); nRow = GetDlgItemInt(hWnd, IDC_ROW, NULL, FALSE);
nCol = GetDlgItemInt(hWnd, IDC_COLUMN, NULL, FALSE); __try {
SetDlgItemInt(hWnd, IDC_VALUE, g_ss[nRow][nCol].dwValue, FALSE);
}
__except (
g_ssObject.ExceptionFilter(GetExceptionInformation(), FALSE)) {
// ячейке не передана физическая память, и ее значение не определе-
но
SetDlgItemText(hWnd, IDC_VALUE, TEXT(""));
}
break;
case IDC_WRITECELL:
// пытаемся считать значение ячейки, выбранной пользователем
SetDlgItemText(g_hWnd, IDC_LOG, TEXT("No violation raised"));
818 Часть V. Структурная обработка исключений
nRow = GetDlgItemInt(hWnd, IDC_ROW, NULL, FALSE);
nCol = GetDlgItemInt(hWnd, IDC_COLUMN, NULL, FALSE);
//если ячейке не передана физическая память,
//возбуждается исключение, и память передается ей автоматически g_ss[nRow][nCol].dwValue =
GetDlgItemInt(hWnd, IDC_VALUE, NULL, FALSE); break;
}
}
/////////////////////////////////////////////////////////////////////
INT_PTR WINAPI Dlg_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
chHANDLE_DLGMSG(hWnd, WM_INITDIALOG, |
Dlg_OnInitDialog); |
chHANDLE_DLGMSG(hWnd, WM_COMMAND, |
Dlg_OnCommand); |
} |
|
return(FALSE); |
|
}
/////////////////////////////////////////////////////////////////////
int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR, int) {
DialogBox(hInstExe, MAKEINTRESOURCE(IDD_SPREADSHEET), NULL, Dlg_Proc); return(0);
}
///////////////////////////// End of File ///////////////////////////
VMArray.h
/****************************************************************************** Module: VMArray.h
Notices: Copyright (c) 2008 Jeffrey Richter & Christophe Nasarre
******************************************************************************/
#pragma once
///////////////////////////////////////////////////////////////////////////////
Глава 25. Необработанные исключения, векторная обработка исключений и исключения
C++.docx 819
//Примечание: этот С++-класс небезопасен в многопоточной среде.
//Несколько потоков не может одновременно создавать/уничтожать
//объекты этого класса. Однако несколько потоков может одновременно
//обращаться к уже созданным объектам класса CVMArray (для доступа
//к одному объекту вам придется самостоятельно синхронизировать потоки).
///////////////////////////////////////////////////////////////////////////////
template <class TYPE> class CVMArray { public:
//резервирует разреженную матрицу
CVMArray(DWORD dwReserveElements);
//освобождает разреженную матрицу virtual ~CVMArray();
//обеспечивает доступ к элементу массива
operator |
TYPE*() |
{ |
return(m_pArray); |
} |
operator |
const TYPE*() const { |
return(m_pArray); |
} |
|
//может вызываться для более "тонкой" обработки,
//если передать память не удалось
LONG ExceptionFilter(PEXCEPTION_POINTERS pep,
BOOL bRetryUntilSuccessful = FALSE);
protected:
//замещается для более "тонкой" обработки исключения,
//связанного с нарушением доступа
virtual LONG OnAccessViolation(PVOID pvAddrTouched, BOOL bAttemptedRead, PEXCEPTION_POINTERS pep, BOOL bRetryUntilSuccessful);
private: |
|
static CVMArray* sm_pHead; |
// адрес первого объекта |
CVMArray* m_pNext; |
// адрес следующего объекта |
TYPE* m_pArray; |
// указатель на регион, |
|
// зарезервированный под массив |
DWORD m_cbReserve; |
// Размер зарезервированного региона (в бай- |
тах) |
|
private:
// адрес предыдущего фильтра необработанных исключений
static PTOP_LEVEL_EXCEPTION_FILTER sm_pfnUnhandledExceptionFilterPrev;
