How do I convert a Win32 exception code to a string?
Asked Answered
H

4

16

I'm reluctantly having to deal with Win32 structured exceptions again. I'm trying to generate a string describing an exception. Most of it is straightforward, but I'm stuck on something basic: how can I convert an exception code (the result of GetExceptionCode(), or the ExceptionCode member of an EXCEPTION_RECORD) into a string describing the exception?

I'm looking for something that will convert eg 0xC0000005 to "Access Violation". Is it just a call to FormatMessage()?

Handshake answered 27/10, 2011 at 11:27 Comment(1)
Yes, FormatMessage should do the trick.Avisavitaminosis
T
11

Yes. It's a NTSTATUS, so use FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_FROM_HMODULE, and pass the HMODULE from LoadLibrary("NTDLL.DLL")

Source: KB259693 (archived)

Th answered 27/10, 2011 at 11:39 Comment(7)
Thanks, that very nearly works. Unfortunately the strings in NTDLL.DLL don't seem to use the correct format codes for FormatMessage. The string for 0xc0000005 is 'The instruction at %p referenced memory at %p.', I think, which FormatMessage converts to 'The instruction at "0x' (sic). See also this related question.Handshake
Eh, you did pass in the actual addresses? FormatMessage sees two %p parameters, and the output looks like it failed to format the first address.Th
I've tried both passing in the actual address and passing in FORMAT_MESSAGE_IGNORE_INSERTS, but it makes no difference. I've resorted to special-casing access violation (and IN_PAGE_ERROR).Handshake
Please see my answer where I explained why FormatMessage cannot be used for this purpose.Intoxicating
@4Legs "cannot" is too strong. I did it and it worked well, after handling the troublesome cases specially.Handshake
Agree, rephrased. And even added a reference that supports your choice, although I still believe it's not good idea :)Intoxicating
The Source doesn't exist.Wroth
I
16

Structured exception codes are defined through NTSTATUS numbers. Although someone from MS suggests here (the article has been moved to here) using FormatMessage() to convert NTSTATUS numbers to strings, I would not do this. Flag FORMAT_MESSAGE_FROM_SYSTEM is used to convert result of GetLastError() into a string, so it makes no sense here. Using flag FORMAT_MESSAGE_FROM_HMODULE along with ntdll.dll will lead to incorrect results for some codes. E.g., for EXCEPTION_ACCESS_VIOLATION you will get The instruction at 0x, which is not very informative :) .

When you look at the strings that are stored in ntdll.dll it becomes obvious that many of them are supposed to be used with the printf() function, not with the FormatMessage(). For example, the string for EXCEPTION_ACCESS_VIOLATION is:

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

%0 is treated by FormatMessage() as the escape sequence meaning message terminator, not an insert. Inserts are %1 to %99. That's why flag FORMAT_MESSAGE_IGNORE_INSERTS does not make any difference.

You might want to load the string from ntdll.dll and pass it to vprintf() but you will need to prepare arguments exactly as the string specifies (e.g. for EXCEPTION_ACCESS_VIOLATION it's unsigned long, unsigned long, char*). And this approach has major drawback: any change in the number, order or size of arguments in ntdll.dll may break your code.

So it's safer and easier to hard code the strings into your own code. I find it dangerous to use strings prepared by someone else without coordination with me :) and moreover for other function. This is just one more possibility for malfunction.

Intoxicating answered 9/10, 2015 at 18:27 Comment(2)
Thanks for your answer! (Even though the question is now quite old.) If you look at my comments on the other answer you'll see I discovered the problems you mention. I just special-cased the troublesome messages; I think I prefer that to hard coding everything as you suggest. (Especially since more errors may be added in the future.)Handshake
It's a matter of taste of course. I chose to print code number (useful for possible new errors as you noted), its string representation (e.g. "EXCEPTION_INVALID_DISPOSITION") and other values from EXCEPTION_RECORD struct. For my needs it's enough. I don't think it makes sense to display verbose description to end users. Most of them will hardly understand it anyway. And even for advanced ones it will not be useful, they will not fix your program. End-user should just pass that info to developers for investigation. And I as a developer can read up-to-date description of error code in Internet.Intoxicating
T
11

Yes. It's a NTSTATUS, so use FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_FROM_HMODULE, and pass the HMODULE from LoadLibrary("NTDLL.DLL")

Source: KB259693 (archived)

Th answered 27/10, 2011 at 11:39 Comment(7)
Thanks, that very nearly works. Unfortunately the strings in NTDLL.DLL don't seem to use the correct format codes for FormatMessage. The string for 0xc0000005 is 'The instruction at %p referenced memory at %p.', I think, which FormatMessage converts to 'The instruction at "0x' (sic). See also this related question.Handshake
Eh, you did pass in the actual addresses? FormatMessage sees two %p parameters, and the output looks like it failed to format the first address.Th
I've tried both passing in the actual address and passing in FORMAT_MESSAGE_IGNORE_INSERTS, but it makes no difference. I've resorted to special-casing access violation (and IN_PAGE_ERROR).Handshake
Please see my answer where I explained why FormatMessage cannot be used for this purpose.Intoxicating
@4Legs "cannot" is too strong. I did it and it worked well, after handling the troublesome cases specially.Handshake
Agree, rephrased. And even added a reference that supports your choice, although I still believe it's not good idea :)Intoxicating
The Source doesn't exist.Wroth
R
5

It is complicated to correctly manage the stream format some of the NTSTATUS strings have. You should consider converting it into a Win32 message with RtlNtStatusToDosError(), which comes in header Winternl.h. You'll need to have ntdll.lib in your linker input.

Example implementation:

// 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;
}
Rossman answered 14/5, 2017 at 6:47 Comment(4)
What does that do when given 0xC0000005?Handshake
@AlanStokes It will be converted into ERROR_NOACCESS (In English localization: "Invalid access to memory location.").Rossman
Needs: FARPROC RtlNtStatusToDosError; RtlNtStatusToDosError = GetProcAddress(hNtDll, "RtlNtStatusToDosError");Ehlers
There's also the win32 API function LsaNtStatusToWinError which converts NTSTATUS codes to win32 error ones.Aloysia
E
-2

I suggest that you use bugslayer. Just call GetFaultReason with the EXCEPTION_POINTERS.

Additionally you could walk the stack using GetFirstStackTraceString and GetNextStackTraceString.

Ever answered 27/4, 2019 at 14:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.