1 /* 2 * PROJECT: ReactOS CRT library 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: Debug CRT reporting functions 5 * COPYRIGHT: Copyright 2020 Mark Jansen (mark.jansen@reactos.org) 6 */ 7 8 // This file should not be included in release builds, 9 // but since we do not have a good mechanism for this at the moment, 10 // just rely on the compiler to optimize it away instead of omitting the code. 11 //#ifdef _DEBUG 12 13 #include <crtdbg.h> 14 #include <stdio.h> 15 #include <signal.h> 16 #include <windows.h> 17 18 #undef OutputDebugString 19 #undef _CrtSetReportMode 20 #undef _CrtSetReportFile 21 22 #define DBGRPT_MAX_BUFFER_SIZE 4096 23 #define DBGRPT_ASSERT_PREFIX_MESSAGE "Assertion failed: " 24 #define DBGRPT_ASSERT_PREFIX_NOMESSAGE "Assertion failed!" 25 #define DBGRPT_STRING_TOO_LONG "_CrtDbgReport: String too long" 26 27 // Keep track of active asserts 28 static long _CrtInAssert = -1; 29 // State per type 30 static int _CrtModeOutputFormat[_CRT_ERRCNT] = 31 { 32 _CRTDBG_MODE_DEBUG, 33 _CRTDBG_MODE_WNDW, 34 _CRTDBG_MODE_WNDW, 35 }; 36 // Caption per type 37 static const wchar_t* _CrtModeMessages[_CRT_ERRCNT] = 38 { 39 L"Warning", 40 L"Error", 41 L"Assertion Failed" 42 }; 43 // Report files 44 static _HFILE _CrtReportFiles[_CRT_ERRCNT] = 45 { 46 _CRTDBG_INVALID_HFILE, 47 _CRTDBG_INVALID_HFILE, 48 _CRTDBG_INVALID_HFILE 49 }; 50 51 // Manually delay-load as to not have a dependency on user32 52 typedef int (WINAPI *tMessageBoxW)(_In_opt_ HWND hWnd, _In_opt_ LPCWSTR lpText, _In_opt_ LPCWSTR lpCaption, _In_ UINT uType); 53 static HMODULE _CrtUser32Handle = NULL; 54 static tMessageBoxW _CrtMessageBoxW = NULL; 55 56 template <typename char_t> 57 struct dbgrpt_char_traits; 58 59 template<> 60 struct dbgrpt_char_traits<char> 61 { 62 typedef char char_t; 63 64 static const wchar_t* szAssertionMessage; 65 static const char_t* szEmptyString; 66 static const char_t* szUnknownFile; 67 68 static void OutputDebugString(const char_t* message); 69 static size_t StringLength(const char_t* str) { return strlen(str); } 70 }; 71 72 template<> 73 struct dbgrpt_char_traits<wchar_t> 74 { 75 typedef wchar_t char_t; 76 77 static const wchar_t* szAssertionMessage; 78 static const char_t* szEmptyString; 79 static const char_t* szUnknownFile; 80 81 static void OutputDebugString(const char_t* message); 82 static size_t StringLength(const char_t* str) { return wcslen(str); }; 83 }; 84 85 // Shortcut 86 typedef dbgrpt_char_traits<char> achar_traits; 87 typedef dbgrpt_char_traits<wchar_t> wchar_traits; 88 89 const wchar_t* achar_traits::szAssertionMessage = 90 L"Debug %s!\n" 91 L"%s%hs" /* module */ 92 L"%s%hs" /* filename */ 93 L"%s%s" /* linenumber */ 94 L"%s%hs" /* message */ 95 L"\n\n(Press Retry to debug the application)"; 96 const wchar_t* wchar_traits::szAssertionMessage = 97 L"Debug %s!\n" 98 L"%s%ws" /* module */ 99 L"%s%ws" /* filename */ 100 L"%s%s" /* linenumber */ 101 L"%s%ws" /* message */ 102 L"\n\n(Press Retry to debug the application)"; 103 104 const achar_traits::char_t* achar_traits::szEmptyString = ""; 105 const wchar_traits::char_t* wchar_traits::szEmptyString = L""; 106 107 const achar_traits::char_t* achar_traits::szUnknownFile = "<unknown file>"; 108 const wchar_traits::char_t* wchar_traits::szUnknownFile = L"<unknown file>"; 109 110 inline void achar_traits::OutputDebugString(const char* message) 111 { 112 OutputDebugStringA(message); 113 } 114 115 inline void wchar_traits::OutputDebugString(const wchar_t* message) 116 { 117 OutputDebugStringW(message); 118 } 119 120 static 121 HMODULE _CrtGetUser32() 122 { 123 if (_CrtUser32Handle == NULL) 124 { 125 HMODULE mod = LoadLibraryExW(L"user32.dll", NULL, 0 /* NT6+: LOAD_LIBRARY_SEARCH_SYSTEM32 */); 126 if (mod == NULL) 127 mod = (HMODULE)INVALID_HANDLE_VALUE; 128 129 if (_InterlockedCompareExchangePointer((PVOID*)&_CrtUser32Handle, mod, NULL)) 130 { 131 if (mod != INVALID_HANDLE_VALUE) 132 FreeLibrary(mod); 133 } 134 } 135 136 return _CrtUser32Handle != INVALID_HANDLE_VALUE ? _CrtUser32Handle : NULL; 137 } 138 139 static tMessageBoxW _CrtGetMessageBox() 140 { 141 HMODULE mod = _CrtGetUser32(); 142 143 if (_CrtMessageBoxW == NULL && mod != INVALID_HANDLE_VALUE) 144 { 145 tMessageBoxW proc = (tMessageBoxW)GetProcAddress(mod, "MessageBoxW"); 146 if (proc == NULL) 147 proc = (tMessageBoxW)INVALID_HANDLE_VALUE; 148 149 _InterlockedCompareExchangePointer((PVOID*)&_CrtMessageBoxW, (PVOID)proc, NULL); 150 } 151 152 return _CrtMessageBoxW != INVALID_HANDLE_VALUE ? _CrtMessageBoxW : NULL; 153 } 154 155 156 template <typename char_t> 157 static int _CrtDbgReportWindow(int reportType, const char_t *filename, int linenumber, const char_t *moduleName, const char_t* message) 158 { 159 typedef dbgrpt_char_traits<char_t> traits; 160 161 wchar_t szCompleteMessage[(DBGRPT_MAX_BUFFER_SIZE+1)*2] = {0}; 162 wchar_t LineBuffer[20] = {0}; 163 164 if (filename && !filename[0]) 165 filename = NULL; 166 if (moduleName && !moduleName[0]) 167 moduleName = NULL; 168 if (message && !message[0]) 169 message = NULL; 170 if (linenumber) 171 _itow(linenumber, LineBuffer, 10); 172 173 _snwprintf(szCompleteMessage, DBGRPT_MAX_BUFFER_SIZE * 2, 174 traits::szAssertionMessage, 175 _CrtModeMessages[reportType], 176 moduleName ? L"\nModule: " : L"", moduleName ? moduleName : traits::szEmptyString, 177 filename ? L"\nFile: " : L"", filename ? filename : traits::szEmptyString, 178 LineBuffer[0] ? L"\nLine: " : L"", LineBuffer[0] ? LineBuffer : L"", 179 message ? L"\n\n" : L"", message ? message : traits::szEmptyString); 180 181 if (IsDebuggerPresent()) 182 { 183 OutputDebugStringW(szCompleteMessage); 184 } 185 186 tMessageBoxW messageBox = _CrtGetMessageBox(); 187 if (!messageBox) 188 return IsDebuggerPresent() ? IDRETRY : IDABORT; 189 190 // TODO: If we are not interacive, add MB_SERVICE_NOTIFICATION 191 return messageBox(NULL, szCompleteMessage, L"ReactOS C++ Runtime Library", 192 MB_ABORTRETRYIGNORE | MB_ICONHAND | MB_SETFOREGROUND | MB_TASKMODAL); 193 } 194 195 template <typename char_t> 196 static int _CrtEnterDbgReport(int reportType, const char_t *filename, int linenumber) 197 { 198 typedef dbgrpt_char_traits<char_t> traits; 199 200 if (reportType < 0 || reportType >= _CRT_ERRCNT) 201 return FALSE; 202 203 if (reportType == _CRT_ASSERT) 204 { 205 if (_InterlockedIncrement(&_CrtInAssert) > 0) 206 { 207 char LineBuffer[20] = {0}; 208 209 _itoa(linenumber, LineBuffer, 10); 210 211 OutputDebugStringA("Nested Assert from File: "); 212 traits::OutputDebugString(filename ? filename : traits::szUnknownFile); 213 OutputDebugStringA(", Line: "); 214 OutputDebugStringA(LineBuffer); 215 OutputDebugStringA("\n"); 216 217 _CrtDbgBreak(); 218 219 _InterlockedDecrement(&_CrtInAssert); 220 return FALSE; 221 } 222 } 223 return TRUE; 224 } 225 226 static 227 void _CrtLeaveDbgReport(int reportType) 228 { 229 if (reportType == _CRT_ASSERT) 230 _InterlockedDecrement(&_CrtInAssert); 231 } 232 233 EXTERN_C 234 int __cdecl _CrtSetReportMode(int reportType, int reportMode) 235 { 236 if (reportType >= _CRT_ERRCNT || reportType < 0) 237 return 0; 238 239 int oldReportMode = _CrtModeOutputFormat[reportType]; 240 if (reportMode != _CRTDBG_REPORT_MODE) 241 _CrtModeOutputFormat[reportType] = reportMode; 242 return oldReportMode; 243 } 244 245 EXTERN_C 246 _HFILE __cdecl _CrtSetReportFile(int reportType, _HFILE reportFile) 247 { 248 if (reportType >= _CRT_ERRCNT || reportType < 0) 249 return NULL; 250 251 _HFILE oldReportFile = _CrtReportFiles[reportType]; 252 if (reportFile != _CRTDBG_REPORT_FILE) 253 _CrtReportFiles[reportType] = reportFile; 254 return oldReportFile; 255 } 256 257 template <typename char_t> 258 static inline BOOL _CrtDbgReportToFile(HANDLE hFile, const char_t* szMsg) 259 { 260 typedef dbgrpt_char_traits<char_t> traits; 261 262 if (hFile == _CRTDBG_INVALID_HFILE || hFile == NULL) 263 return FALSE; 264 265 if (hFile == _CRTDBG_FILE_STDOUT) 266 hFile = ::GetStdHandle(STD_OUTPUT_HANDLE); 267 else if (hFile == _CRTDBG_FILE_STDERR) 268 hFile = ::GetStdHandle(STD_ERROR_HANDLE); 269 270 DWORD cbMsg = (DWORD)(traits::StringLength(szMsg) * sizeof(char_t)); 271 return ::WriteFile(hFile, szMsg, cbMsg, &cbMsg, NULL); 272 } 273 274 template <typename char_t> 275 static int _CrtHandleDbgReport(int reportType, const char_t* szCompleteMessage, const char_t* szFormatted, 276 const char_t *filename, int linenumber, const char_t *moduleName) 277 { 278 typedef dbgrpt_char_traits<char_t> traits; 279 280 if (_CrtModeOutputFormat[reportType] & _CRTDBG_MODE_FILE) 281 { 282 _CrtDbgReportToFile<char_t>(_CrtReportFiles[reportType], szCompleteMessage); 283 } 284 285 if (_CrtModeOutputFormat[reportType] & _CRTDBG_MODE_DEBUG) 286 { 287 traits::OutputDebugString(szCompleteMessage); 288 } 289 290 if (_CrtModeOutputFormat[reportType] & _CRTDBG_MODE_WNDW) 291 { 292 int nResult = _CrtDbgReportWindow(reportType, filename, linenumber, moduleName, szFormatted); 293 switch (nResult) 294 { 295 case IDRETRY: 296 return TRUE; 297 case IDIGNORE: 298 default: 299 return FALSE; 300 case IDABORT: 301 raise(SIGABRT); 302 _exit(3); 303 return FALSE; // Unreachable 304 } 305 } 306 307 return FALSE; 308 } 309 310 311 EXTERN_C 312 int __cdecl _CrtDbgReport(int reportType, const char *filename, int linenumber, const char *moduleName, const char *format, ...) 313 { 314 char szFormatted[DBGRPT_MAX_BUFFER_SIZE+1] = {0}; // The user provided message 315 char szCompleteMessage[(DBGRPT_MAX_BUFFER_SIZE+1)*2] = {0}; // The output for debug / file 316 317 // Check for recursive _CrtDbgReport calls, and validate reportType 318 if (!_CrtEnterDbgReport(reportType, filename, linenumber)) 319 return -1; 320 321 if (filename) 322 { 323 _snprintf(szCompleteMessage, DBGRPT_MAX_BUFFER_SIZE, "%s(%d) : ", filename, linenumber); 324 } 325 326 if (format) 327 { 328 va_list arglist; 329 va_start(arglist, format); 330 int len = _vsnprintf(szFormatted, DBGRPT_MAX_BUFFER_SIZE - 2 - sizeof(DBGRPT_ASSERT_PREFIX_MESSAGE), format, arglist); 331 va_end(arglist); 332 333 if (len < 0) 334 { 335 strcpy(szFormatted, DBGRPT_STRING_TOO_LONG); 336 } 337 338 if (reportType == _CRT_ASSERT) 339 strcat(szCompleteMessage, DBGRPT_ASSERT_PREFIX_MESSAGE); 340 strcat(szCompleteMessage, szFormatted); 341 } 342 else if (reportType == _CRT_ASSERT) 343 { 344 strcat(szCompleteMessage, DBGRPT_ASSERT_PREFIX_NOMESSAGE); 345 } 346 347 if (reportType == _CRT_ASSERT) 348 { 349 if (_CrtModeOutputFormat[reportType] & _CRTDBG_MODE_FILE) 350 strcat(szCompleteMessage, "\r"); 351 strcat(szCompleteMessage, "\n"); 352 } 353 354 // FIXME: Handle user report hooks here 355 356 int nResult = _CrtHandleDbgReport(reportType, szCompleteMessage, szFormatted, filename, linenumber, moduleName); 357 358 _CrtLeaveDbgReport(reportType); 359 360 return nResult; 361 } 362 363 EXTERN_C 364 int __cdecl _CrtDbgReportW(int reportType, const wchar_t *filename, int linenumber, const wchar_t *moduleName, const wchar_t *format, ...) 365 { 366 wchar_t szFormatted[DBGRPT_MAX_BUFFER_SIZE+1] = {0}; // The user provided message 367 wchar_t szCompleteMessage[(DBGRPT_MAX_BUFFER_SIZE+1)*2] = {0}; // The output for debug / file 368 369 // Check for recursive _CrtDbgReportW calls, and validate reportType 370 if (!_CrtEnterDbgReport(reportType, filename, linenumber)) 371 return -1; 372 373 if (filename) 374 { 375 _snwprintf(szCompleteMessage, DBGRPT_MAX_BUFFER_SIZE, L"%s(%d) : ", filename, linenumber); 376 } 377 378 if (format) 379 { 380 va_list arglist; 381 va_start(arglist, format); 382 int len = _vsnwprintf(szFormatted, DBGRPT_MAX_BUFFER_SIZE - 2 - sizeof(DBGRPT_ASSERT_PREFIX_MESSAGE), format, arglist); 383 va_end(arglist); 384 385 if (len < 0) 386 { 387 wcscpy(szFormatted, _CRT_WIDE(DBGRPT_STRING_TOO_LONG)); 388 } 389 390 if (reportType == _CRT_ASSERT) 391 wcscat(szCompleteMessage, _CRT_WIDE(DBGRPT_ASSERT_PREFIX_MESSAGE)); 392 wcscat(szCompleteMessage, szFormatted); 393 } 394 else if (reportType == _CRT_ASSERT) 395 { 396 wcscat(szCompleteMessage, _CRT_WIDE(DBGRPT_ASSERT_PREFIX_NOMESSAGE)); 397 } 398 399 if (reportType == _CRT_ASSERT) 400 { 401 if (_CrtModeOutputFormat[reportType] & _CRTDBG_MODE_FILE) 402 wcscat(szCompleteMessage, L"\r"); 403 wcscat(szCompleteMessage, L"\n"); 404 } 405 406 // FIXME: Handle user report hooks here 407 408 int nResult = _CrtHandleDbgReport(reportType, szCompleteMessage, szFormatted, filename, linenumber, moduleName); 409 410 _CrtLeaveDbgReport(reportType); 411 412 return nResult; 413 } 414 415 416 //#endif // _DEBUG 417