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