436a18bf30e79326318fd1e7bf961f24dbaf55f4
[reactos.git] / base / applications / drwtsn32 / main.cpp
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
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
46 static bool SortModules(const ModuleData& left, const ModuleData& right)
47 {
48 return left.BaseAddress < right.BaseAddress;
49 }
50
51
52 void PrintBugreport(FILE* output, DumpData& data)
53 {
54 PrintSystemInfo(output, data);
55 xfprintf(output, NEWLINE "*----> Task List <----*" NEWLINE NEWLINE);
56 HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
57 if (hSnap != INVALID_HANDLE_VALUE)
58 {
59 PROCESSENTRY32 pe;
60 pe.dwSize = sizeof(pe);
61 if (Process32First(hSnap, &pe))
62 {
63 do
64 {
65 xfprintf(output, "%5d: %ls" NEWLINE, pe.th32ProcessID, pe.szExeFile);
66 } while (Process32Next(hSnap, &pe));
67 }
68 CloseHandle(hSnap);
69 }
70
71 xfprintf(output, NEWLINE "*----> Module List <----*" NEWLINE NEWLINE);
72 std::sort(data.Modules.begin(), data.Modules.end(), SortModules);
73
74 ModuleData mainModule(NULL);
75 mainModule.Update(data.ProcessHandle);
76 xfprintf(output, "(%p - %p) %ls" NEWLINE,
77 mainModule.BaseAddress,
78 (PBYTE)mainModule.BaseAddress + mainModule.Size,
79 data.ProcessPath.c_str());
80
81 for (size_t n = 0; n < data.Modules.size(); ++n)
82 {
83 ModuleData& mod = data.Modules[n];
84 if (!mod.Unloaded)
85 {
86 mod.Update(data.ProcessHandle);
87 xfprintf(output, "(%p - %p) %s" NEWLINE,
88 mod.BaseAddress,
89 (PBYTE)mod.BaseAddress + mod.Size,
90 mod.ModuleName.c_str());
91 }
92 }
93
94 BeginStackBacktrace(data);
95 for (ThreadMap::iterator it = data.Threads.begin(); it != data.Threads.end(); ++it)
96 {
97 it->second.Update();
98
99 xfprintf(output, NEWLINE "State Dump for Thread Id 0x%x" NEWLINE NEWLINE, it->first);
100 const CONTEXT& ctx = it->second.Context;
101 if ((ctx.ContextFlags & CONTEXT_INTEGER) == CONTEXT_INTEGER)
102 {
103 #if defined(_M_IX86)
104 xfprintf(output, "eax:%p ebx:%p ecx:%p edx:%p esi:%p edi:%p" NEWLINE,
105 ctx.Eax, ctx.Ebx, ctx.Ecx, ctx.Edx, ctx.Esi, ctx.Edi);
106 #elif defined(_M_AMD64)
107 xfprintf(output, "rax:%p rbx:%p rcx:%p rdx:%p rsi:%p rdi:%p" NEWLINE,
108 ctx.Rax, ctx.Rbx, ctx.Rcx, ctx.Rdx, ctx.Rsi, ctx.Rdi);
109 xfprintf(output, "r8:%p r9:%p r10:%p r11:%p r12:%p r13:%p r14:%p r15:%p" NEWLINE,
110 ctx.R8, ctx.R9, ctx.R10, ctx.R11, ctx.R12, ctx.R13, ctx.R14, ctx.R15);
111 #else
112 #error Unknown architecture
113 #endif
114 }
115 if ((ctx.ContextFlags & CONTEXT_CONTROL) == CONTEXT_CONTROL)
116 {
117 #if defined(_M_IX86)
118 xfprintf(output, "eip:%p esp:%p ebp:%p" NEWLINE,
119 ctx.Eip, ctx.Esp, ctx.Ebp);
120 #elif defined(_M_AMD64)
121 xfprintf(output, "rip:%p rsp:%p rbp:%p" NEWLINE,
122 ctx.Rip, ctx.Rsp, ctx.Rbp);
123 #else
124 #error Unknown architecture
125 #endif
126 }
127 if ((ctx.ContextFlags & CONTEXT_DEBUG_REGISTERS) == CONTEXT_DEBUG_REGISTERS)
128 {
129 #if defined(_M_IX86) || defined(_M_AMD64)
130 xfprintf(output, "dr0:%p dr1:%p dr2:%p dr3:%p dr6:%p dr7:%p" NEWLINE,
131 ctx.Dr0, ctx.Dr1, ctx.Dr2, ctx.Dr3, ctx.Dr6, ctx.Dr7);
132 #else
133 #error Unknown architecture
134 #endif
135 }
136
137 PrintStackBacktrace(output, data, it->second);
138 }
139 EndStackBacktrace(data);
140 }
141
142
143 int abort(FILE* output, int err)
144 {
145 if (output != stdout)
146 fclose(output);
147 else
148 _getch();
149
150 return err;
151 }
152
153 std::wstring Settings_GetOutputPath(void)
154 {
155 WCHAR Buffer[MAX_PATH] = L"";
156 ULONG BufferSize = _countof(Buffer);
157 BOOL UseDefaultPath = FALSE;
158
159 CRegKey key;
160 if (key.Open(HKEY_CURRENT_USER, L"SOFTWARE\\ReactOS\\Crash Reporter", KEY_READ) != ERROR_SUCCESS)
161 {
162 UseDefaultPath = TRUE;
163 }
164
165 if (key.QueryStringValue(L"Dump Directory", Buffer, &BufferSize) != ERROR_SUCCESS)
166 {
167 UseDefaultPath = TRUE;
168 }
169
170 if (UseDefaultPath)
171 {
172 if (FAILED(SHGetFolderPathW(NULL, CSIDL_DESKTOP, NULL, SHGFP_TYPE_CURRENT, Buffer)))
173 {
174 return std::wstring();
175 }
176 }
177
178 return std::wstring(Buffer);
179 }
180
181 BOOL Settings_GetShouldWriteDump(void)
182 {
183 CRegKey key;
184 if (key.Open(HKEY_CURRENT_USER, L"SOFTWARE\\ReactOS\\Crash Reporter", KEY_READ) != ERROR_SUCCESS)
185 {
186 return FALSE;
187 }
188
189 DWORD Value;
190 if (key.QueryDWORDValue(L"Minidump", Value) != ERROR_SUCCESS)
191 {
192 return FALSE;
193 }
194
195 return (Value != 0);
196 }
197
198 HRESULT WriteMinidump(LPCWSTR LogFilePath, DumpData& data)
199 {
200 HRESULT hr = S_OK;
201
202 WCHAR DumpFilePath[MAX_PATH] = L"";
203 StringCchCopyW(DumpFilePath, _countof(DumpFilePath), LogFilePath);
204 PathRemoveExtensionW(DumpFilePath);
205 PathAddExtensionW(DumpFilePath, L".dmp");
206
207 HANDLE hDumpFile = CreateFileW(DumpFilePath, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
208 if (hDumpFile == INVALID_HANDLE_VALUE)
209 {
210 return HRESULT_FROM_WIN32(GetLastError());
211 }
212
213 ThreadData& Thread = data.Threads[data.ThreadID];
214 Thread.Update();
215 PCONTEXT ContextPointer = &Thread.Context;
216
217 MINIDUMP_EXCEPTION_INFORMATION DumpExceptionInfo = {0};
218 EXCEPTION_POINTERS ExceptionPointers = {0};
219 ExceptionPointers.ExceptionRecord = &data.ExceptionInfo.ExceptionRecord;
220 ExceptionPointers.ContextRecord = ContextPointer;
221
222 DumpExceptionInfo.ThreadId = data.ThreadID;
223 DumpExceptionInfo.ExceptionPointers = &ExceptionPointers;
224 DumpExceptionInfo.ClientPointers = FALSE;
225
226 BOOL DumpSucceeded = MiniDumpWriteDump(data.ProcessHandle, data.ProcessID, hDumpFile, MiniDumpNormal, &DumpExceptionInfo, NULL, NULL);
227 if (!DumpSucceeded)
228 {
229 // According to MSDN, this value is already an HRESULT, so don't convert it again.
230 hr = GetLastError();
231 }
232
233 CloseHandle(hDumpFile);
234 return hr;
235 }
236
237 int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR cmdLine, INT)
238 {
239 int argc;
240 WCHAR **argv = CommandLineToArgvW(cmdLine, &argc);
241
242 DWORD pid = 0;
243 WCHAR Filename[50];
244 FILE* output = NULL;
245 SYSTEMTIME st;
246 DumpData data;
247
248
249 for (int n = 0; n < argc; ++n)
250 {
251 WCHAR* arg = argv[n];
252
253 if (!wcscmp(arg, L"-i"))
254 {
255 /* FIXME: Installs as the postmortem debugger. */
256 }
257 else if (!wcscmp(arg, L"-g"))
258 {
259 }
260 else if (!wcscmp(arg, L"-p"))
261 {
262 if (n + 1 < argc)
263 {
264 pid = wcstoul(argv[n+1], NULL, 10);
265 n++;
266 }
267 }
268 else if (!wcscmp(arg, L"-e"))
269 {
270 if (n + 1 < argc)
271 {
272 data.Event = (HANDLE)wcstoul(argv[n+1], NULL, 10);
273 n++;
274 }
275 }
276 else if (!wcscmp(arg, L"-?"))
277 {
278 MessageBoxA(NULL, szUsage, "ReactOS Crash Reporter", MB_OK);
279 return abort(output, 0);
280 }
281 else if (!wcscmp(arg, L"/?"))
282 {
283 xfprintf(stdout, "%s\n", szUsage);
284 return abort(stdout, 0);
285 }
286 }
287
288 if (!pid)
289 {
290 MessageBoxA(NULL, szUsage, "ReactOS Crash Reporter", MB_OK);
291 return abort(stdout, 0);
292 }
293
294 GetLocalTime(&st);
295
296 std::wstring OutputPath = Settings_GetOutputPath();
297 BOOL HasPath = (OutputPath.size() != 0);
298
299 if (!PathIsDirectoryW(OutputPath.c_str()))
300 {
301 int res = SHCreateDirectoryExW(NULL, OutputPath.c_str(), NULL);
302 if (res != ERROR_SUCCESS && res != ERROR_ALREADY_EXISTS)
303 {
304 xfprintf(stdout, "Could not create output directory, not writing dump\n");
305 MessageBoxA(NULL, "Could not create directory to write crash report.", "ReactOS Crash Reporter", MB_ICONERROR | MB_OK);
306 return abort(stdout, 0);
307 }
308 }
309
310 if (HasPath &&
311 SUCCEEDED(StringCchPrintfW(Filename, _countof(Filename), L"Appcrash_%d-%02d-%02d_%02d-%02d-%02d.txt",
312 st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond)))
313 {
314 OutputPath += L"\\";
315 OutputPath += Filename;
316 output = _wfopen(OutputPath.c_str(), L"wb");
317 }
318 if (!output)
319 output = stdout;
320
321
322 if (!DebugActiveProcess(pid))
323 return abort(output, -2);
324
325 /* We should not kill it? */
326 DebugSetProcessKillOnExit(FALSE);
327
328 DEBUG_EVENT evt;
329 if (!WaitForDebugEvent(&evt, 30000))
330 return abort(output, -3);
331
332 assert(evt.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT);
333
334 while (UpdateFromEvent(evt, data))
335 {
336 ContinueDebugEvent(evt.dwProcessId, evt.dwThreadId, DBG_CONTINUE);
337
338 if (!WaitForDebugEvent(&evt, 30000))
339 return abort(output, -4);
340 }
341
342 PrintBugreport(output, data);
343 if (Settings_GetShouldWriteDump() && HasPath)
344 {
345 WriteMinidump(OutputPath.c_str(), data);
346 }
347
348 TerminateProcess(data.ProcessHandle, data.ExceptionInfo.ExceptionRecord.ExceptionCode);
349
350 CStringW FormattedMessage;
351 FormattedMessage.Format(IDS_USER_ALERT_MESSAGE, data.ProcessName.c_str(), OutputPath.c_str());
352 CStringW DialogTitle;
353 DialogTitle.LoadString(hInstance, IDS_APP_TITLE);
354
355 MessageBoxW(NULL, FormattedMessage.GetString(), DialogTitle.GetString(), MB_OK);
356
357 return abort(output, 0);
358 }