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 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 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 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 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 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 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 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