如何将Win32异常代码转换为字符串?

16

我不情愿地又得处理Win32结构化异常了。我试图生成一个描述异常的字符串。大部分都很简单,但是我困在一个基本的问题上:我如何将异常代码(GetExceptionCode()的结果或EXCEPTION_RECORDExceptionCode成员)转换为描述异常的字符串?

我正在寻找一种将例如0xC0000005转换为“访问冲突”的方法。只需要调用FormatMessage()吗?


是的,FormatMessage 应该可以解决问题。 - avakar
4个回答

16
结构化异常代码是通过NTSTATUS号码来定义的。虽然有人建议使用这里(该文章已移至这里)使用FormatMessage()将NTSTATUS号码转换为字符串,但我不会这样做。标志FORMAT_MESSAGE_FROM_SYSTEM用于将GetLastError()的结果转换为字符串,所以在这里没有意义。使用标志FORMAT_MESSAGE_FROM_HMODULEntdll.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.

FormatMessage()%0 视为转义序列,表示消息终止符,而不是插入项。插入项为 %1 至 %99。这就是为什么标志 FORMAT_MESSAGE_IGNORE_INSERTS 不起作用的原因。

你可能想要从 ntdll.dll 加载字符串,并将其传递给 vprintf(),但你需要按照字符串指定的参数准备参数(例如对于 EXCEPTION_ACCESS_VIOLATION,它是 unsigned longunsigned longchar*)。而这种方法有一个主要弱点:在 ntdll.dll 中参数的数量、顺序或大小的任何更改都可能导致代码出错。
所以更安全、更容易的做法是将字符串硬编码到自己的代码中。我发现使用其他人准备的字符串而没有与我协调是很危险的 :),特别是用于其他函数。这只是出现故障的更多可能性之一。

谢谢你的回答!(尽管问题现在已经很老了。)如果你看一下我对另一个答案的评论,你会发现我发现了你提到的问题。我只是特别处理了有问题的消息;我认为我更喜欢这样做,而不是像你建议的那样硬编码所有内容。(特别是因为将来可能会添加更多的错误。) - Alan Stokes
当然这是个人口味问题。我选择打印代码编号(如您所述,这对于可能出现的新错误很有用),它的字符串表示形式(例如"EXCEPTION_INVALID_DISPOSITION")和来自EXCEPTION_RECORD结构的其他值。对我的需求而言这已经足够了。我认为向终端用户展示冗余的描述没有意义。他们中大部分人都难以理解,即使对于高级用户也没有什么用处,他们无法修复您的程序。终端用户应该将此信息传递给开发人员进行调查。作为开发人员,我可以在互联网上阅读最新的错误码描述。 - 4LegsDrivenCat

11

是的。这是一个NTSTATUS,所以请使用FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE,并将来自LoadLibrary("NTDLL.DLL")HMODULE传递进去。

来源:KB259693(已存档)


1
谢谢,那差不多可以了。不幸的是,NTDLL.DLL中的字符串似乎没有使用正确的格式代码来进行FormatMessage。我认为0xc0000005的字符串是'The instruction at %p referenced memory at %p.',但是FormatMessage将其转换为'The instruction at "0x' (sic)。另请参阅这个相关问题 - Alan Stokes
2
请查看我的回答,我解释了为什么不能使用FormatMessage来实现这个目的。 - 4LegsDrivenCat
1
@4Legs,“不能”太绝对了。我做了,处理了一些特殊的麻烦情况后,它运行得很好。 - Alan Stokes
1
同意,可以重新表述一下。即使添加一个支持您选择的参考文献,我仍然认为这不是一个好主意 :) - 4LegsDrivenCat
2
来源不存在。 - Armali
显示剩余2条评论

5

正确管理一些NTSTATUS字符串的流格式比较复杂。您应该考虑使用RtlNtStatusToDosError()将其转换为Win32消息,该函数在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;
}

当给出0xC0000005时,它会做什么? - Alan Stokes
@AlanStokes 它将被转换为ERROR_NOACCESS(在英语本地化中:“内存位置访问无效”)。 - Laa Laa
需要:FARPROC RtlNtStatusToDosError; RtlNtStatusToDosError = GetProcAddress(hNtDll,"RtlNtStatusToDosError"); - trindflo

-2

我建议你使用bugslayer。只需使用EXCEPTION_POINTERS调用GetFaultReason

此外,你还可以使用GetFirstStackTraceStringGetNextStackTraceString来遍历堆栈。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接