We have a C++ library. We are providing a custom assert and abandoning Posix NDEBUG
and assert
(back story below).
The assert looks like so under 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(); \
} \
}
The problem we are having is, we have to include <windows.h>
, and it brings in a lot of extra cruft, even with WIN32_LEAN_AND_MEAN
defined. Some of the extra cruft, like min
and max
, breaks C++ compiles. In fact, testing our changes broke us.
We looked through <windows.h>
searching for a "debug includes only" type define but we could not find it. We also tried adding extern void WINAPI DebugBreak(void);
according to Microsoft's docs on DebugBreak, but it resulted in compile errors due to redefined symbols.
Adding NO_MIN_MAX
(I think that's the macro) is not an option because we are changing defines in user programs when the define cross-pollinates out of our header and into user code. Related, see Limiting Scope of #include Directives and friends.
Using #pragma push_macro
and #pragma pop_macro
is not an option because we support Microsoft compilers back to VC++ 6.0. The earliest the pragmas are available is VS2003.
Because of the breaks, we do not want to include <windows.h>
. How can I get a declaration for DebugBreak
without including Windows.h?
Thanks in advance.
Here's a reduced case:
// 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));
}
Here's the error when simulating a user program with our extern declaration. This test occurred on Windows 8.1 x64 with VS2012 and VS2013 installed. We also used a developer command prompt.
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 '{'
When we inspect <debugapi.h>
we see:
WINBASEAPI
VOID
WINAPI
DebugBreak(
VOID
);
WINBASEAPI
expands into additional macros. I don't think we are going to be able to get them all right across all platforms.
We have a cross-platform C++ security library and it recently caught CVE-2016-7420. It was classified as an information disclosure due the potential data loss if an assert fired. The loss occurs when the sensitive data is egressed to the file system (core dumps and crash reports); and egressed to third parties (Apple via CrashReporter, Ubuntu via Apport, Microsoft via Windows Error Reporting, developers, etc).
The asserts never fired in production/release for us because our Makefile and our Visual Studio solution well configured the library. In production/release builds, the asserts are removed and a C++ throw()
handles the error condition. The asserts are present in debug/developer configurations so the code will debug itself, and relieve the programmer from the task.
After we analyzed it, we realized documenting "release/production builds must use -DNDEBUG
" is an incomplete remediation. Folks won't read the docs; if RTFM was going to work, then it would have happened by now. In addition, CMake won't define it, Autotools won't define it, Eclipse won't define it, etc. We are effectively at the same point we were before the CVE. All we did was move the blame around without reducing the risk.
You can use the intrinsic, it works without includes:
__debugbreak();
Just add a new source code file which contains nothing but:
#include <windows.h>
void MyDebugBreak(void)
{
DebugBreak();
}
Export as necessary, and call MyDebugBreak() instead of DebugBreak() in your macro.
You can either include the file only in Windows builds, or add #if
blocks as appropriate.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With