diff --git a/devel/w32dumper/aegisub-w32dumper.vcxproj b/devel/w32dumper/aegisub-w32dumper.vcxproj new file mode 100644 index 000000000..f164d3644 --- /dev/null +++ b/devel/w32dumper/aegisub-w32dumper.vcxproj @@ -0,0 +1,91 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {343E27E9-303B-4489-BAD7-1DDCD1C9552A} + Win32Proj + aegisubw32dumper + + + + Application + true + Unicode + + + Application + false + true + Unicode + + + + + + + + + + + + + true + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + + + Windows + true + comctl32.lib;psapi.lib;dbghelp.lib;%(AdditionalDependencies) + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + MultiThreaded + + + Windows + true + true + true + comctl32.lib;psapi.lib;dbghelp.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/devel/w32dumper/aegisub-w32dumper.vcxproj.filters b/devel/w32dumper/aegisub-w32dumper.vcxproj.filters new file mode 100644 index 000000000..cd1886511 --- /dev/null +++ b/devel/w32dumper/aegisub-w32dumper.vcxproj.filters @@ -0,0 +1,32 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + + + Header Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/devel/w32dumper/resource.h b/devel/w32dumper/resource.h new file mode 100644 index 000000000..ef44b025c --- /dev/null +++ b/devel/w32dumper/resource.h @@ -0,0 +1,19 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by w32dumper.rc +// +#define IDD_W32DUMPER 101 +#define IDC_LOGLIST 1001 +#define IDC_SYSLINK1 1002 +#define IDC_DUMPFOLDERLINK 1002 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1003 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/devel/w32dumper/w32dumper.aps b/devel/w32dumper/w32dumper.aps new file mode 100644 index 000000000..9c01377b5 Binary files /dev/null and b/devel/w32dumper/w32dumper.aps differ diff --git a/devel/w32dumper/w32dumper.cpp b/devel/w32dumper/w32dumper.cpp new file mode 100644 index 000000000..eb5f1abbd --- /dev/null +++ b/devel/w32dumper/w32dumper.cpp @@ -0,0 +1,329 @@ +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "resource.h" + + +std::wstring CanonicalFileName(std::wstring const &fn) +{ + DWORD bufsize = GetLongPathNameW(fn.c_str(), 0, 0); + wchar_t *fnbuf = (LPWSTR)malloc(sizeof(*fnbuf)*bufsize); + bufsize = GetLongPathNameW(fn.c_str(), fnbuf, bufsize); + auto canfn = std::wstring(fnbuf, fnbuf+bufsize); + free(fnbuf); + return canfn; +} + +std::wstring GetDumpfileFolder() +{ + std::wstring dumpfile_folder; + wchar_t appdata_folder[MAX_PATH+1] = {0}; + SHGetFolderPathW(0, CSIDL_APPDATA, 0, SHGFP_TYPE_CURRENT, appdata_folder); + dumpfile_folder = std::wstring(appdata_folder); + dumpfile_folder += L"\\Aegisub\\"; + if (CreateDirectoryW(dumpfile_folder.c_str(), 0) == 0 && GetLastError() == ERROR_PATH_NOT_FOUND) + return std::wstring(); // nowhere to write, somehow there is no %appdata% + dumpfile_folder += L"dumps\\"; + CreateDirectoryW(dumpfile_folder.c_str(), 0); + return dumpfile_folder; +} + +std::wstring IntToWstring(int n) +{ + wchar_t buf[16]; + swprintf_s(buf, L"%d", n); + return std::wstring(buf); +} + + +// DNM = Dumper Notify Message +#define DNM_COMPLETED (WM_APP + 0) +#define DNM_ERROR (WM_APP + 1) +#define DNM_DUMPSTARTED (WM_APP + 2) +#define DNM_DUMPFINISHED (WM_APP + 3) + + +// Data being passed to the worker thread +struct DumperThreadData { + HWND hwndDlg; + HINSTANCE hInstance; +}; + +// Miniclass to make sure the dialog gets sent a message when the thread ends, +// regardless of the reason or manner +struct EnsureDialogNotifiedOfThreadCompletion { + HWND hwnd; + EnsureDialogNotifiedOfThreadCompletion(HWND hwnd) : hwnd(hwnd) { } + ~EnsureDialogNotifiedOfThreadCompletion() + { + SendMessageW(hwnd, DNM_COMPLETED, 0, 0); + } + void error(int code, wchar_t const *message) + { + SendMessageW(hwnd, DNM_ERROR, (WPARAM)code, (LPARAM)message); + } +}; + +struct WindowsHandle { + HANDLE handle; + explicit WindowsHandle(HANDLE handle) : handle(handle) { } + ~WindowsHandle() { if (handle != 0) CloseHandle(handle); } + operator HANDLE() { return handle; } +}; + +// Thread that will actually find and make dumps of Aegisub processes +void __cdecl dumper_thread(void *data) +{ + DumperThreadData *dtd = static_cast(data); + + EnsureDialogNotifiedOfThreadCompletion completion_notify(dtd->hwndDlg); + + // Find Aegisub's install dir based on where we are located + std::wstring aegisub_filename_prefix; + { + DWORD bufsize = MAX_PATH; + LPWSTR modfnbuf = (LPWSTR)malloc(sizeof(*modfnbuf)*bufsize); + DWORD modfnlen = GetModuleFileNameW(0, modfnbuf, bufsize); + if (modfnlen > bufsize) + { + bufsize = modfnlen; + modfnbuf = (LPWSTR)realloc(modfnbuf, sizeof(*modfnbuf)*bufsize); + modfnlen = GetModuleFileNameW(0, modfnbuf, bufsize); + } + aegisub_filename_prefix = CanonicalFileName(std::wstring(modfnbuf, modfnbuf+modfnlen)); + free(modfnbuf); + } + { + // Chomp it at the last backslash and append "aegisub" + size_t backslash_pos = aegisub_filename_prefix.rfind(L'\\'); + if (backslash_pos == std::wstring::npos) + { + completion_notify.error(2, L"Something is wrong with the installation path"); + return; + } + aegisub_filename_prefix.erase(backslash_pos+1); + } + + // Figure out where we should be writing dump files to + std::wstring dumpfile_folder = GetDumpfileFolder(); + if (dumpfile_folder.empty()) + { + completion_notify.error(3, L"Could not access folder for writing dump files to"); + return; + } + + // Get pids of all processes + std::vector process_ids; + { + size_t pidlist_size = 128; + size_t pidlist_count = 0; + do { + process_ids.resize(pidlist_size); + DWORD bytes_returned = 0; + if (EnumProcesses(&process_ids[0], sizeof(DWORD)*pidlist_size, &bytes_returned) == 0) + { + completion_notify.error(4, L"An error occurred trying to enumerate processes on the system"); + return; + } + pidlist_count = bytes_returned / sizeof(DWORD); + } while (pidlist_count == pidlist_size); + process_ids.resize(pidlist_count); + } + + // Build a string useful for making filenames more unique + std::wstring timestring; + { + time_t t = time(0); + tm curtime; + localtime_s(&curtime, &t); + wchar_t fmttime[20] = {0}; + swprintf_s(fmttime, L"%4d%02d%02d-%02d%02d%02d", + curtime.tm_year+1900, curtime.tm_mon, curtime.tm_mday, + curtime.tm_hour, curtime.tm_min, curtime.tm_sec); + timestring = std::wstring(fmttime); + } + + // Check each process for being interesting (i.e. probably an Aegisub process) + const DWORD mypid = GetCurrentProcessId(); + for (auto ppid = process_ids.begin(); ppid != process_ids.end(); ++ppid) + { + if (*ppid == mypid) + continue; + + WindowsHandle proc(OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, FALSE, *ppid)); + if (proc == 0) + continue; + + // Get process name + std::wstring procfn; + { + DWORD bufsize = MAX_PATH; + LPWSTR modfnbuf = (LPWSTR)malloc(sizeof(*modfnbuf)*bufsize); + DWORD modfnlen = GetModuleFileNameExW(proc, 0, modfnbuf, bufsize); + if (modfnlen == 0) + { + DWORD err = GetLastError(); + continue; + } + if (modfnlen > bufsize) + { + bufsize = modfnlen; + modfnbuf = (LPWSTR)realloc(modfnbuf, sizeof(*modfnbuf)*bufsize); + modfnlen = GetModuleFileNameExW(proc, 0, modfnbuf, bufsize); + } + procfn = CanonicalFileName(std::wstring(modfnbuf, modfnbuf+modfnlen)); + free(modfnbuf); + } + + // Check it's relevant + if (procfn.find(aegisub_filename_prefix) != 0) + continue; + + // Pick a filename to write + std::wstring procfn_basename; + { + // Chop off everything up to and including last backslash + size_t pos = procfn.rfind(L'\\'); + if (pos != std::wstring::npos) + procfn_basename = procfn.substr(pos+1); + else + procfn_basename = procfn; + } + + // Tell about our exploits + SendMessageW(dtd->hwndDlg, DNM_DUMPSTARTED, (WPARAM)*ppid, (LPARAM)procfn_basename.c_str()); + + std::wstring dumpfile_name = + dumpfile_folder + + procfn_basename + L'-' + + IntToWstring(*ppid) + L'-' + + timestring + + L".dmp"; + + WindowsHandle dumpfile(CreateFileW(dumpfile_name.c_str(), GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0)); + + MiniDumpWriteDump( + proc, + *ppid, + dumpfile, + MINIDUMP_TYPE(MiniDumpWithThreadInfo|MiniDumpIgnoreInaccessibleMemory|MiniDumpWithIndirectlyReferencedMemory), + 0, 0, 0); + + SendMessageW(dtd->hwndDlg, DNM_DUMPFINISHED, 0, (LPARAM)dumpfile_name.c_str()); + } +} + + +int numdumps = 0; +int numerrors = 0; + +void AddStringToListbox(HWND hwndDlg, std::wstring const &str) +{ + SendDlgItemMessageW(hwndDlg, IDC_LOGLIST, LB_ADDSTRING, 0, (LPARAM)str.c_str()); +} + +INT_PTR CALLBACK dialog_msghandler(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case DNM_COMPLETED: + EnableWindow(GetDlgItem(hwndDlg, IDCLOSE), TRUE); + if (numerrors > 0) + AddStringToListbox(hwndDlg, L"Finished with errors."); + else if (numdumps > 0) + AddStringToListbox(hwndDlg, std::wstring(L"Completed ") + IntToWstring(numdumps) + (numdumps>1?L" minidumps.":L" minidump.")); + else + AddStringToListbox(hwndDlg, L"Finished, found no processes to dump."); + break; + + case DNM_ERROR: + numerrors += 1; + AddStringToListbox(hwndDlg, std::wstring(L"An error occurred: ") + (wchar_t const *)lParam); + break; + + case DNM_DUMPSTARTED: + numdumps += 1; + AddStringToListbox(hwndDlg, std::wstring(L"Beginning dump of pid ") + IntToWstring(wParam) + L" (" + (wchar_t const *)lParam + L")"); + break; + + case DNM_DUMPFINISHED: + AddStringToListbox(hwndDlg, std::wstring(L" Finished dump: ") + (wchar_t const *)lParam); + break; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDCLOSE: + if (HIWORD(wParam) == BN_CLICKED) + PostQuitMessage(0); + break; + } + break; + + case WM_NOTIFY: + { + NMHDR &nm = *(NMHDR*)lParam; + if (nm.idFrom == IDC_DUMPFOLDERLINK && (nm.code == NM_CLICK || nm.code == NM_RETURN)) + { + std::wstring dumpfile_folder = GetDumpfileFolder(); + ShellExecuteW(hwndDlg, L"open", dumpfile_folder.c_str(), 0, 0, SW_SHOWNORMAL); + } + } + break; + } + + return FALSE; +} + + +#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' ""version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") + +int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) +{ + CoInitializeEx(0, COINIT_MULTITHREADED); + INITCOMMONCONTROLSEX iccx = { + sizeof(INITCOMMONCONTROLSEX), + ICC_LINK_CLASS|ICC_STANDARD_CLASSES + }; + if (InitCommonControlsEx(&iccx) == FALSE) + ExitProcess(1); + + HWND hwndDlg = CreateDialogW(hInstance, MAKEINTRESOURCE(IDD_W32DUMPER), 0, dialog_msghandler); + if (hwndDlg == 0) + ExitProcess(2); + + DumperThreadData dtd = { hwndDlg, hInstance }; + uintptr_t dumper_thread_handle = _beginthread(dumper_thread, 0, &dtd); + ShowWindow(hwndDlg, SW_SHOWNORMAL); + + MSG msg; + BOOL gmret; + while ((gmret = GetMessageW(&msg, 0, 0, 0)) != 0) + { + if (gmret == -1) + { + ExitProcess(3); + } + else if (!IsDialogMessageW(hwndDlg, &msg)) + { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + + CoUninitialize(); + + return gmret; +} diff --git a/devel/w32dumper/w32dumper.rc b/devel/w32dumper/w32dumper.rc new file mode 100644 index 000000000..ff1e67cbe --- /dev/null +++ b/devel/w32dumper/w32dumper.rc @@ -0,0 +1,105 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_W32DUMPER DIALOGEX 0, 0, 317, 118 +STYLE DS_SETFONT | DS_FIXEDSYS | DS_CENTER | WS_CAPTION +CAPTION "Aegisub crash logger" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "Close",IDCLOSE,260,97,50,14,WS_DISABLED + LISTBOX IDC_LOGLIST,7,7,303,85,NOT LBS_NOTIFY | LBS_SORT | LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + CONTROL "Open folder containing dump files",IDC_DUMPFOLDERLINK, + "SysLink",WS_TABSTOP,7,99,195,12 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_W32DUMPER, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 310 + TOPMARGIN, 7 + BOTTOMMARGIN, 111 + END +END +#endif // APSTUDIO_INVOKED + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// English (United Kingdom) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // English (United Kingdom) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED +