xref: /reactos/sdk/lib/crt/misc/dbgrpt.cpp (revision 161eb5d3)
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