如何在不包含 Windows.h 的情况下获取 DebugBreak 的声明?

How to get a declaration for DebugBreak without including Windows.h?

我们有一个 C++ 库。我们正在提供自定义断言并放弃 Posix NDEBUGassert(下面的背景故事)。

断言在 Windows:

下看起来像这样
#  define CRYPTOPP_ASSERT(exp) {                                  \
    if (!(exp)) {                                                 \
      std::ostringstream oss;                                     \
      oss << "Assertion failed: " << (char*)(__FILE__) << "("     \
          << (int)(__LINE__) << "): " << (char*)(__FUNCTION__)    \
          << std::endl;                                           \
      std::cerr << oss.str();                                     \
      DebugBreak();                                               \
    }                                                             \
  }

我们遇到的问题是,我们必须包括 <windows.h>,它会带来很多额外的麻烦,即使定义了 WIN32_LEAN_AND_MEAN。一些多余的东西,比如 minmax,会破坏 C++ 编译。事实上,测试我们的更改让我们崩溃了。

我们通过 <windows.h> 搜索了 "debug includes only" 类型定义,但找不到。我们也试过根据Microsoft's docs on DebugBreak添加extern void WINAPI DebugBreak(void);,但是由于重新定义符号导致编译错误。

添加 NO_MIN_MAX(我认为那是宏)不是一个选项,因为当定义 cross-pollinates 从我们的 header 进入用户代码时,我们正在更改用户程序中的定义.相关的,见Limiting Scope of #include Directives和朋友

使用 #pragma push_macro#pragma pop_macro 不是一个选项,因为我们支持回到 VC++ 6.0 的 Microsoft 编译器。最早可用的编译指示是 VS2003。

由于中断,我们不想包含<windows.h>。如何在不包含 Windows.h 的情况下获得 DebugBreak 的声明?

提前致谢。


这是一个简化的案例:

// cl.exe /c assert.cpp

#include <algorithm>

// #include <windows.h>
// #ifndef WINAPI
// # define WINAPI __stdcall
// #endif
// extern void WINAPI DebugBreak(void);

#define MY_ASSERT(exp) { \
   if (!(exp)) {         \
     DebugBreak();       \
   }                     \
}

void dummy(int x1, int x2)
{
    MY_ASSERT(x1 == std::min(x1, x2));
}

这是使用我们的 extern 声明模拟用户程序时的错误。此测试发生在安装了 VS2012 和 VS2013 的 Windows 8.1 x64 上。我们还使用了开发人员命令提示符。

Microsoft (R) C/C++ Optimizing Compiler Version 18.00.21005.1 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

assert.cpp
assert.cpp(11) : warning C4273: 'DebugBreak' : inconsistent dll linkage
        C:\Program Files (x86)\Windows Kits.1\include\um\debugapi.h(70) : see
previous definition of 'DebugBreak'
assert.cpp(21) : error C2589: '(' : illegal token on right side of '::'
assert.cpp(21) : error C2059: syntax error : '::'
assert.cpp(21) : error C2143: syntax error : missing ';' before '{'

当我们检查 <debugapi.h> 时,我们看到:

WINBASEAPI
VOID
WINAPI
DebugBreak(
    VOID
    );

WINBASEAPI 扩展为额外的宏。我不认为我们能够让它们在所有平台上都正常运行。


我们有一个 cross-platform C++ 安全库,它最近被发现 CVE-2016-7420。由于断言被触发可能导致数据丢失,因此它被归类为信息泄露。当敏感数据传出到文件系统(核心转储和崩溃报告)时会发生丢失;并流向第三方(Apple 通过 CrashReporter,Ubuntu 通过 Apport,Microsoft 通过 Windows 错误报告,开发人员等)。

我们的 production/release 断言从未触发,因为我们的 Makefile 和 Visual Studio 解决方案很好地配置了库。在 production/release 构建中,断言被删除,C++ throw() 处理错误情况。断言出现在 debug/developer 配置中,因此代码将自行调试,并减轻程序员的负担。

经过我们的分析,我们实现了documenting "release/production builds must use -DNDEBUG" is an incomplete remediation。人们不会阅读文档;如果 RTFM 要工作,那么它现在就会发生。此外,CMake 不会定义它,Autotools 不会定义它,Eclipse 不会定义它,等等。我们实际上处于 CVE 之前的同一点。我们所做的只是在不降低风险的情况下推卸责任。

您可以使用内在函数,它可以在没有包含的情况下工作:

__debugbreak();

使用 Visual Studio 6 和 2015 声明 DebugBreak 似乎可以正常工作,前提是您使用 __stdcallextern "C" 声明它。由于 VC++ 6 似乎没有在算法 header 中包含 std::min 我已经稍微修改了你的例子但是如果第一个参数大于第二个它会引发断言使用 cl -nologo -W3 -O2 -Zi -NDEBUG -o assert.exe assert.cpp -link -subsystem:console -debug

构建时
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

extern "C" extern void __stdcall DebugBreak(void );

#define MY_ASSERT(exp) { \
   if (!(exp)) {         \
     DebugBreak();       \
   }                     \
}

void dummy(int x1, int x2)
{
    MY_ASSERT(x1 < x2);
}

int main(int argc, char *argv[])
{
    if (argc != 3) {
        fprintf(stderr, "usage: assert integer integer\n");
        exit(1);
    }
    int a = strtol(argv[1], NULL, 0);
    int b = strtol(argv[2], NULL, 0);
    dummy(a, b);
    return 0;
}

只需添加一个新的源代码文件,其中仅包含:

#include <windows.h>

void MyDebugBreak(void)
{
   DebugBreak();
}

根据需要导出,并在您的宏中调用 MyDebugBreak() 而不是 DebugBreak()。

您可以仅在 Windows 构建中包含该文件,或根据需要添加 #if 块。