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