[DRWTSN32] Implement basic crash report functionality
authorMark Jansen <mark.jansen@reactos.org>
Sat, 6 Jan 2018 10:47:54 +0000 (11:47 +0100)
committerGitHub <noreply@github.com>
Sat, 6 Jan 2018 10:47:54 +0000 (11:47 +0100)
On application crash, drwtsn32 will attach to the application and try to get a dump, consisting of:
- List of loaded modules
- List of loaded threads
- Per thread, a stacktrace
- Per thread, a small hexdump from the stack
- Per thread, a dump of the most common registers

This dump is saved to the desktop, and the user is notified of the dump being dropped there.

CORE-14180
#145

base/applications/CMakeLists.txt
base/applications/drwtsn32/CMakeLists.txt [new file with mode: 0644]
base/applications/drwtsn32/drwtsn32.cpp [new file with mode: 0644]
base/applications/drwtsn32/drwtsn32.h [new file with mode: 0644]
base/applications/drwtsn32/main.cpp [new file with mode: 0644]
base/applications/drwtsn32/precomp.h [new file with mode: 0644]
base/applications/drwtsn32/stacktrace.cpp [new file with mode: 0644]
base/applications/drwtsn32/sysinfo.cpp [new file with mode: 0644]

index d25fa3e..2af017d 100644 (file)
@@ -6,6 +6,7 @@ add_subdirectory(charmap)
 add_subdirectory(clipbrd)
 add_subdirectory(cmdutils)
 add_subdirectory(control)
+add_subdirectory(drwtsn32)
 add_subdirectory(dxdiag)
 add_subdirectory(extrac32)
 add_subdirectory(findstr)
