【问题标题】:How to populate the va_list for system error messages using the FormatMessage function?如何使用 FormatMessage 函数填充系统错误消息的 va_list?
【发布时间】:2015-05-02 23:50:47
【问题描述】:

我不确定我的实现中是否存在基本问题,或者在格式化系统错误消息方面存在 Windows API 错误。

我有一个 FormatMessage 函数的 Windows API 包装方法。我的实现允许调用者传入额外的参数以允许格式化系统错误消息。

方法签名如下:

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);
}

注意: codelanguage 是实例化列表中设置的类的成员(使用 GetLastErrorLANGIDFROMLCID(GetThreadLocale())

以下是一些没有任何问题通过的单元测试:

如果我为错误代码 34 创建类的实例,如果我将方法调用为 test.setFormattedMessage(0),我将得到以下信息(请注意,由于 @,这不会进入我发布的代码块中987654330@线):

驱动器中有错误的软盘。将 %2(卷序列号:%3)插入驱动器 %1。`

如果我将该方法称为test.setFormattedMessage(3, L"C:", L"disk 2", L"ABC123"),我会得到以下信息:

驱动器中有错误的软盘。将磁盘 2(卷序列号:ABC123)插入驱动器 C:.;

但是,对于包含 %hs%08lx 等示例的错误消息,我在尝试格式化文本时遇到了问题。

以错误代码 573 为例。当将参数 0 传递给方法时,我得到以下文本(如预期的那样):

{Missing System File} 所需的系统文件 %hs 损坏或丢失。

但是,如果我传入(1, "test"),我最终会得到:

{Missing System File} 所需的系统文件 hs 损坏或丢失。

其实我发现不管我传入"test"L"test",复制charwchar_t等等,我总是以上面的错误信息结束。创建的消息与传递(0) 相同,只是从字符串中删除了%

我发现所有其他参数都有相同的问题,除非它们被编号(例如%1)。在这一点上,我完全不知道我是否犯了根本性错误,或者FormatMessage 函数确实存在缺陷。

我的印象是我可以传递一个替换 %hs 占位符,就像我为 swprintf 做的一样:

char * t = "file 123";
wchar_t a[2000];
int v = swprintf(a, 2000, L"Blah blah %hs", t);
std::wstring someText(a, v);

废话档案 123

【问题讨论】:

  • 我没有看到任何证据表明%1%2 等之外的任何内容都将被视为格式字符串。文档在哪里说它会?我认为你在这里的使命基本上是徒劳的。
  • @DavidHeffernan - msdn.microsoft.com/en-us/library/windows/desktop/… - 查找 %n!format string! - 我从这些备注/注释中假设我可以传入整数、“短字符样式”(%hs) 字符串等。
  • 我根本不会这样读。 %hs 不符合要求。现在,%1!hs! 可能。
  • 关于不支持%hs 的文档似乎没有明确说明。例如,在我提到的块之后,它引用了“百分号字符之后的任何其他非数字字符在输出消息中被格式化,没有百分号字符。以下是一些示例......”。我的观点的关键部分“......是一些例子”。
  • 我没有看到任何证据表明%hs 会被格式化为hs 以外的任何内容。这就是文档对我说的。

标签: c++ windows winapi visual-studio-2013 error-handling


【解决方案1】:

如果您阅读FormatMessage() documentation,它特别指出:

安全备注

如果在没有 FORMAT_MESSAGE_IGNORE_INSERTS 的情况下调用此函数,则 Arguments 参数必须包含足够的参数以满足消息字符串中的所有插入序列,并且它们必须是正确的类型。因此,不要在启用插入的情况下使用不受信任或未知的消息字符串,因为它们可能包含比 Arguments 提供的更多的插入序列,或者可能属于错误类型的插入序列。 尤其是,获取从 API 返回的任意系统错误代码并在没有 FORMAT_MESSAGE_IGNORE_INSERTS 的情况下使用 FORMAT_MESSAGE_FROM_SYSTEM 是不安全的

您的代码使用FORMAT_MESSAGE_FROM_SYSTEM 而不使用FORMAT_MESSAGE_IGNORE_INSERTS,这是不安全的。

所以,除非绝对确定您知道任何给定系统错误代码的确切格式,并且您的代码在决定哪种格式之前检查了该错误代码要传入的参数,并且您绝对确定微软永远不会在操作系统版本之间更改该错误消息的格式,那么您根本无法安全地使用带有系统错误的格式化参数,所以甚至不要尝试.只有当您通过FORMAT_MESSAGE_FROM_HMODULE 使用您自己的错误消息来访问您自己的 EXE/DLL 模块资源,或通过FORMAT_MESSAGE_FROM_STRING 在内存中提供您自己的错误消息字符串时,格式化参数才可以安全使用。

【讨论】:

    【解决方案2】:

    根据 David Heffernan 的澄清(请参阅原始问题中的 cmets),可以确认 FormatMessage 函数不支持替换系统错误消息占位符的功能,除非它们被编号 (%1)。因此,%hs 等占位符将变为 hs

    请注意,仅当您不使用FORMAT_MESSAGE_IGNORE_INSERTS 标志时,上述情况才适用。

    我已使用此链接检查了系统错误消息:

    https://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx

    此处列出的错误消息似乎没有混合占位符样式(错误消息将使用编号占位符或使用替代格式)。

    对于编号占位符,可以继续使用原始问题中描述的功能。对于使用替代样式的错误消息,可以使用vswprintf,但只能在使用FORMAT_MESSAGE_IGNORE_INSERTS 标志调用FormatMessage 函数之后(在填充的缓冲区上调用vswprintf,并记住重新初始化va_list )。

    在支持国际化时,整体方法可能存在弱点。占位符的顺序可能因不同语言而异。

    如果您真的需要在不使用FORMAT_MESSAGE_IGNORE_INSERTS 标志的情况下调用FormatMessage 来检索系统错误消息,那么工作量会大大增加。除了上面描述的内容之外,如果您的 va_list 与占位符的数量(可能还有类型)不匹配,您还可以获得 C0000005 异常。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-10-03
      • 2010-11-02
      • 2011-01-10
      • 1970-01-01
      • 1970-01-01
      • 2020-01-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多