Как преобразовать код исключения Win32 в строку?

Мне неохотно приходится иметь дело с структурными исключениями Win32. Я пытаюсь создать строку, описывающую исключение. Большинство из них просты, но я зациклился на чем-то базовом: как я могу преобразовать код исключения (результат GetExceptionCode() или ExceptionCode члена EXCEPTION_RECORD) в строку, описывающую исключение?

Я ищу что-то, что преобразует, например, 0xC0000005 в "Нарушение доступа". Это просто вызов FormatMessage()?

Ответ 1

Да. Это NTSTATUS, поэтому используйте FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_FROM_HMODULE и передайте HMODULE из LoadLibrary("NTDLL.DLL")

Источник: KB259693 (в архиве)

Ответ 2

Структурированные коды исключений определяются через номера NTSTATUS. Хотя кто-то из MS предлагает, используя FormatMessage() для преобразования NTSTATUS числа в строки, я бы этого не сделал. Флаг FORMAT_MESSAGE_FROM_SYSTEM используется для преобразования результата GetLastError() в строку, поэтому здесь нет смысла. Использование флага FORMAT_MESSAGE_FROM_HMODULE вместе с ntdll.dll приведет к неправильным результатам для некоторых кодов. Например, для EXCEPTION_ACCESS_VIOLATION вы получите The instruction at 0x, что не очень информативно:).

Когда вы смотрите на строки, хранящиеся в ntdll.dll, становится очевидным, что многие из них должны использоваться с printf(), а не с FormatMessage(). Например, строка для EXCEPTION_ACCESS_VIOLATION:

The instruction at 0x%08lx referenced memory at 0x%08lx. The memory could not be %s.

%0 обрабатывается FormatMessage() в качестве escape-последовательности, означающей терминатор сообщения, а не вставку. Вкладыши составляют от 1 до 99. Поэтому флаг FORMAT_MESSAGE_IGNORE_INSERTS не имеет никакого значения.

Возможно, вы захотите загрузить строку из ntdll.dll и передать ее в vprintf, но вам нужно будет подготовить аргументы точно так же, как указано в строке (например, для EXCEPTION_ACCESS_VIOLATION it unsigned long, unsigned long, char*)), И этот подход имеет большой недостаток: любое изменение количества, порядка или размера аргументов в ntdll.dll может нарушить ваш код.

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

Ответ 3

Сложно правильно управлять форматом потока, который имеет некоторые из строк NTSTATUS. Вы должны рассмотреть возможность преобразования его в сообщение Win32 с RtlNtStatusToDosError(), который входит в заголовок Winternl.h. Вам нужно будет иметь ntdll.lib в вашем вводе компоновщика.

Пример реализации:

// Returns length of resulting string, excluding null-terminator.
// Use LocalFree() to free the buffer when it is no longer needed.
// Returns 0 upon failure, use GetLastError() to get error details.
DWORD FormatNtStatus(NTSTATUS nsCode, TCHAR **ppszMessage) {

    // Get handle to ntdll.dll.
    HMODULE hNtDll = LoadLibrary(_T("NTDLL.DLL"));

    // Check for fail, user may use GetLastError() for details.
    if (hNtDll == NULL) return 0;

    // Call FormatMessage(), note use of RtlNtStatusToDosError().
    DWORD dwRes = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE,
        hNtDll, RtlNtStatusToDosError(nsCode), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR)ppszMessage, 0, NULL);

    // Free loaded dll module and decrease its reference count.
    FreeLibrary(hNtDll);

    return dwRes;
}

Ответ 4

Я предлагаю вам использовать Bugslayer. Просто вызовите GetFaultReason с помощью EXCEPTION_POINTERS.

Кроме того, вы можете пройтись по стеку, используя GetFirstStackTraceString и GetNextStackTraceString.