diff --git a/base/applications/drwtsn32/CMakeLists.txt b/base/applications/drwtsn32/CMakeLists.txt
new file mode 100644 (file)
index 0000000..3a7e93f
--- /dev/null
@@ -0,0 +1,18 @@
+
+PROJECT(drwtsn32)
+
+set_cpp(WITH_RUNTIME WITH_EXCEPTIONS WITH_STL)
+
+list(APPEND CPP_SOURCE
+    drwtsn32.cpp
+    main.cpp
+    stacktrace.cpp
+    sysinfo.cpp
+    drwtsn32.h
+    precomp.h)
+
+add_executable(drwtsn32 ${CPP_SOURCE})
+add_pch(drwtsn32 precomp.h CPP_SOURCE)
+set_module_type(drwtsn32 win32gui)
+add_importlibs(drwtsn32 dbghelp psapi advapi32 shell32 msvcrt user32 kernel32 ntdll)
+add_cd_file(TARGET drwtsn32 DESTINATION reactos/system32 FOR all)
diff --git a/base/applications/drwtsn32/drwtsn32.cpp b/base/applications/drwtsn32/drwtsn32.cpp
new file mode 100644 (file)
index 0000000..b667dd7
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * PROJECT:     Dr. Watson crash reporter
+ * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
+ * PURPOSE:     Debug loop
+ * COPYRIGHT:   Copyright 2017 Mark Jansen (mark.jansen@reactos.org)
+ */
+
+#include "precomp.h"
+#include <psapi.h>
+
+#define MS_VC_EXCEPTION_THREAD_NAME 0x406d1388
+
+ModuleData::ModuleData(void* addr)
+{
+    BaseAddress = addr;
+    Size = 0;
+    Unloaded = false;
+}
+
+void ModuleData::Update(HANDLE hProcess)
+{
+    MODULEINFO mi = {0};
+    GetModuleInformation(hProcess, (HMODULE)BaseAddress, &mi, sizeof(mi));
+    assert(BaseAddress == mi.lpBaseOfDll);
+    Size = mi.SizeOfImage;
+
+    ModuleName.resize(MAX_PATH);
+    DWORD dwLen = GetModuleFileNameExA(hProcess, (HMODULE)BaseAddress, &ModuleName[0], ModuleName.size());
+    ModuleName.resize(dwLen);
+}
+
+
+ThreadData::ThreadData(HANDLE handle)
+    : Handle(handle)
+{
+    memset(&Context, 0, sizeof(Context));
+}
+
+void ThreadData::Update()
+{
+    Context.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL | CONTEXT_DEBUG_REGISTERS;
+    GetThreadContext(Handle, &Context);
+}
+
+DumpData::DumpData()
+    :ProcessID(0)
+    ,ThreadID(0)
+    ,ProcessHandle(NULL)
+    ,Event(NULL)
+    ,FirstBPHit(false)
+{
+    memset(&ExceptionInfo, 0, sizeof(ExceptionInfo));
+}
+
+
+bool UpdateFromEvent(DEBUG_EVENT& evt, DumpData& data)
+{
+    switch(evt.dwDebugEventCode)
+    {
+    case CREATE_PROCESS_DEBUG_EVENT:
+    {
+        data.ProcessPath.resize(MAX_PATH);
+        DWORD len = GetModuleFileNameExA(evt.u.CreateProcessInfo.hProcess, NULL, &data.ProcessPath[0], data.ProcessPath.size());
+        if (len)
+        {
+            data.ProcessPath.resize(len);
+            std::string::size_type pos = data.ProcessPath.find_last_of("\\/");
+            if (pos != std::string::npos)
+                data.ProcessName = data.ProcessPath.substr(pos+1);
+        }
+        else
+        {
+            data.ProcessPath = "??";
+        }
+        if (data.ProcessName.empty())
+            data.ProcessName = data.ProcessPath;
+
+        CloseHandle(evt.u.CreateProcessInfo.hFile);
+        data.ProcessID = evt.dwProcessId;
+        data.ProcessHandle = evt.u.CreateProcessInfo.hProcess;
+        data.Threads[evt.dwThreadId] = ThreadData(evt.u.CreateProcessInfo.hThread);
+    }
+        break;
+    case CREATE_THREAD_DEBUG_EVENT:
+        data.Threads[evt.dwThreadId] = ThreadData(evt.u.CreateThread.hThread);
+        break;
+    case EXIT_THREAD_DEBUG_EVENT:
+    {
+        ThreadMap::iterator it = data.Threads.find(evt.dwThreadId);
+        if (it != data.Threads.end())
+        {
+            data.Threads.erase(it);
+        }
+    }
+        break;
+    case LOAD_DLL_DEBUG_EVENT:
+        CloseHandle(evt.u.LoadDll.hFile);
+        for (size_t n = 0; n < data.Modules.size(); ++n)
+        {
+            if (data.Modules[n].BaseAddress == evt.u.LoadDll.lpBaseOfDll)
+            {
+                data.Modules[n].Unloaded = false;
+                return true;
+            }
+        }
+        data.Modules.push_back(ModuleData(evt.u.LoadDll.lpBaseOfDll));
+        break;
+    case UNLOAD_DLL_DEBUG_EVENT:
+        for (size_t n = 0; n < data.Modules.size(); ++n)
+        {
+            if (data.Modules[n].BaseAddress == evt.u.UnloadDll.lpBaseOfDll)
+                data.Modules[n].Unloaded = true;
+        }
+        break;
+    case OUTPUT_DEBUG_STRING_EVENT: // ignore
+        break;
+    case EXCEPTION_DEBUG_EVENT:
+        if (evt.u.Exception.dwFirstChance)
+        {
+            switch(evt.u.Exception.ExceptionRecord.ExceptionCode)
+            {
+            case EXCEPTION_BREAKPOINT:
+                if (!data.FirstBPHit)
+                {
+                    data.FirstBPHit = true;
+
+                    if (data.Event)
+                    {
+                        SetEvent(data.Event);
+                        CloseHandle(data.Event);
+                        data.Event = NULL;
+                    }
+                    return true;
+                }
+                break;
+            case MS_VC_EXCEPTION_THREAD_NAME:
+                /* Thread name */
+                return true;
+            case DBG_CONTROL_C:
+            case DBG_CONTROL_BREAK:
+                return true;
+            }
+        }
+        data.ExceptionInfo = evt.u.Exception;
+        data.ThreadID = evt.dwThreadId;
+        return false;
+    case EXIT_PROCESS_DEBUG_EVENT:
+        //assert(FALSE);
+        return false;
+    case RIP_EVENT:
+        //assert(FALSE);
+        return false;
+    default:
+        assert(false);
+    }
+    return true;
+}
+
diff --git a/base/applications/drwtsn32/drwtsn32.h b/base/applications/drwtsn32/drwtsn32.h
new file mode 100644 (file)
index 0000000..4d161ff
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * PROJECT:     Dr. Watson crash reporter
+ * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
+ * PURPOSE:     Project header
+ * COPYRIGHT:   Copyright 2017 Mark Jansen (mark.jansen@reactos.org)
+ */
+
+#pragma once
+
+
+struct ModuleData
+{
+    std::string ModuleName;
+    void *BaseAddress;
+    DWORD Size;
+    bool Unloaded;
+
+
+    ModuleData(void* addr);
+    void Update(HANDLE hProcess);
+};
+
+struct ThreadData
+{
+    HANDLE Handle;
+    CONTEXT Context;
+
+    ThreadData(HANDLE handle = NULL);
+
+    void Update();
+};
+
+typedef std::vector<ModuleData> ModuleList;
+typedef std::map<DWORD, ThreadData> ThreadMap;
+
+class DumpData
+{
+public:
+    std::string ProcessPath;
+    std::string ProcessName;
+    DWORD ProcessID;
+    DWORD ThreadID;
+    HANDLE ProcessHandle;
+    ModuleList Modules;
+    ThreadMap Threads;
+    EXCEPTION_DEBUG_INFO ExceptionInfo;
+    HANDLE Event;
+    bool FirstBPHit;
+
+    DumpData();
+};
+
+#define NEWLINE "\r\n"
+
+/* main.cpp */
+void xfprintf(FILE* stream, const char *fmt, ...);
+
+/* drwtsn32.cpp */
+bool UpdateFromEvent(DEBUG_EVENT& evt, DumpData& data);
+
+/* sysinfo.cpp */
+void PrintSystemInfo(FILE* output, DumpData& data);
+
+/* stacktrace.cpp */
+void BeginStackBacktrace(DumpData& data);
+void PrintStackBacktrace(FILE* output, DumpData& data, ThreadData& thread);
+void EndStackBacktrace(DumpData& data);
+
diff --git a/base/applications/drwtsn32/main.cpp b/base/applications/drwtsn32/main.cpp
new file mode 100644 (file)
index 0000000..2ac1e05
--- /dev/null
@@ -0,0 +1,222 @@
+/*
+ * PROJECT:     Dr. Watson crash reporter
+ * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
+ * PURPOSE:     Entrypoint / main print function
+ * COPYRIGHT:   Copyright 2017 Mark Jansen (mark.jansen@reactos.org)
+ */
+
+#include "precomp.h"
+#include <winuser.h>
+#include <algorithm>
+#include <shlobj.h>
+#include <strsafe.h>
+#include <tlhelp32.h>
+#include <conio.h>
+
+
+static const char szUsage[] = "Usage: DrWtsn32 [-i] [-g] [-p dddd] [-e dddd] [-?]\n"
+                              "    -i: Install DrWtsn32 as the postmortem debugger\n"
+                              "    -g: Ignored, Provided for compatibility with WinDbg and CDB.\n"
+                              "    -p dddd: Attach to process dddd.\n"
+                              "    -e dddd: Signal the event dddd.\n"
+                              "    -?: This help.\n";
+
+extern "C"
+NTSYSAPI ULONG NTAPI vDbgPrintEx(_In_ ULONG ComponentId, _In_ ULONG Level, _In_z_ PCCH Format, _In_ va_list ap);
+#define DPFLTR_ERROR_LEVEL 0
+
+void xfprintf(FILE* stream, const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    vfprintf(stream, fmt, ap);
+    vDbgPrintEx(-1, DPFLTR_ERROR_LEVEL, fmt, ap);
+}
+
+
+
+static bool SortModules(const ModuleData& left, const ModuleData& right)
+{
+    return left.BaseAddress < right.BaseAddress;
+}
+
+
+void PrintBugreport(FILE* output, DumpData& data)
+{
+    PrintSystemInfo(output, data);
+    xfprintf(output, NEWLINE "*----> Task List <----*" NEWLINE NEWLINE);
+    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+    if (hSnap != INVALID_HANDLE_VALUE)
+    {
+        PROCESSENTRY32 pe;
+        pe.dwSize = sizeof(pe);
+        if (Process32First(hSnap, &pe))
+        {
+            do
+            {
+                xfprintf(output, "%5d: %s" NEWLINE, pe.th32ProcessID, pe.szExeFile);
+            } while (Process32Next(hSnap, &pe));
+        }
+        CloseHandle(hSnap);
+    }
+
+    xfprintf(output, NEWLINE "*----> Module List <----*" NEWLINE NEWLINE);
+    std::sort(data.Modules.begin(), data.Modules.end(), SortModules);
+
+    ModuleData mainModule(NULL);
+    mainModule.Update(data.ProcessHandle);
+    xfprintf(output, "(%p - %p) %s" NEWLINE,
+             mainModule.BaseAddress,
+             (PBYTE)mainModule.BaseAddress + mainModule.Size,
+             data.ProcessPath.c_str());
+
+    for (size_t n = 0; n < data.Modules.size(); ++n)
+    {
+        ModuleData& mod = data.Modules[n];
+        if (!mod.Unloaded)
+        {
+            mod.Update(data.ProcessHandle);
+            xfprintf(output, "(%p - %p) %s" NEWLINE,
+                     mod.BaseAddress,
+                     (PBYTE)mod.BaseAddress + mod.Size,
+                     mod.ModuleName.c_str());
+        }
+    }
+
+    BeginStackBacktrace(data);
+    for (ThreadMap::iterator it = data.Threads.begin(); it != data.Threads.end(); ++it)
+    {
+        it->second.Update();
+
+        xfprintf(output, NEWLINE "State Dump for Thread Id 0x%x" NEWLINE NEWLINE, it->first);
+        const CONTEXT& ctx = it->second.Context;
+        if (ctx.ContextFlags & CONTEXT_INTEGER)
+            xfprintf(output, "eax:%p ebx:%p ecx:%p edx:%p esi:%p edi:%p" NEWLINE,
+                     ctx.Eax, ctx.Ebx, ctx.Ecx, ctx.Edx, ctx.Esi, ctx.Edi);
+        if (ctx.ContextFlags & CONTEXT_CONTROL)
+            xfprintf(output, "eip:%p esp:%p ebp:%p" NEWLINE,
+                     ctx.Eip, ctx.Esp, ctx.Ebp);
+        if (ctx.ContextFlags & CONTEXT_DEBUG_REGISTERS)
+            xfprintf(output, "dr0:%p dr1:%p dr2:%p dr3:%p dr6:%p dr7:%p" NEWLINE,
+                     ctx.Dr0, ctx.Dr1, ctx.Dr2, ctx.Dr3, ctx.Dr6, ctx.Dr7);
+
+        PrintStackBacktrace(output, data, it->second);
+    }
+    EndStackBacktrace(data);
+}
+
+
+int abort(FILE* output, int err)
+{
+    if (output != stdout)
+        fclose(output);
+    else
+        _getch();
+
+    return err;
+}
+
+int main(int argc, char* argv[])
+{
+    DWORD pid = 0;
+    char Buffer[MAX_PATH+55];
+    char Filename[50];
+    FILE* output = NULL;
+    SYSTEMTIME st;
+    DumpData data;
+
+
+    for (int n = 0; n < argc; ++n)
+    {
+        char* arg = argv[n];
+
+        if (!strcmp(arg, "-i"))
+        {
+            /* FIXME: Installs as the postmortem debugger. */
+        }
+        else if (!strcmp(arg, "-g"))
+        {
+        }
+        else if (!strcmp(arg, "-p"))
+        {
+            if (n + 1 < argc)
+            {
+                pid = strtoul(argv[n+1], NULL, 10);
+                n++;
+            }
+        }
+        else if (!strcmp(arg, "-e"))
+        {
+            if (n + 1 < argc)
+            {
+                data.Event = (HANDLE)strtoul(argv[n+1], NULL, 10);
+                n++;
+            }
+        }
+        else if (!strcmp(arg, "-?"))
+        {
+            MessageBoxA(NULL, szUsage, "DrWtsn32", MB_OK);
+            return abort(output, 0);
+        }
+        else if (!strcmp(arg, "/?"))
+        {
+            xfprintf(stdout, "%s\n", szUsage);
+            return abort(stdout, 0);
+        }
+    }
+
+    if (!pid)
+    {
+        MessageBoxA(NULL, szUsage, "DrWtsn32", MB_OK);
+        return abort(stdout, 0);
+    }
+
+    GetLocalTime(&st);
+
+    if (SHGetFolderPathA(NULL, CSIDL_DESKTOP, NULL, SHGFP_TYPE_CURRENT, Buffer) == S_OK &&
+        SUCCEEDED(StringCchPrintfA(Filename, _countof(Filename), "Appcrash_%d-%02d-%02d_%02d-%02d-%02d.txt",
+                                   st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond)))
+    {
+        StringCchCatA(Buffer, _countof(Buffer), "\\");
+        StringCchCatA(Buffer, _countof(Buffer), Filename);
+        output = fopen(Buffer, "wb");
+    }
+    if (!output)
+        output = stdout;
+
+
+    if (!DebugActiveProcess(pid))
+        return abort(output, -2);
+
+    /* We should not kill it? */
+    DebugSetProcessKillOnExit(FALSE);
+
+    DEBUG_EVENT evt;
+    if (!WaitForDebugEvent(&evt, 30000))
+        return abort(output, -3);
+
+    assert(evt.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT);
+
+    while (UpdateFromEvent(evt, data))
+    {
+        ContinueDebugEvent(evt.dwProcessId, evt.dwThreadId, DBG_CONTINUE);
+
+        if (!WaitForDebugEvent(&evt, 30000))
+            return abort(output, -4);
+    }
+
+    PrintBugreport(output, data);
+
+    TerminateProcess(data.ProcessHandle, data.ExceptionInfo.ExceptionRecord.ExceptionCode);
+
+    std::string Message = "The application '";
+    Message += data.ProcessName;
+    Message += "' has just crashed :(\n";
+    Message += "Information about this crash is saved to:\n";
+    Message += Filename;
+    Message += "\nThis file is stored on your desktop.";
+    MessageBoxA(NULL, Message.c_str(), "Sorry!", MB_OK);
+
+    return abort(output, 0);
+}
diff --git a/base/applications/drwtsn32/precomp.h b/base/applications/drwtsn32/precomp.h
new file mode 100644 (file)
index 0000000..8e456c7
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * PROJECT:     Dr. Watson crash reporter
+ * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
+ * PURPOSE:     Precompiled Header
+ * COPYRIGHT:   Copyright 2017 Mark Jansen (mark.jansen@reactos.org)
+ */
+
+#ifndef _DRWTSN32_PRECOMP_H_
+#define _DRWTSN32_PRECOMP_H_
+
+#include <ntstatus.h>
+#define WIN32_NO_STATUS
+
+#include <windef.h>
+#include <winbase.h>
+#include <winver.h>
+
+#include <string>
+#include <vector>
+#include <map>
+#include <stdio.h>
+#include <assert.h>
+
+#include "drwtsn32.h"
+
+typedef LONG NTSTATUS;
+
+#endif // _DRWTSN32_PRECOMP_H_
diff --git a/base/applications/drwtsn32/stacktrace.cpp b/base/applications/drwtsn32/stacktrace.cpp
new file mode 100644 (file)
index 0000000..a867150
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * PROJECT:     Dr. Watson crash reporter
+ * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
+ * PURPOSE:     Print a stacktrace
+ * COPYRIGHT:   Copyright 2017 Mark Jansen (mark.jansen@reactos.org)
+ */
+
+#include "precomp.h"
+#include <dbghelp.h>
+
+
+void BeginStackBacktrace(DumpData& data)
+{
+    DWORD symOptions = SymGetOptions();
+    symOptions |= SYMOPT_UNDNAME | SYMOPT_AUTO_PUBLICS | SYMOPT_DEFERRED_LOADS;
+    SymSetOptions(symOptions);
+    SymInitialize(data.ProcessHandle, NULL, TRUE);
+}
+
+void EndStackBacktrace(DumpData& data)
+{
+    SymCleanup(data.ProcessHandle);
+}
+
+static char ToChar(UCHAR data)
+{
+    if (data < 0xa)
+        return '0' + data;
+    else if (data <= 0xf)
+        return 'a' + data - 0xa;
+    return '?';
+}
+
+void PrintStackBacktrace(FILE* output, DumpData& data, ThreadData& thread)
+{
+    DWORD MachineType;
+    STACKFRAME64 StackFrame = { { 0 } };
+
+#ifdef _M_X64
+    MachineType = IMAGE_FILE_MACHINE_AMD64;
+    StackFrame.AddrPC.Offset = thread.Context.Rip;
+    StackFrame.AddrPC.Mode = AddrModeFlat;
+    StackFrame.AddrStack.Offset = thread.Context.Rsp;
+    StackFrame.AddrStack.Mode = AddrModeFlat;
+    StackFrame.AddrFrame.Offset = thread.Context.Rbp;
+    StackFrame.AddrFrame.Mode = AddrModeFlat;
+#else
+    MachineType = IMAGE_FILE_MACHINE_I386;
+    StackFrame.AddrPC.Offset =  thread.Context.Eip;
+    StackFrame.AddrPC.Mode = AddrModeFlat;
+    StackFrame.AddrStack.Offset = thread.Context.Esp;
+    StackFrame.AddrStack.Mode = AddrModeFlat;
+    StackFrame.AddrFrame.Offset = thread.Context.Ebp;
+    StackFrame.AddrFrame.Mode = AddrModeFlat;
+#endif
+
+
+#define STACKWALK_MAX_NAMELEN   512
+    char buf[sizeof(SYMBOL_INFO) + STACKWALK_MAX_NAMELEN] = {0};
+    SYMBOL_INFO* sym = (SYMBOL_INFO *)buf;
+    IMAGEHLP_MODULE64 Module = { 0 };
+    sym->SizeOfStruct = sizeof(sym);
+
+    /* FIXME: Disasm function! */
+
+    xfprintf(output, NEWLINE "*----> Stack Back Trace <----*" NEWLINE NEWLINE);
+    bool first = true;
+    ULONG_PTR LastFrame = StackFrame.AddrFrame.Offset - 8;
+    while(StackWalk64(MachineType, data.ProcessHandle, thread.Handle, &StackFrame, &thread.Context,
+                         NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL))
+    {
+        if (!StackFrame.AddrPC.Offset)
+            break;
+
+        if (LastFrame >= StackFrame.AddrFrame.Offset)
+            break;
+
+        LastFrame = StackFrame.AddrFrame.Offset;
+
+        if (first)
+        {
+            xfprintf(output, "FramePtr ReturnAd Param#1  Param#2  Param#3  Param#4  Function Name" NEWLINE);
+            first = false;
+        }
+
+        Module.SizeOfStruct = sizeof(Module);
+        DWORD64 ModBase = SymGetModuleBase64(data.ProcessHandle, StackFrame.AddrPC.Offset);
+        if (!SymGetModuleInfo64(data.ProcessHandle, ModBase, &Module))
+            strcpy(Module.ModuleName, "<nomod>");
+
+        memset(sym, '\0', sizeof(*sym) + STACKWALK_MAX_NAMELEN);
+        sym->SizeOfStruct = sizeof(*sym);
+        sym->MaxNameLen = STACKWALK_MAX_NAMELEN;
+        DWORD64 displacement;
+
+        if (!SymFromAddr(data.ProcessHandle, StackFrame.AddrPC.Offset, &displacement, sym))
+            strcpy(sym->Name, "<nosymbols>");
+
+        xfprintf(output, "%p %p %p %p %p %p %s!%s" NEWLINE,
+                 (ULONG_PTR)StackFrame.AddrFrame.Offset, (ULONG_PTR)StackFrame.AddrPC.Offset,
+                 (ULONG_PTR)StackFrame.Params[0], (ULONG_PTR)StackFrame.Params[1],
+                 (ULONG_PTR)StackFrame.Params[2], (ULONG_PTR)StackFrame.Params[3],
+                 Module.ModuleName, sym->Name);
+    }
+
+    UCHAR stackData[0x10 * 10];
+    DWORD dwSizeRead;
+    if (!ReadProcessMemory(data.ProcessHandle, (LPCVOID)thread.Context.Esp, stackData, sizeof(stackData), &dwSizeRead))
+        return;
+
+    xfprintf(output, NEWLINE "*----> Raw Stack Dump <----*" NEWLINE NEWLINE);
+    for (size_t n = 0; n < sizeof(stackData); n += 0x10)
+    {
+        char HexData1[] = "?? ?? ?? ?? ?? ?? ?? ??";
+        char HexData2[] = "?? ?? ?? ?? ?? ?? ?? ??";
+        char AsciiData1[] = "????????";
+        char AsciiData2[] = "????????";
+
+        for (size_t j = 0; j < 8; ++j)
+        {
+            size_t idx = j + n;
+            if (idx < dwSizeRead)
+            {
+                HexData1[j * 3] = ToChar(stackData[idx] >> 4);
+                HexData1[j * 3 + 1] = ToChar(stackData[idx] & 0xf);
+                AsciiData1[j] = isprint(stackData[idx]) ? stackData[idx] : '.';
+            }
+            idx += 8;
+            if (idx < dwSizeRead)
+            {
+                HexData2[j * 3] = ToChar(stackData[idx] >> 4);
+                HexData2[j * 3 + 1] = ToChar(stackData[idx] & 0xf);
+                AsciiData2[j] = isprint(stackData[idx]) ? stackData[idx] : '.';
+            }
+        }
+
+        xfprintf(output, "%p %s - %s  %s%s" NEWLINE, thread.Context.Esp+n, HexData1, HexData2, AsciiData1, AsciiData2);
+    }
+}
diff --git a/base/applications/drwtsn32/sysinfo.cpp b/base/applications/drwtsn32/sysinfo.cpp
new file mode 100644 (file)
index 0000000..7cad0ca
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * PROJECT:     Dr. Watson crash reporter
+ * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
+ * PURPOSE:     Output system info
+ * COPYRIGHT:   Copyright 2017 Mark Jansen (mark.jansen@reactos.org)
+ */
+
+#include "precomp.h"
+#include <winreg.h>
+#include <reactos/buildno.h>
+
+static const char* Exception2Str(DWORD code)
+{
+    switch (code)
+    {
+    case EXCEPTION_ACCESS_VIOLATION: return "EXCEPTION_ACCESS_VIOLATION";
+    case EXCEPTION_DATATYPE_MISALIGNMENT: return "EXCEPTION_DATATYPE_MISALIGNMENT";
+    case EXCEPTION_BREAKPOINT: return "EXCEPTION_BREAKPOINT";
+    case EXCEPTION_SINGLE_STEP: return "EXCEPTION_SINGLE_STEP";
+    case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED";
+    case EXCEPTION_FLT_DENORMAL_OPERAND: return "EXCEPTION_FLT_DENORMAL_OPERAND";
+    case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "EXCEPTION_FLT_DIVIDE_BY_ZERO";
+    case EXCEPTION_FLT_INEXACT_RESULT: return "EXCEPTION_FLT_INEXACT_RESULT";
+    case EXCEPTION_FLT_INVALID_OPERATION: return "EXCEPTION_FLT_INVALID_OPERATION";
+    case EXCEPTION_FLT_OVERFLOW: return "EXCEPTION_FLT_OVERFLOW";
+    case EXCEPTION_FLT_STACK_CHECK: return "EXCEPTION_FLT_STACK_CHECK";
+    case EXCEPTION_FLT_UNDERFLOW: return "EXCEPTION_FLT_UNDERFLOW";
+    case EXCEPTION_INT_DIVIDE_BY_ZERO: return "EXCEPTION_INT_DIVIDE_BY_ZERO";
+    case EXCEPTION_INT_OVERFLOW: return "EXCEPTION_INT_OVERFLOW";
+    case EXCEPTION_PRIV_INSTRUCTION: return "EXCEPTION_PRIV_INSTRUCTION";
+    case EXCEPTION_IN_PAGE_ERROR: return "EXCEPTION_IN_PAGE_ERROR";
+    case EXCEPTION_ILLEGAL_INSTRUCTION: return "EXCEPTION_ILLEGAL_INSTRUCTION";
+    case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "EXCEPTION_NONCONTINUABLE_EXCEPTION";
+    case EXCEPTION_STACK_OVERFLOW: return "EXCEPTION_STACK_OVERFLOW";
+    case EXCEPTION_INVALID_DISPOSITION: return "EXCEPTION_INVALID_DISPOSITION";
+    case EXCEPTION_GUARD_PAGE: return "EXCEPTION_GUARD_PAGE";
+    case EXCEPTION_INVALID_HANDLE: return "EXCEPTION_INVALID_HANDLE";
+    }
+
+    return "--";
+}
+
+static void ReadKey(HKEY hKey, const char* ValueName, char* Buffer, DWORD size)
+{
+    DWORD dwType;
+    LSTATUS ret = RegQueryValueExA(hKey, ValueName, NULL, &dwType, (LPBYTE)Buffer, &size);
+    if (ret != ERROR_SUCCESS || dwType != REG_SZ)
+        Buffer[0] = '\0';
+}
+
+void PrintSystemInfo(FILE* output, DumpData& data)
+{
+    SYSTEMTIME LocalTime;
+    GetLocalTime(&LocalTime);
+    xfprintf(output, NEWLINE "ReactOS " KERNEL_VERSION_STR " DrWtsn32" NEWLINE NEWLINE);
+    xfprintf(output, "Application exception occurred:" NEWLINE);
+    xfprintf(output, "    App: %s (pid=%d, tid=0x%x)" NEWLINE, data.ProcessName.c_str(), data.ProcessID, data.ThreadID);
+    xfprintf(output, "    When: %d/%d/%d @ %02d:%02d:%02d.%d" NEWLINE,
+             LocalTime.wDay, LocalTime.wMonth, LocalTime.wYear,
+             LocalTime.wHour, LocalTime.wMinute, LocalTime.wSecond, LocalTime.wMilliseconds);
+    DWORD ExceptionCode = data.ExceptionInfo.ExceptionRecord.ExceptionCode;
+    xfprintf(output, "    Exception number: 0x%8x (%s)" NEWLINE, ExceptionCode, Exception2Str(ExceptionCode));
+
+    char Buffer[MAX_PATH];
+    DWORD count = sizeof(Buffer);
+    xfprintf(output, NEWLINE "*----> System Information <----*" NEWLINE NEWLINE);
+    if (GetComputerNameA(Buffer, &count))
+        xfprintf(output, "    Computer Name: %s" NEWLINE, Buffer);
+    count = sizeof(Buffer);
+    if (GetUserNameA(Buffer, &count))
+        xfprintf(output, "    User Name: %s" NEWLINE, Buffer);
+
+
+    SYSTEM_INFO info;
+    GetSystemInfo(&info);
+    xfprintf(output, "    Number of Processors: %d" NEWLINE, info.dwNumberOfProcessors);
+
+    HKEY hKey;
+    LONG ret = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0",
+                             0, KEY_READ, &hKey);
+    if (ret == ERROR_SUCCESS)
+    {
+        DWORD dwType;
+        count = sizeof(Buffer);
+        ret = RegQueryValueExA(hKey, "Identifier", NULL, &dwType, (LPBYTE)Buffer, &count);
+        if (ret == ERROR_SUCCESS && dwType == REG_SZ)
+        {
+            Buffer[count] = '\0';
+            xfprintf(output, "    Processor Type: %s" NEWLINE, Buffer);
+        }
+        RegCloseKey(hKey);
+    }
+
+    ret = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_READ, &hKey);
+    if (ret == ERROR_SUCCESS)
+    {
+        char Version[50];
+        ReadKey(hKey, "ProductName", Buffer, sizeof(Buffer));
+        ReadKey(hKey, "CurrentVersion", Version, sizeof(Version));
+        xfprintf(output, "    %s Version: %s" NEWLINE, Buffer, Version);
+        ReadKey(hKey, "BuildLab", Buffer, sizeof(Buffer));
+        xfprintf(output, "    BuildLab: %s" NEWLINE, Buffer);
+        ReadKey(hKey, "CSDVersion", Buffer, sizeof(Buffer));
+        if (Buffer[0])
+            xfprintf(output, "    Service Pack: %s" NEWLINE, Buffer);
+        ReadKey(hKey, "CurrentType", Buffer, sizeof(Buffer));
+        xfprintf(output, "    Current Type: %s" NEWLINE, Buffer);
+        ReadKey(hKey, "RegisteredOrganization", Buffer, sizeof(Buffer));
+        xfprintf(output, "    Registered Organization: %s" NEWLINE, Buffer);
+        ReadKey(hKey, "RegisteredOwner", Buffer, sizeof(Buffer));
+        xfprintf(output, "    Registered Owner: %s" NEWLINE, Buffer);
+
+        RegCloseKey(hKey);
+    }
+}