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

Posted

技术标签:

【中文标题】如何在不包含 Windows.h 的情况下获得 DebugBreak 的声明?【英文标题】:How to get a declaration for DebugBreak without including Windows.h? 【发布时间】:2016-09-17 20:09:08 【问题描述】:

我们有一个 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();                                               \
                                                                 \
  

我们遇到的问题是,我们必须包含&lt;windows.h&gt;,这会带来很多额外的麻烦,即使定义了WIN32_LEAN_AND_MEAN。一些额外的麻烦,如minmax,会破坏 C++ 编译。事实上,测试我们的更改让我们崩溃了。

我们通过&lt;windows.h&gt; 搜索“仅包含调试”类型定义,但找不到它。我们也试过根据Microsoft's docs on DebugBreak添加extern void WINAPI DebugBreak(void);,但是由于重新定义符号导致编译错误。

添加NO_MIN_MAX(我认为这是宏)不是一个选项,因为当定义从我们的标题交叉传到用户代码时,我们正在更改用户程序中的定义。相关,见Limiting Scope of #include Directives和朋友。

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

由于中断,我们想要包含&lt;windows.h&gt;。如何在不包含 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\8.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 ''

当我们检查 &lt;debugapi.h&gt; 时,我们看到:

WINBASEAPI
VOID
WINAPI
DebugBreak(
    VOID
    );

WINBASEAPI 扩展为其他宏。我不认为我们将能够在所有平台上都得到它们。


我们有一个跨平台的 C++ 安全库,它最近捕获了CVE-2016-7420。如果断言被触发,可能会丢失数据,因此它被归类为信息泄露。当敏感数据导出到文件系统(核心转储和崩溃报告)时会发生丢失;并出口到第三方(Apple 通过 CrashReporter、Ubuntu 通过 Appor、Microsoft 通过 Windows 错误报告、开发人员等)。

断言从未在我们的生产/发布中触发,因为我们的 Makefile 和 Visual Studio 解决方案很好地配置了库。在生产/发布版本中,断言被删除,C++ throw() 处理错误情况。断言存在于调试/开发人员配置中,因此代码将自行调试,并将程序员从任务中解脱出来。

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

【问题讨论】:

很抱歉,您有 minimal reproducible example 吗?如果你有,我会调查它。我在&lt;windows.h&gt; 定义 TRUE 和 FALSE 时遇到了这个问题,并在我只想让结构测量准确的时间并将其整理出来时破坏了一些东西。 代码调试步骤:1) 阅读,2) 记录,3) 逐步检查,4) 设置陷阱 5) 为客户设置陷阱(在发布版本中)。 @Jean-FrançoisFabre - 很公平。完成。 “一些多余的东西,比如minmax,会破坏C++ 编译。” - 这就是#define NOMINMAX 的原因。 Windows 头文件可以驯服。我不是建议你应该走这条路,但如果你必须这样做,这很有可能。 @IInspectable - 请发布您使用 NOMINMAX 的代码,这样它就不会从我们的标题中交叉传播到用户标题中。我很乐意对其进行测试。 【参考方案1】:

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

__debugbreak();

【讨论】:

【参考方案2】:

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

#include <windows.h>

void MyDebugBreak(void)

   DebugBreak();

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

您可以仅在 Windows 版本中包含该文件,也可以根据需要添加 #if 块。

【讨论】:

谢谢@Harry。我认为这样可以确保没有异花授粉。我将尝试@Dani 的解决方案,因为它是一个仅包含标头的解决方案,以及Microsoft states under requirements: "x86, ARM, x64"&lt;intrin&gt; 的要求不太对;它打破了VS2002和VS2003。 Microsoft also states 这些好处:“你可以调用 DebugBreak Win32 函数或 __debugbreak ... 因为 DebugBreak 是对系统函数的调用,所以必须安装系统调试符号...” @jww:我对 Dani 解决方案的唯一担忧是它特定于 Visual Studio。如果在您的上下文中这不是问题,那么是的,这听起来是最好的选择。 好点。测试将揭示它们。在这一点上,MinGW 是我唯一关心的问题,但尽管我很努力地建立了一个测试台,但我没有一个测试台。 Borland 是另一个问题,但我只知道一个使用它的用户。我试图从 Embarcadero 获得免费许可证进行测试,但他们拒绝了。因此,他们的用户必须受苦​​,直到有人提醒我们并提交补丁。【参考方案3】:

使用 Visual Studio 6 和 2015 声明 DebugBreak 似乎可以正常工作,前提是您使用 __stdcallextern "C" 声明它。由于 VC++ 6 似乎没有在算法标头中包含 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;

【讨论】:

谢谢@patthoyts。不幸的是,extern "C" 由于链接不同而导致相同的编译错误。我还从&lt;debugapi.h&gt; 添加了DebugBreak 的Microsoft 签名。看起来他们用比他们记录的更多的东西来装饰这个符号。

以上是关于如何在不包含 Windows.h 的情况下获得 DebugBreak 的声明?的主要内容,如果未能解决你的问题,请参考以下文章

在不包含任何内容的情况下获得错误声音?

如何在不扩展包含的头文件的情况下预编译 C 源文件?

asp.net中,在不播放视频的情况下如何获得视频长度

如何在不打开的情况下获得最大文件的大小?

如何在不安装的情况下列出python库的依赖项? [复制]

如何在不安装GlassFish的情况下下载最新的Java EE jar?