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