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);
StringLengthdbgrpt_char_traits69 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);
StringLengthdbgrpt_char_traits82 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
OutputDebugString(const char * message)110 inline void achar_traits::OutputDebugString(const char* message)
111 {
112 OutputDebugStringA(message);
113 }
114
OutputDebugString(const wchar_t * message)115 inline void wchar_traits::OutputDebugString(const wchar_t* message)
116 {
117 OutputDebugStringW(message);
118 }
119
120 static
_CrtGetUser32()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
_CrtGetMessageBox()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>
_CrtDbgReportWindow(int reportType,const char_t * filename,int linenumber,const char_t * moduleName,const char_t * message)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>
_CrtEnterDbgReport(int reportType,const char_t * filename,int linenumber)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
_CrtLeaveDbgReport(int reportType)228 void _CrtLeaveDbgReport(int reportType)
229 {
230 if (reportType == _CRT_ASSERT)
231 _InterlockedDecrement(&_CrtInAssert);
232 }
233
234 EXTERN_C
_CrtSetReportMode(int reportType,int reportMode)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
_CrtSetReportFile(int reportType,_HFILE reportFile)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>
_CrtDbgReportToFile(HANDLE hFile,const char_t * szMsg)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>
_CrtHandleDbgReport(int reportType,const char_t * szCompleteMessage,const char_t * szFormatted,const char_t * filename,int linenumber,const char_t * moduleName)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
_VCrtDbgReportA(int reportType,const char * filename,int linenumber,const char * moduleName,const char * format,va_list arglist)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
_VCrtDbgReportW(int reportType,const wchar_t * filename,int linenumber,const wchar_t * moduleName,const wchar_t * format,va_list arglist)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
_CrtDbgReportV(int reportType,const char * filename,int linenumber,const char * moduleName,const char * format,va_list arglist)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
_CrtDbgReportWV(int reportType,const wchar_t * filename,int linenumber,const wchar_t * moduleName,const wchar_t * format,va_list arglist)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
_CrtDbgReport(int reportType,const char * filename,int linenumber,const char * moduleName,const char * format,...)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
_CrtDbgReportW(int reportType,const wchar_t * filename,int linenumber,const wchar_t * moduleName,const wchar_t * format,...)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