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 20 #define DBGRPT_MAX_BUFFER_SIZE 4096 21 #define DBGRPT_ASSERT_PREFIX_MESSAGE "Assertion failed: " 22 #define DBGRPT_ASSERT_PREFIX_NOMESSAGE "Assertion failed!" 23 #define DBGRPT_STRING_TOO_LONG "_CrtDbgReport: String too long" 24 25 // Keep track of active asserts 26 static long _CrtInAssert = -1; 27 // State per type 28 static int _CrtModeOutputFormat[_CRT_ERRCNT] = 29 { 30 _CRTDBG_MODE_DEBUG, 31 _CRTDBG_MODE_WNDW, 32 _CRTDBG_MODE_WNDW, 33 }; 34 // Caption per type 35 static const wchar_t* _CrtModeMessages[_CRT_ERRCNT] = 36 { 37 L"Warning", 38 L"Error", 39 L"Assertion Failed" 40 }; 41 42 // Manually delay-load as to not have a dependency on user32 43 typedef int (WINAPI *tMessageBoxW)(_In_opt_ HWND hWnd, _In_opt_ LPCWSTR lpText, _In_opt_ LPCWSTR lpCaption, _In_ UINT uType); 44 static HMODULE _CrtUser32Handle = NULL; 45 static tMessageBoxW _CrtMessageBoxW = NULL; 46 47 template <typename char_t> 48 struct dbgrpt_char_traits; 49 50 template<> 51 struct dbgrpt_char_traits<char> 52 { 53 typedef char char_t; 54 55 static const wchar_t* szAssertionMessage; 56 static const char_t* szEmptyString; 57 static const char_t* szUnknownFile; 58 59 static void OutputDebugString(const char_t* message); 60 }; 61 62 template<> 63 struct dbgrpt_char_traits<wchar_t> 64 { 65 typedef wchar_t char_t; 66 67 static const wchar_t* szAssertionMessage; 68 static const char_t* szEmptyString; 69 static const char_t* szUnknownFile; 70 71 static void OutputDebugString(const char_t* message); 72 }; 73 74 // Shortcut 75 typedef dbgrpt_char_traits<char> achar_traits; 76 typedef dbgrpt_char_traits<wchar_t> wchar_traits; 77 78 79 const wchar_t* achar_traits::szAssertionMessage = 80 L"Debug %s!\n" 81 L"%s%hs" /* module */ 82 L"%s%hs" /* filename */ 83 L"%s%s" /* linenumber */ 84 L"%s%hs" /* message */ 85 L"\n\n(Press Retry to debug the application)"; 86 const wchar_t* wchar_traits::szAssertionMessage = 87 L"Debug %s!\n" 88 L"%s%ws" /* module */ 89 L"%s%ws" /* filename */ 90 L"%s%s" /* linenumber */ 91 L"%s%ws" /* message */ 92 L"\n\n(Press Retry to debug the application)"; 93 94 const achar_traits::char_t* achar_traits::szEmptyString = ""; 95 const wchar_traits::char_t* wchar_traits::szEmptyString = L""; 96 97 const achar_traits::char_t* achar_traits::szUnknownFile = "<unknown file>"; 98 const wchar_traits::char_t* wchar_traits::szUnknownFile = L"<unknown file>"; 99 100 void achar_traits::OutputDebugString(const char* message) 101 { 102 OutputDebugStringA(message); 103 } 104 105 void wchar_traits::OutputDebugString(const wchar_t* message) 106 { 107 OutputDebugStringW(message); 108 } 109 110 111 static 112 HMODULE _CrtGetUser32() 113 { 114 if (_CrtUser32Handle == NULL) 115 { 116 HMODULE mod = LoadLibraryExW(L"user32.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); 117 if (mod == NULL) 118 mod = (HMODULE)INVALID_HANDLE_VALUE; 119 120 if (_InterlockedCompareExchangePointer((PVOID*)&_CrtUser32Handle, mod, NULL)) 121 { 122 if (mod != INVALID_HANDLE_VALUE) 123 FreeLibrary(mod); 124 } 125 } 126 127 return _CrtUser32Handle != INVALID_HANDLE_VALUE ? _CrtUser32Handle : NULL; 128 } 129 130 static tMessageBoxW _CrtGetMessageBox() 131 { 132 HMODULE mod = _CrtGetUser32(); 133 134 if (_CrtMessageBoxW == NULL && mod != INVALID_HANDLE_VALUE) 135 { 136 tMessageBoxW proc = (tMessageBoxW)GetProcAddress(mod, "MessageBoxW"); 137 if (proc == NULL) 138 proc = (tMessageBoxW)INVALID_HANDLE_VALUE; 139 140 _InterlockedCompareExchangePointer((PVOID*)&_CrtMessageBoxW, (PVOID)proc, NULL); 141 } 142 143 return _CrtMessageBoxW != INVALID_HANDLE_VALUE ? _CrtMessageBoxW : NULL; 144 } 145 146 147 template <typename char_t> 148 static int _CrtDbgReportWindow(int reportType, const char_t *filename, int linenumber, const char_t *moduleName, const char_t* message) 149 { 150 typedef dbgrpt_char_traits<char_t> traits; 151 152 wchar_t szCompleteMessage[(DBGRPT_MAX_BUFFER_SIZE+1)*2] = {0}; 153 wchar_t LineBuffer[20] = {0}; 154 155 if (filename && !filename[0]) 156 filename = NULL; 157 if (moduleName && !moduleName[0]) 158 moduleName = NULL; 159 if (message && !message[0]) 160 message = NULL; 161 if (linenumber) 162 _itow(linenumber, LineBuffer, 10); 163 164 _snwprintf(szCompleteMessage, DBGRPT_MAX_BUFFER_SIZE * 2, 165 traits::szAssertionMessage, 166 _CrtModeMessages[reportType], 167 moduleName ? L"\nModule: " : L"", moduleName ? moduleName : traits::szEmptyString, 168 filename ? L"\nFile: " : L"", filename ? filename : traits::szEmptyString, 169 LineBuffer[0] ? L"\nLine: " : L"", LineBuffer[0] ? LineBuffer : L"", 170 message ? L"\n\n" : L"", message ? message : traits::szEmptyString); 171 172 if (IsDebuggerPresent()) 173 { 174 OutputDebugStringW(szCompleteMessage); 175 } 176 177 tMessageBoxW messageBox = _CrtGetMessageBox(); 178 if (!messageBox) 179 return IsDebuggerPresent() ? IDRETRY : IDABORT; 180 181 // TODO: If we are not interacive, add MB_SERVICE_NOTIFICATION 182 return messageBox(NULL, szCompleteMessage, L"ReactOS C++ Runtime Library", 183 MB_ABORTRETRYIGNORE | MB_ICONHAND | MB_SETFOREGROUND | MB_TASKMODAL); 184 } 185 186 template <typename char_t> 187 static int _CrtEnterDbgReport(int reportType, const char_t *filename, int linenumber) 188 { 189 typedef dbgrpt_char_traits<char_t> traits; 190 191 if (reportType < 0 || reportType >= _CRT_ERRCNT) 192 return FALSE; 193 194 if (reportType == _CRT_ASSERT) 195 { 196 if (_InterlockedIncrement(&_CrtInAssert) > 0) 197 { 198 char LineBuffer[20] = {0}; 199 200 _itoa(linenumber, LineBuffer, 10); 201 202 OutputDebugStringA("Nested Assert from File: "); 203 traits::OutputDebugString(filename ? filename : traits::szUnknownFile); 204 OutputDebugStringA(", Line: "); 205 OutputDebugStringA(LineBuffer); 206 OutputDebugStringA("\n"); 207 208 _CrtDbgBreak(); 209 210 _InterlockedDecrement(&_CrtInAssert); 211 return FALSE; 212 } 213 } 214 return TRUE; 215 } 216 217 static 218 void _CrtLeaveDbgReport(int reportType) 219 { 220 if (reportType == _CRT_ASSERT) 221 _InterlockedDecrement(&_CrtInAssert); 222 } 223 224 225 template <typename char_t> 226 static int _CrtHandleDbgReport(int reportType, const char_t* szCompleteMessage, const char_t* szFormatted, 227 const char_t *filename, int linenumber, const char_t *moduleName) 228 { 229 typedef dbgrpt_char_traits<char_t> traits; 230 231 if (_CrtModeOutputFormat[reportType] & _CRTDBG_MODE_FILE) 232 { 233 OutputDebugStringA("ERROR: Please implement _CrtSetReportFile first\n"); 234 _CrtDbgBreak(); 235 } 236 237 if (_CrtModeOutputFormat[reportType] & _CRTDBG_MODE_DEBUG) 238 { 239 traits::OutputDebugString(szCompleteMessage); 240 } 241 242 if (_CrtModeOutputFormat[reportType] & _CRTDBG_MODE_WNDW) 243 { 244 int nResult = _CrtDbgReportWindow(reportType, filename, linenumber, moduleName, szFormatted); 245 switch (nResult) 246 { 247 case IDRETRY: 248 return TRUE; 249 case IDIGNORE: 250 default: 251 return FALSE; 252 case IDABORT: 253 raise(SIGABRT); 254 _exit(3); 255 return FALSE; // Unreachable 256 } 257 } 258 259 return FALSE; 260 } 261 262 263 EXTERN_C 264 int __cdecl _CrtDbgReport(int reportType, const char *filename, int linenumber, const char *moduleName, const char *format, ...) 265 { 266 char szFormatted[DBGRPT_MAX_BUFFER_SIZE+1] = {0}; // The user provided message 267 char szCompleteMessage[(DBGRPT_MAX_BUFFER_SIZE+1)*2] = {0}; // The output for debug / file 268 269 // Check for recursive _CrtDbgReport calls, and validate reportType 270 if (!_CrtEnterDbgReport(reportType, filename, linenumber)) 271 return -1; 272 273 if (filename) 274 { 275 _snprintf(szCompleteMessage, DBGRPT_MAX_BUFFER_SIZE, "%s(%d) : ", filename, linenumber); 276 } 277 278 if (format) 279 { 280 va_list arglist; 281 va_start(arglist, format); 282 int len = _vsnprintf(szFormatted, DBGRPT_MAX_BUFFER_SIZE - 2 - sizeof(DBGRPT_ASSERT_PREFIX_MESSAGE), format, arglist); 283 va_end(arglist); 284 285 if (len < 0) 286 { 287 strcpy(szFormatted, DBGRPT_STRING_TOO_LONG); 288 } 289 290 if (reportType == _CRT_ASSERT) 291 strcat(szCompleteMessage, DBGRPT_ASSERT_PREFIX_MESSAGE); 292 strcat(szCompleteMessage, szFormatted); 293 } 294 else if (reportType == _CRT_ASSERT) 295 { 296 strcat(szCompleteMessage, DBGRPT_ASSERT_PREFIX_NOMESSAGE); 297 } 298 299 if (reportType == _CRT_ASSERT) 300 { 301 if (_CrtModeOutputFormat[reportType] & _CRTDBG_MODE_FILE) 302 strcat(szCompleteMessage, "\r"); 303 strcat(szCompleteMessage, "\n"); 304 } 305 306 // FIXME: Handle user report hooks here 307 308 int nResult = _CrtHandleDbgReport(reportType, szCompleteMessage, szFormatted, filename, linenumber, moduleName); 309 310 _CrtLeaveDbgReport(reportType); 311 312 return nResult; 313 } 314 315 EXTERN_C 316 int __cdecl _CrtDbgReportW(int reportType, const wchar_t *filename, int linenumber, const wchar_t *moduleName, const wchar_t *format, ...) 317 { 318 wchar_t szFormatted[DBGRPT_MAX_BUFFER_SIZE+1] = {0}; // The user provided message 319 wchar_t szCompleteMessage[(DBGRPT_MAX_BUFFER_SIZE+1)*2] = {0}; // The output for debug / file 320 321 // Check for recursive _CrtDbgReportW calls, and validate reportType 322 if (!_CrtEnterDbgReport(reportType, filename, linenumber)) 323 return -1; 324 325 if (filename) 326 { 327 _snwprintf(szCompleteMessage, DBGRPT_MAX_BUFFER_SIZE, L"%s(%d) : ", filename, linenumber); 328 } 329 330 if (format) 331 { 332 va_list arglist; 333 va_start(arglist, format); 334 int len = _vsnwprintf(szFormatted, DBGRPT_MAX_BUFFER_SIZE - 2 - sizeof(DBGRPT_ASSERT_PREFIX_MESSAGE), format, arglist); 335 va_end(arglist); 336 337 if (len < 0) 338 { 339 wcscpy(szFormatted, _CRT_WIDE(DBGRPT_STRING_TOO_LONG)); 340 } 341 342 if (reportType == _CRT_ASSERT) 343 wcscat(szCompleteMessage, _CRT_WIDE(DBGRPT_ASSERT_PREFIX_MESSAGE)); 344 wcscat(szCompleteMessage, szFormatted); 345 } 346 else if (reportType == _CRT_ASSERT) 347 { 348 wcscat(szCompleteMessage, _CRT_WIDE(DBGRPT_ASSERT_PREFIX_NOMESSAGE)); 349 } 350 351 if (reportType == _CRT_ASSERT) 352 { 353 if (_CrtModeOutputFormat[reportType] & _CRTDBG_MODE_FILE) 354 wcscat(szCompleteMessage, L"\r"); 355 wcscat(szCompleteMessage, L"\n"); 356 } 357 358 // FIXME: Handle user report hooks here 359 360 int nResult = _CrtHandleDbgReport(reportType, szCompleteMessage, szFormatted, filename, linenumber, moduleName); 361 362 _CrtLeaveDbgReport(reportType); 363 364 return nResult; 365 } 366 367 368 //#endif // _DEBUG 369