如何使用 FormatMessage 函数为系统错误消息填充 va_list?
How to populate the va_list for system error messages using the FormatMessage function?
我不确定我的实现是否存在根本问题,或者 Windows API 中是否存在关于格式化系统错误消息的错误。
我有一个 Windows API 包装器方法用于 FormatMessage
函数。我的实现允许调用者传入附加参数以允许格式化系统错误消息。
方法签名如下:
bool setFormattedMessage(int arguments, ...)
在此方法中,我有以下代码块似乎不适用于我的所有单元测试:
if (arguments)
{
va_list argumentsList;
va_start(argumentsList, arguments);
size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM, NULL, code, language, (LPWSTR)&buffer, 0,
&argumentsList);
va_end(argumentsList);
}
注意:
code
和 language
是实例化列表中 class 集的成员(使用 GetLastError
和 LANGIDFROMLCID(GetThreadLocale())
.
以下是一些顺利通过的单元测试:
如果我为错误代码 34 创建 class 的实例,如果我将方法调用为 test.setFormattedMessage(0)
,我将得到以下结果(请注意,这不会进入块由于 if (arguments)
行我发布的代码):
The wrong diskette is in the drive. Insert %2 (Volume Serial Number: %3) into drive %1.`
如果我将方法调用为 test.setFormattedMessage(3, L"C:", L"disk 2", L"ABC123")
,我会得到以下结果:
The wrong diskette is in the drive. Insert disk 2 (Volume Serial Number: ABC123) into drive C:.;
但是,对于包含 %hs
或 %08lx
等示例的错误消息,我 运行 尝试格式化文本时出现问题。
以错误代码 573 为例。当将参数 0 传递到方法中时,我得到以下文本(如预期的那样):
{Missing System File} The required system file %hs is bad or missing.
但是,如果我传入 (1, "test")
,我最终会得到:
{Missing System File} The required system file hs is bad or missing.
事实上,我发现无论我是否传入 "test"
、L"test"
、char
、wchar_t
等的副本,我总是以上面的错误信息。创建的消息与传递 (0)
相同,只是 %
从字符串中删除。
我发现所有其他参数都存在同样的问题,除非它们被编号(例如 %1
)。关于我是否犯了基本错误或者 FormatMessage
函数确实存在缺陷,我完全陷入了这一点。
我的印象是我可以像 swprintf
:
一样传递 %hs
占位符的替代品
char * t = "file 123";
wchar_t a[2000];
int v = swprintf(a, 2000, L"Blah blah %hs", t);
std::wstring someText(a, v);
Blah blah file 123
根据 David Heffernan 的说明(请参阅原始问题中的评论)可以确认 FormatMessage
函数不支持替换系统错误消息占位符的功能,除非它们已编号(%1
).因此,%hs
等占位符将变为 hs
.
请注意,以上内容仅在您未使用 FORMAT_MESSAGE_IGNORE_INSERTS
标志时适用。
我已经使用 link:
检查了系统错误消息
https://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx
此处列出的错误消息似乎没有混合占位符样式(错误消息将使用带编号的占位符或使用其他格式)。
对于编号占位符,可以继续使用原始问题中描述的功能。对于使用替代样式的错误消息,可以使用 vswprintf
,但只能在使用 FORMAT_MESSAGE_IGNORE_INSERTS
标志调用 FormatMessage
函数之后(在填充的缓冲区上调用 vswprintf
,并记得重新初始化 va_list).
在支持国际化的同时,整体方法可能存在弱点。不同语言的占位符顺序可能不同。
如果您 确实 需要调用 FormatMessage
来检索系统错误消息而不使用 FORMAT_MESSAGE_IGNORE_INSERTS
标志,则工作量会大大增加。除了上面描述的内容之外,如果您的 va_list 与占位符的数量(可能还有类型)不匹配,您也可能会遇到 C0000005 异常。
如果您阅读 FormatMessage()
documentation,它明确指出:
Security Remarks
If this function is called without FORMAT_MESSAGE_IGNORE_INSERTS, the Arguments parameter must contain enough parameters to satisfy all insertion sequences in the message string, and they must be of the correct type. Therefore, do not use untrusted or unknown message strings with inserts enabled because they can contain more insertion sequences than Arguments provides, or those that may be of the wrong type. In particular, it is unsafe to take an arbitrary system error code returned from an API and use FORMAT_MESSAGE_FROM_SYSTEM without FORMAT_MESSAGE_IGNORE_INSERTS.
您的代码使用了 FORMAT_MESSAGE_FROM_SYSTEM
而没有使用 FORMAT_MESSAGE_IGNORE_INSERTS
,这是不安全的。
因此,除非您绝对确定您知道任何给定系统错误代码的确切格式,并且您的代码已经过检查在决定传入哪些格式参数之前获取该错误代码,并且您绝对确定 Microsoft 永远不会在 OS 版本之间更改该错误消息的格式,那么您只需不能安全地使用带有系统错误的格式化参数,所以不要尝试。只有当您通过 FORMAT_MESSAGE_FROM_HMODULE
使用您自己的错误消息来访问您自己的 EXE/DLL 模块资源,或 FORMAT_MESSAGE_FROM_STRING
在内存中提供您自己的错误消息字符串时,格式化参数才可以安全使用。
我不确定我的实现是否存在根本问题,或者 Windows API 中是否存在关于格式化系统错误消息的错误。
我有一个 Windows API 包装器方法用于 FormatMessage
函数。我的实现允许调用者传入附加参数以允许格式化系统错误消息。
方法签名如下:
bool setFormattedMessage(int arguments, ...)
在此方法中,我有以下代码块似乎不适用于我的所有单元测试:
if (arguments)
{
va_list argumentsList;
va_start(argumentsList, arguments);
size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM, NULL, code, language, (LPWSTR)&buffer, 0,
&argumentsList);
va_end(argumentsList);
}
注意:
code
和 language
是实例化列表中 class 集的成员(使用 GetLastError
和 LANGIDFROMLCID(GetThreadLocale())
.
以下是一些顺利通过的单元测试:
如果我为错误代码 34 创建 class 的实例,如果我将方法调用为 test.setFormattedMessage(0)
,我将得到以下结果(请注意,这不会进入块由于 if (arguments)
行我发布的代码):
The wrong diskette is in the drive. Insert %2 (Volume Serial Number: %3) into drive %1.`
如果我将方法调用为 test.setFormattedMessage(3, L"C:", L"disk 2", L"ABC123")
,我会得到以下结果:
The wrong diskette is in the drive. Insert disk 2 (Volume Serial Number: ABC123) into drive C:.;
但是,对于包含 %hs
或 %08lx
等示例的错误消息,我 运行 尝试格式化文本时出现问题。
以错误代码 573 为例。当将参数 0 传递到方法中时,我得到以下文本(如预期的那样):
{Missing System File} The required system file %hs is bad or missing.
但是,如果我传入 (1, "test")
,我最终会得到:
{Missing System File} The required system file hs is bad or missing.
事实上,我发现无论我是否传入 "test"
、L"test"
、char
、wchar_t
等的副本,我总是以上面的错误信息。创建的消息与传递 (0)
相同,只是 %
从字符串中删除。
我发现所有其他参数都存在同样的问题,除非它们被编号(例如 %1
)。关于我是否犯了基本错误或者 FormatMessage
函数确实存在缺陷,我完全陷入了这一点。
我的印象是我可以像 swprintf
:
%hs
占位符的替代品
char * t = "file 123";
wchar_t a[2000];
int v = swprintf(a, 2000, L"Blah blah %hs", t);
std::wstring someText(a, v);
Blah blah file 123
根据 David Heffernan 的说明(请参阅原始问题中的评论)可以确认 FormatMessage
函数不支持替换系统错误消息占位符的功能,除非它们已编号(%1
).因此,%hs
等占位符将变为 hs
.
请注意,以上内容仅在您未使用 FORMAT_MESSAGE_IGNORE_INSERTS
标志时适用。
我已经使用 link:
检查了系统错误消息https://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx
此处列出的错误消息似乎没有混合占位符样式(错误消息将使用带编号的占位符或使用其他格式)。
对于编号占位符,可以继续使用原始问题中描述的功能。对于使用替代样式的错误消息,可以使用 vswprintf
,但只能在使用 FORMAT_MESSAGE_IGNORE_INSERTS
标志调用 FormatMessage
函数之后(在填充的缓冲区上调用 vswprintf
,并记得重新初始化 va_list).
在支持国际化的同时,整体方法可能存在弱点。不同语言的占位符顺序可能不同。
如果您 确实 需要调用 FormatMessage
来检索系统错误消息而不使用 FORMAT_MESSAGE_IGNORE_INSERTS
标志,则工作量会大大增加。除了上面描述的内容之外,如果您的 va_list 与占位符的数量(可能还有类型)不匹配,您也可能会遇到 C0000005 异常。
如果您阅读 FormatMessage()
documentation,它明确指出:
Security Remarks
If this function is called without FORMAT_MESSAGE_IGNORE_INSERTS, the Arguments parameter must contain enough parameters to satisfy all insertion sequences in the message string, and they must be of the correct type. Therefore, do not use untrusted or unknown message strings with inserts enabled because they can contain more insertion sequences than Arguments provides, or those that may be of the wrong type. In particular, it is unsafe to take an arbitrary system error code returned from an API and use FORMAT_MESSAGE_FROM_SYSTEM without FORMAT_MESSAGE_IGNORE_INSERTS.
您的代码使用了 FORMAT_MESSAGE_FROM_SYSTEM
而没有使用 FORMAT_MESSAGE_IGNORE_INSERTS
,这是不安全的。
因此,除非您绝对确定您知道任何给定系统错误代码的确切格式,并且您的代码已经过检查在决定传入哪些格式参数之前获取该错误代码,并且您绝对确定 Microsoft 永远不会在 OS 版本之间更改该错误消息的格式,那么您只需不能安全地使用带有系统错误的格式化参数,所以不要尝试。只有当您通过 FORMAT_MESSAGE_FROM_HMODULE
使用您自己的错误消息来访问您自己的 EXE/DLL 模块资源,或 FORMAT_MESSAGE_FROM_STRING
在内存中提供您自己的错误消息字符串时,格式化参数才可以安全使用。