xref: /reactos/base/applications/drwtsn32/main.cpp (revision 1f44552d)
1 /*
2  * PROJECT:     Dr. Watson crash reporter
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Entrypoint / main print function
5  * COPYRIGHT:   Copyright 2017 Mark Jansen <mark.jansen@reactos.org>
6  */
7 
8 #include "precomp.h"
9 #include <winuser.h>
10 #include <algorithm>
11 #include <shlobj.h>
12 #include <shlwapi.h>
13 #include <tchar.h>
14 #include <strsafe.h>
15 #include <tlhelp32.h>
16 #include <dbghelp.h>
17 #include <conio.h>
18 #include <atlbase.h>
19 #include <atlstr.h>
20 #include "resource.h"
21 
22 
23 static const char szUsage[] = "Usage: DrWtsn32 [-i] [-g] [-p dddd] [-e dddd] [-?]\n"
24                               "    -i: Install DrWtsn32 as the postmortem debugger\n"
25                               "    -g: Ignored, Provided for compatibility with WinDbg and CDB.\n"
26                               "    -p dddd: Attach to process dddd.\n"
27                               "    -e dddd: Signal the event dddd.\n"
28                               "    -?: This help.\n";
29 
30 extern "C"
31 NTSYSAPI ULONG NTAPI vDbgPrintEx(_In_ ULONG ComponentId, _In_ ULONG Level, _In_z_ PCCH Format, _In_ va_list ap);
32 #define DPFLTR_ERROR_LEVEL 0
33 
xfprintf(FILE * stream,const char * fmt,...)34 void xfprintf(FILE* stream, const char *fmt, ...)
35 {
36     va_list ap;
37 
38     va_start(ap, fmt);
39     vfprintf(stream, fmt, ap);
40     vDbgPrintEx(-1, DPFLTR_ERROR_LEVEL, fmt, ap);
41     va_end(ap);
42 }
43 
44 
45 
SortModules(const ModuleData & left,const ModuleData & right)46 static bool SortModules(const ModuleData& left, const ModuleData& right)
47 {
48     return left.BaseAddress < right.BaseAddress;
49 }
50 
PrintThread(FILE * output,DumpData & data,DWORD tid,ThreadData & thread)51 static void PrintThread(FILE* output, DumpData& data, DWORD tid, ThreadData& thread)
52 {
53     thread.Update();
54 
55     xfprintf(output, NEWLINE "State Dump for Thread Id 0x%x%s" NEWLINE NEWLINE, tid,
56              (tid == data.ThreadID) ? " (CRASH)" : "");
57 
58     const CONTEXT& ctx = thread.Context;
59     if ((ctx.ContextFlags & CONTEXT_INTEGER) == CONTEXT_INTEGER)
60     {
61 #if defined(_M_IX86)
62         xfprintf(output, "eax:%p ebx:%p ecx:%p edx:%p esi:%p edi:%p" NEWLINE,
63                  ctx.Eax, ctx.Ebx, ctx.Ecx, ctx.Edx, ctx.Esi, ctx.Edi);
64 #elif defined(_M_AMD64)
65         xfprintf(output, "rax:%p rbx:%p rcx:%p rdx:%p rsi:%p rdi:%p rbp:%p rsp:%p" NEWLINE,
66                  ctx.Rax, ctx.Rbx, ctx.Rcx, ctx.Rdx, ctx.Rsi, ctx.Rdi, ctx.Rbp, ctx.Rsp);
67         xfprintf(output, "r8:%p r9:%p r10:%p r11:%p r12:%p r13:%p r14:%p r15:%p" NEWLINE,
68                  ctx.R8, ctx.R9, ctx.R10, ctx.R11, ctx.R12, ctx.R13, ctx.R14, ctx.R15);
69 #elif defined(_M_ARM)
70         xfprintf(output, "r0:%p r1:%p r2:%p r3:%p r4:%p r5:%p r6:%p" NEWLINE,
71                  ctx.R0, ctx.R1, ctx.R2, ctx.R3, ctx.R4, ctx.R5, ctx.R6);
72         xfprintf(output, "r7:%p r8:%p r9:%p r10:%p r11:%p r12:%p" NEWLINE,
73                  ctx.R7, ctx.R8, ctx.R9, ctx.R10, ctx.R11, ctx.R12);
74 #else
75 #error Unknown architecture
76 #endif
77     }
78 
79     if ((ctx.ContextFlags & CONTEXT_CONTROL) == CONTEXT_CONTROL)
80     {
81 #if defined(_M_IX86)
82         xfprintf(output, "eip:%p esp:%p ebp:%p" NEWLINE,
83                  ctx.Eip, ctx.Esp, ctx.Ebp);
84 #elif defined(_M_AMD64)
85         xfprintf(output, "rip:%p rsp:%p rbp:%p" NEWLINE,
86                  ctx.Rip, ctx.Rsp, ctx.Rbp);
87 #elif defined(_M_ARM)
88         xfprintf(output, "sp:%p lr:%p pc:%p cpsr:%p" NEWLINE,
89                  ctx.Sp, ctx.Lr, ctx.Pc, ctx.Cpsr);
90 #else
91 #error Unknown architecture
92 #endif
93     }
94 
95     if ((ctx.ContextFlags & CONTEXT_DEBUG_REGISTERS) == CONTEXT_DEBUG_REGISTERS)
96     {
97 #if defined(_M_IX86) || defined(_M_AMD64)
98         xfprintf(output, "dr0:%p dr1:%p dr2:%p dr3:%p dr6:%p dr7:%p" NEWLINE,
99                  ctx.Dr0, ctx.Dr1, ctx.Dr2, ctx.Dr3, ctx.Dr6, ctx.Dr7);
100 #elif defined(_M_ARM)
101         for (int n = 0; n < ARM_MAX_BREAKPOINTS; ++n)
102             xfprintf(output, "Bvr%d:%p%s", n, ctx.Bvr[n], ((n + 1) == ARM_MAX_BREAKPOINTS) ? NEWLINE : " ");
103         for (int n = 0; n < ARM_MAX_BREAKPOINTS; ++n)
104             xfprintf(output, "Bcr%d:%p%s", n, ctx.Bcr[n], ((n + 1) == ARM_MAX_BREAKPOINTS) ? NEWLINE : " ");
105 
106         for (int n = 0; n < ARM_MAX_WATCHPOINTS; ++n)
107             xfprintf(output, "Wvr%d:%p%s", n, ctx.Wvr[n], ((n + 1) == ARM_MAX_WATCHPOINTS) ? NEWLINE : " ");
108         for (int n = 0; n < ARM_MAX_WATCHPOINTS; ++n)
109             xfprintf(output, "Wcr%d:%p%s", n, ctx.Wcr[n], ((n + 1) == ARM_MAX_WATCHPOINTS) ? NEWLINE : " ");
110 #else
111 #error Unknown architecture
112 #endif
113     }
114 
115     PrintStackBacktrace(output, data, thread);
116 }
117 
PrintBugreport(FILE * output,DumpData & data)118 void PrintBugreport(FILE* output, DumpData& data)
119 {
120     PrintSystemInfo(output, data);
121     xfprintf(output, NEWLINE "*----> Task List <----*" NEWLINE NEWLINE);
122     HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
123     if (hSnap != INVALID_HANDLE_VALUE)
124     {
125         PROCESSENTRY32 pe;
126         pe.dwSize = sizeof(pe);
127         if (Process32First(hSnap, &pe))
128         {
129             do
130             {
131                 xfprintf(output, "%5d: %ls" NEWLINE, pe.th32ProcessID, pe.szExeFile);
132             } while (Process32Next(hSnap, &pe));
133         }
134         CloseHandle(hSnap);
135     }
136 
137     xfprintf(output, NEWLINE "*----> Module List <----*" NEWLINE NEWLINE);
138     std::sort(data.Modules.begin(), data.Modules.end(), SortModules);
139 
140     ModuleData mainModule(NULL);
141     mainModule.Update(data.ProcessHandle);
142     xfprintf(output, "(%p - %p) %ls" NEWLINE,
143              mainModule.BaseAddress,
144              (PBYTE)mainModule.BaseAddress + mainModule.Size,
145              data.ProcessPath.c_str());
146 
147     for (size_t n = 0; n < data.Modules.size(); ++n)
148     {
149         ModuleData& mod = data.Modules[n];
150         if (!mod.Unloaded)
151         {
152             mod.Update(data.ProcessHandle);
153             xfprintf(output, "(%p - %p) %s" NEWLINE,
154                      mod.BaseAddress,
155                      (PBYTE)mod.BaseAddress + mod.Size,
156                      mod.ModuleName.c_str());
157         }
158     }
159 
160     BeginStackBacktrace(data);
161 
162     // First print the thread that crashed
163     ThreadMap::iterator crash = data.Threads.find(data.ThreadID);
164     if (crash != data.Threads.end())
165         PrintThread(output, data, crash->first, crash->second);
166 
167     // Print the other threads
168     for (ThreadMap::iterator it = data.Threads.begin(); it != data.Threads.end(); ++it)
169     {
170         if (it->first != data.ThreadID)
171             PrintThread(output, data, it->first, it->second);
172     }
173     EndStackBacktrace(data);
174 }
175 
176 
abort(FILE * output,int err)177 int abort(FILE* output, int err)
178 {
179     if (output != stdout)
180         fclose(output);
181     else
182         _getch();
183 
184     return err;
185 }
186 
Settings_GetOutputPath(void)187 std::wstring Settings_GetOutputPath(void)
188 {
189     WCHAR Buffer[MAX_PATH] = L"";
190     ULONG BufferSize = _countof(Buffer);
191     BOOL UseDefaultPath = TRUE;
192 
193     CRegKey key;
194     if (key.Open(HKEY_CURRENT_USER, L"SOFTWARE\\ReactOS\\Crash Reporter", KEY_READ) == ERROR_SUCCESS &&
195         key.QueryStringValue(L"Dump Directory", Buffer, &BufferSize) == ERROR_SUCCESS)
196     {
197         UseDefaultPath = FALSE;
198     }
199 
200     if (UseDefaultPath)
201     {
202         if (FAILED(SHGetFolderPathW(NULL, CSIDL_DESKTOP, NULL, SHGFP_TYPE_CURRENT, Buffer)))
203         {
204             return std::wstring();
205         }
206     }
207 
208     return std::wstring(Buffer);
209 }
210 
Settings_GetShouldWriteDump(void)211 BOOL Settings_GetShouldWriteDump(void)
212 {
213     CRegKey key;
214     if (key.Open(HKEY_CURRENT_USER, L"SOFTWARE\\ReactOS\\Crash Reporter", KEY_READ) != ERROR_SUCCESS)
215     {
216         return FALSE;
217     }
218 
219     DWORD Value;
220     if (key.QueryDWORDValue(L"Minidump", Value) != ERROR_SUCCESS)
221     {
222         return FALSE;
223     }
224 
225     return (Value != 0);
226 }
227 
WriteMinidump(LPCWSTR LogFilePath,DumpData & data)228 HRESULT WriteMinidump(LPCWSTR LogFilePath, DumpData& data)
229 {
230     HRESULT hr = S_OK;
231 
232     WCHAR DumpFilePath[MAX_PATH] = L"";
233     StringCchCopyW(DumpFilePath, _countof(DumpFilePath), LogFilePath);
234     PathRemoveExtensionW(DumpFilePath);
235     PathAddExtensionW(DumpFilePath, L".dmp");
236 
237     HANDLE hDumpFile = CreateFileW(DumpFilePath, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
238     if (hDumpFile == INVALID_HANDLE_VALUE)
239     {
240         return HRESULT_FROM_WIN32(GetLastError());
241     }
242 
243     ThreadData& Thread = data.Threads[data.ThreadID];
244     Thread.Update();
245     PCONTEXT ContextPointer = &Thread.Context;
246 
247     MINIDUMP_EXCEPTION_INFORMATION DumpExceptionInfo = {0};
248     EXCEPTION_POINTERS ExceptionPointers = {0};
249     ExceptionPointers.ExceptionRecord = &data.ExceptionInfo.ExceptionRecord;
250     ExceptionPointers.ContextRecord = ContextPointer;
251 
252     DumpExceptionInfo.ThreadId = data.ThreadID;
253     DumpExceptionInfo.ExceptionPointers = &ExceptionPointers;
254     DumpExceptionInfo.ClientPointers = FALSE;
255 
256     BOOL DumpSucceeded = MiniDumpWriteDump(data.ProcessHandle, data.ProcessID, hDumpFile, MiniDumpNormal, &DumpExceptionInfo, NULL, NULL);
257     if (!DumpSucceeded)
258     {
259         // According to MSDN, this value is already an HRESULT, so don't convert it again.
260         hr = GetLastError();
261     }
262 
263     CloseHandle(hDumpFile);
264     return hr;
265 }
266 
wWinMain(HINSTANCE hInstance,HINSTANCE,LPWSTR cmdLine,INT)267 int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR cmdLine, INT)
268 {
269     int argc;
270     WCHAR **argv = CommandLineToArgvW(GetCommandLineW(), &argc);
271 
272     DWORD pid = 0;
273     WCHAR Filename[50];
274     FILE* output = NULL;
275     SYSTEMTIME st;
276     DumpData data;
277 
278 
279     for (int n = 0; n < argc; ++n)
280     {
281         WCHAR* arg = argv[n];
282 
283         if (!wcscmp(arg, L"-i"))
284         {
285             /* FIXME: Installs as the postmortem debugger. */
286         }
287         else if (!wcscmp(arg, L"-g"))
288         {
289         }
290         else if (!wcscmp(arg, L"-p"))
291         {
292             if (n + 1 < argc)
293             {
294                 pid = wcstoul(argv[n+1], NULL, 10);
295                 n++;
296             }
297         }
298         else if (!wcscmp(arg, L"-e"))
299         {
300             if (n + 1 < argc)
301             {
302                 data.Event = (HANDLE)(ULONG_PTR)_wcstoui64(argv[n+1], NULL, 10);
303                 n++;
304             }
305         }
306         else if (!wcscmp(arg, L"-?"))
307         {
308             MessageBoxA(NULL, szUsage, "ReactOS Crash Reporter", MB_OK);
309             return abort(output, 0);
310         }
311         else if (!wcscmp(arg, L"/?"))
312         {
313             xfprintf(stdout, "%s\n", szUsage);
314             return abort(stdout, 0);
315         }
316     }
317 
318     if (!pid)
319     {
320         MessageBoxA(NULL, szUsage, "ReactOS Crash Reporter", MB_OK);
321         return abort(stdout, 0);
322     }
323 
324     GetLocalTime(&st);
325 
326     std::wstring OutputPath = Settings_GetOutputPath();
327     BOOL HasPath = (OutputPath.size() != 0);
328 
329     if (!PathIsDirectoryW(OutputPath.c_str()))
330     {
331         int res = SHCreateDirectoryExW(NULL, OutputPath.c_str(), NULL);
332         if (res != ERROR_SUCCESS && res != ERROR_ALREADY_EXISTS)
333         {
334             xfprintf(stdout, "Could not create output directory, not writing dump\n");
335             MessageBoxA(NULL, "Could not create directory to write crash report.", "ReactOS Crash Reporter", MB_ICONERROR | MB_OK);
336             return abort(stdout, 0);
337         }
338     }
339 
340     if (HasPath &&
341         SUCCEEDED(StringCchPrintfW(Filename, _countof(Filename), L"Appcrash_%d-%02d-%02d_%02d-%02d-%02d.txt",
342                                    st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond)))
343     {
344         OutputPath += L"\\";
345         OutputPath += Filename;
346         output = _wfopen(OutputPath.c_str(), L"wb");
347     }
348     if (!output)
349         output = stdout;
350 
351 
352     if (!DebugActiveProcess(pid))
353         return abort(output, -2);
354 
355     /* We should not kill it? */
356     DebugSetProcessKillOnExit(FALSE);
357 
358     DEBUG_EVENT evt;
359     if (!WaitForDebugEvent(&evt, 30000))
360         return abort(output, -3);
361 
362     assert(evt.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT);
363 
364     while (UpdateFromEvent(evt, data))
365     {
366         ContinueDebugEvent(evt.dwProcessId, evt.dwThreadId, DBG_CONTINUE);
367 
368         if (!WaitForDebugEvent(&evt, 30000))
369             return abort(output, -4);
370     }
371 
372     PrintBugreport(output, data);
373     if (Settings_GetShouldWriteDump() && HasPath)
374     {
375         WriteMinidump(OutputPath.c_str(), data);
376     }
377 
378     TerminateProcess(data.ProcessHandle, data.ExceptionInfo.ExceptionRecord.ExceptionCode);
379 
380     CStringW FormattedMessage;
381     FormattedMessage.Format(IDS_USER_ALERT_MESSAGE, data.ProcessName.c_str(), OutputPath.c_str());
382     CStringW DialogTitle;
383     DialogTitle.LoadString(hInstance, IDS_APP_TITLE);
384 
385     MessageBoxW(NULL, FormattedMessage.GetString(), DialogTitle.GetString(), MB_OK);
386 
387     return abort(output, 0);
388 }
389