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] = {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, 174 _countof(szCompleteMessage) - 1, 175 traits::szAssertionMessage, 176 _CrtModeMessages[reportType], 177 moduleName ? L"\nModule: " : L"", moduleName ? moduleName : traits::szEmptyString, 178 filename ? L"\nFile: " : L"", filename ? filename : traits::szEmptyString, 179 LineBuffer[0] ? L"\nLine: " : L"", LineBuffer[0] ? LineBuffer : L"", 180 message ? L"\n\n" : L"", message ? message : traits::szEmptyString); 181 182 if (IsDebuggerPresent()) 183 { 184 OutputDebugStringW(szCompleteMessage); 185 } 186 187 tMessageBoxW messageBox = _CrtGetMessageBox(); 188 if (!messageBox) 189 return (IsDebuggerPresent() ? IDRETRY : IDABORT); 190 191 // TODO: If we are not interacive, add MB_SERVICE_NOTIFICATION 192 return messageBox(NULL, szCompleteMessage, L"ReactOS C++ Runtime Library", 193 MB_ABORTRETRYIGNORE | MB_ICONHAND | MB_SETFOREGROUND | MB_TASKMODAL); 194 } 195 196 template <typename char_t> 197 static int _CrtEnterDbgReport(int reportType, const char_t *filename, int linenumber) 198 { 199 typedef dbgrpt_char_traits<char_t> traits; 200 201 if (reportType < 0 || reportType >= _CRT_ERRCNT) 202 return FALSE; 203 204 if (reportType == _CRT_ASSERT) 205 { 206 if (_InterlockedIncrement(&_CrtInAssert) > 0) 207 { 208 char LineBuffer[20] = {0}; 209 210 _itoa(linenumber, LineBuffer, 10); 211 212 OutputDebugStringA("Nested Assert from File: "); 213 traits::OutputDebugString(filename ? filename : traits::szUnknownFile); 214 OutputDebugStringA(", Line: "); 215 OutputDebugStringA(LineBuffer); 216 OutputDebugStringA("\n"); 217 218 _CrtDbgBreak(); 219 220 _InterlockedDecrement(&_CrtInAssert); 221 return FALSE; 222 } 223 } 224 return TRUE; 225 } 226 227 static 228 void _CrtLeaveDbgReport(int reportType) 229 { 230 if (reportType == _CRT_ASSERT) 231 _InterlockedDecrement(&_CrtInAssert); 232 } 233 234 EXTERN_C 235 int __cdecl _CrtSetReportMode(int reportType, int reportMode) 236 { 237 if (reportType >= _CRT_ERRCNT || reportType < 0) 238 return 0; 239 240 int oldReportMode = _CrtModeOutputFormat[reportType]; 241 if (reportMode != _CRTDBG_REPORT_MODE) 242 _CrtModeOutputFormat[reportType] = reportMode; 243 return oldReportMode; 244 } 245 246 EXTERN_C 247 _HFILE __cdecl _CrtSetReportFile(int reportType, _HFILE reportFile) 248 { 249 if (reportType >= _CRT_ERRCNT || reportType < 0) 250 return NULL; 251 252 _HFILE oldReportFile = _CrtReportFiles[reportType]; 253 if (reportFile != _CRTDBG_REPORT_FILE) 254 _CrtReportFiles[reportType] = reportFile; 255 return oldReportFile; 256 } 257 258 template <typename char_t> 259 static inline BOOL _CrtDbgReportToFile(HANDLE hFile, const char_t* szMsg) 260 { 261 typedef dbgrpt_char_traits<char_t> traits; 262 263 if (hFile == _CRTDBG_INVALID_HFILE || hFile == NULL) 264 return FALSE; 265 266 if (hFile == _CRTDBG_FILE_STDOUT) 267 hFile = ::GetStdHandle(STD_OUTPUT_HANDLE); 268 else if (hFile == _CRTDBG_FILE_STDERR) 269 hFile = ::GetStdHandle(STD_ERROR_HANDLE); 270 271 DWORD cbMsg = (DWORD)(traits::StringLength(szMsg) * sizeof(char_t)); 272 return ::WriteFile(hFile, szMsg, cbMsg, &cbMsg, NULL); 273 } 274 275 template <typename char_t> 276 static int _CrtHandleDbgReport(int reportType, const char_t* szCompleteMessage, const char_t* szFormatted, 277 const char_t *filename, int linenumber, const char_t *moduleName) 278 { 279 typedef dbgrpt_char_traits<char_t> traits; 280 281 if (_CrtModeOutputFormat[reportType] & _CRTDBG_MODE_FILE) 282 { 283 _CrtDbgReportToFile<char_t>(_CrtReportFiles[reportType], szCompleteMessage); 284 } 285 286 if (_CrtModeOutputFormat[reportType] & _CRTDBG_MODE_DEBUG) 287 { 288 traits::OutputDebugString(szCompleteMessage); 289 } 290 291 if (_CrtModeOutputFormat[reportType] & _CRTDBG_MODE_WNDW) 292 { 293 int nResult = _CrtDbgReportWindow(reportType, filename, linenumber, moduleName, szFormatted); 294 switch (nResult) 295 { 296 case IDRETRY: 297 return TRUE; 298 case IDIGNORE: 299 default: 300 return FALSE; 301 case IDABORT: 302 raise(SIGABRT); 303 _exit(3); 304 return FALSE; // Unreachable 305 } 306 } 307 308 return FALSE; 309 } 310 311 312 EXTERN_C 313 int __cdecl 314 _VCrtDbgReportA( 315 int reportType, 316 const char *filename, 317 int linenumber, 318 const char *moduleName, 319 const char *format, 320 va_list arglist) 321 { 322 char szFormatted[DBGRPT_MAX_BUFFER_SIZE] = {0}; // The user provided message 323 char szCompleteMessage[DBGRPT_MAX_BUFFER_SIZE] = {0}; // The output for debug / file 324 325 // Check for recursive _CrtDbgReport calls, and validate reportType 326 if (!_CrtEnterDbgReport(reportType, filename, linenumber)) 327 return -1; 328 329 if (filename) 330 { 331 _snprintf(szCompleteMessage, 332 _countof(szCompleteMessage) - 1, 333 "%s(%d) : ", 334 filename, 335 linenumber); 336 } 337 338 if (format) 339 { 340 int len = _vsnprintf(szFormatted, 341 _countof(szFormatted) - 2 - _countof(DBGRPT_ASSERT_PREFIX_MESSAGE), 342 format, 343 arglist); 344 if (len < 0) 345 { 346 strcpy(szFormatted, DBGRPT_STRING_TOO_LONG); 347 } 348 349 if (reportType == _CRT_ASSERT) 350 strcat(szCompleteMessage, DBGRPT_ASSERT_PREFIX_MESSAGE); 351 strcat(szCompleteMessage, szFormatted); 352 } 353 else if (reportType == _CRT_ASSERT) 354 { 355 strcat(szCompleteMessage, DBGRPT_ASSERT_PREFIX_NOMESSAGE); 356 } 357 358 if (reportType == _CRT_ASSERT) 359 { 360 if (_CrtModeOutputFormat[reportType] & _CRTDBG_MODE_FILE) 361 strcat(szCompleteMessage, "\r"); 362 strcat(szCompleteMessage, "\n"); 363 } 364 365 // FIXME: Handle user report hooks here 366 367 int nResult = _CrtHandleDbgReport(reportType, szCompleteMessage, szFormatted, filename, linenumber, moduleName); 368 369 _CrtLeaveDbgReport(reportType); 370 371 return nResult; 372 } 373 374 EXTERN_C 375 int __cdecl 376 _VCrtDbgReportW( 377 int reportType, 378 const wchar_t *filename, 379 int linenumber, 380 const wchar_t *moduleName, 381 const wchar_t *format, 382 va_list arglist) 383 { 384 wchar_t szFormatted[DBGRPT_MAX_BUFFER_SIZE] = {0}; // The user provided message 385 wchar_t szCompleteMessage[DBGRPT_MAX_BUFFER_SIZE] = {0}; // The output for debug / file 386 387 // Check for recursive _CrtDbgReportW calls, and validate reportType 388 if (!_CrtEnterDbgReport(reportType, filename, linenumber)) 389 return -1; 390 391 if (filename) 392 { 393 _snwprintf(szCompleteMessage, 394 _countof(szCompleteMessage) - 1, 395 L"%s(%d) : ", 396 filename, 397 linenumber); 398 } 399 400 if (format) 401 { 402 int len = _vsnwprintf(szFormatted, 403 _countof(szFormatted) - 2 - _countof(DBGRPT_ASSERT_PREFIX_MESSAGE), 404 format, 405 arglist); 406 if (len < 0) 407 { 408 wcscpy(szFormatted, _CRT_WIDE(DBGRPT_STRING_TOO_LONG)); 409 } 410 411 if (reportType == _CRT_ASSERT) 412 wcscat(szCompleteMessage, _CRT_WIDE(DBGRPT_ASSERT_PREFIX_MESSAGE)); 413 wcscat(szCompleteMessage, szFormatted); 414 } 415 else if (reportType == _CRT_ASSERT) 416 { 417 wcscat(szCompleteMessage, _CRT_WIDE(DBGRPT_ASSERT_PREFIX_NOMESSAGE)); 418 } 419 420 if (reportType == _CRT_ASSERT) 421 { 422 if (_CrtModeOutputFormat[reportType] & _CRTDBG_MODE_FILE) 423 wcscat(szCompleteMessage, L"\r"); 424 wcscat(szCompleteMessage, L"\n"); 425 } 426 427 // FIXME: Handle user report hooks here 428 429 int nResult = _CrtHandleDbgReport(reportType, szCompleteMessage, szFormatted, filename, linenumber, moduleName); 430 431 _CrtLeaveDbgReport(reportType); 432 433 return nResult; 434 } 435 436 EXTERN_C 437 int __cdecl 438 _CrtDbgReportV( 439 int reportType, 440 const char *filename, 441 int linenumber, 442 const char *moduleName, 443 const char *format, 444 va_list arglist) 445 { 446 return _VCrtDbgReportA(reportType, filename, linenumber, moduleName, format, arglist); 447 } 448 449 EXTERN_C 450 int __cdecl 451 _CrtDbgReportWV( 452 int reportType, 453 const wchar_t *filename, 454 int linenumber, 455 const wchar_t *moduleName, 456 const wchar_t *format, 457 va_list arglist) 458 { 459 return _VCrtDbgReportW(reportType, filename, linenumber, moduleName, format, arglist); 460 } 461 462 EXTERN_C 463 int __cdecl 464 _CrtDbgReport( 465 int reportType, 466 const char *filename, 467 int linenumber, 468 const char *moduleName, 469 const char *format, 470 ...) 471 { 472 va_list arglist; 473 int result; 474 475 va_start(arglist, format); 476 result = _VCrtDbgReportA(reportType, filename, linenumber, moduleName, format, arglist); 477 va_end(arglist); 478 return result; 479 } 480 481 EXTERN_C 482 int __cdecl 483 _CrtDbgReportW( 484 int reportType, 485 const wchar_t *filename, 486 int linenumber, 487 const wchar_t *moduleName, 488 const wchar_t *format, 489 ...) 490 { 491 va_list arglist; 492 int result; 493 494 va_start(arglist, format); 495 result = _VCrtDbgReportW(reportType, filename, linenumber, moduleName, format, arglist); 496 va_end(arglist); 497 return result; 498 } 499 500 //#endif // _DEBUG 501