mirror of https://github.com/odrling/Aegisub
Add some kind of (really untested, but it compiles!) exception data fetching/command line arguments to w32dumper.
Suggest a format for Aegisub to report its crash information to the dumper. Originally committed to SVN as r6530.
This commit is contained in:
parent
4246ea0bd8
commit
3403bd6113
|
@ -47,18 +47,57 @@ std::wstring IntToWstring(int n)
|
||||||
return std::wstring(buf);
|
return std::wstring(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reinventing the wheel because the C or C++ std libs don't seem to have
|
||||||
|
// a function that does something as simple as this.
|
||||||
|
// note: atoi() and family don't do this, they don't really report success/failure.
|
||||||
|
template <typename UIntType>
|
||||||
|
bool try_str2uint(wchar_t *s, UIntType &res)
|
||||||
|
{
|
||||||
|
res = 0;
|
||||||
|
while (*s != 0)
|
||||||
|
{
|
||||||
|
if (*s >= L'0' && *s <= L'9')
|
||||||
|
{
|
||||||
|
res *= 10;
|
||||||
|
res += (*s - L'0');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// invalid character
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
++s;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ideally identical to the one used by aegisub
|
||||||
|
// but otherwise the "base" part is fixed
|
||||||
|
// todo: move this to some shared include file
|
||||||
|
struct AegisubCrashInfo {
|
||||||
|
struct {
|
||||||
|
EXCEPTION_POINTERS *exception_pointers;
|
||||||
|
DWORD exception_thread_id;
|
||||||
|
size_t sz; // size of entire containing struct
|
||||||
|
} base;
|
||||||
|
wchar_t unhandled_cpp_exception_text[1024];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// DNM = Dumper Notify Message
|
// DNM = Dumper Notify Message
|
||||||
#define DNM_COMPLETED (WM_APP + 0)
|
#define DNM_COMPLETED (WM_APP + 0)
|
||||||
#define DNM_ERROR (WM_APP + 1)
|
#define DNM_ERROR (WM_APP + 1)
|
||||||
#define DNM_DUMPSTARTED (WM_APP + 2)
|
#define DNM_DUMPSTARTED (WM_APP + 2)
|
||||||
#define DNM_DUMPFINISHED (WM_APP + 3)
|
#define DNM_DUMPFINISHED (WM_APP + 3)
|
||||||
|
#define DNM_INFOMSG (WM_APP + 4)
|
||||||
|
|
||||||
|
|
||||||
// Data being passed to the worker thread
|
// Data being passed to the worker thread
|
||||||
struct DumperThreadData {
|
struct DumperThreadData {
|
||||||
HWND hwndDlg;
|
HWND hwndDlg;
|
||||||
HINSTANCE hInstance;
|
DWORD target_pid;
|
||||||
|
INT_PTR target_infoblock;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Miniclass to make sure the dialog gets sent a message when the thread ends,
|
// Miniclass to make sure the dialog gets sent a message when the thread ends,
|
||||||
|
@ -83,6 +122,13 @@ struct WindowsHandle {
|
||||||
operator HANDLE() { return handle; }
|
operator HANDLE() { return handle; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// fake IsWow64Process function for old 32 bit systems (NT 5.1 and earlier miss it)
|
||||||
|
BOOL WINAPI FakeIsWow64Process(HANDLE hProcess, PBOOL Wow64Process)
|
||||||
|
{
|
||||||
|
*Wow64Process = FALSE;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
// Thread that will actually find and make dumps of Aegisub processes
|
// Thread that will actually find and make dumps of Aegisub processes
|
||||||
void __cdecl dumper_thread(void *data)
|
void __cdecl dumper_thread(void *data)
|
||||||
{
|
{
|
||||||
|
@ -90,9 +136,23 @@ void __cdecl dumper_thread(void *data)
|
||||||
|
|
||||||
EnsureDialogNotifiedOfThreadCompletion completion_notify(dtd->hwndDlg);
|
EnsureDialogNotifiedOfThreadCompletion completion_notify(dtd->hwndDlg);
|
||||||
|
|
||||||
// Find Aegisub's install dir based on where we are located
|
|
||||||
std::wstring aegisub_filename_prefix;
|
std::wstring aegisub_filename_prefix;
|
||||||
|
std::vector<DWORD> process_ids;
|
||||||
|
|
||||||
|
BOOL dumper_proc_is_wow64 = FALSE;
|
||||||
|
BOOL (WINAPI * IsWow64Process)(HANDLE hProcess, PBOOL Wow64Process);
|
||||||
{
|
{
|
||||||
|
HMODULE kernel32 = LoadLibraryW(L"kernel32.dll");
|
||||||
|
IsWow64Process = (BOOL(WINAPI*)(HANDLE, PBOOL))GetProcAddress(kernel32, "IsWow64Process");
|
||||||
|
if (IsWow64Process == 0) IsWow64Process = FakeIsWow64Process;
|
||||||
|
}
|
||||||
|
IsWow64Process(GetCurrentProcess(), &dumper_proc_is_wow64);
|
||||||
|
|
||||||
|
if (dtd->target_pid == 0)
|
||||||
|
{
|
||||||
|
SendMessageW(dtd->hwndDlg, DNM_INFOMSG, 0, (LPARAM)L"Searching for active processes...");
|
||||||
|
|
||||||
|
// Find Aegisub's install dir based on where we are located
|
||||||
DWORD bufsize = MAX_PATH;
|
DWORD bufsize = MAX_PATH;
|
||||||
LPWSTR modfnbuf = (LPWSTR)malloc(sizeof(*modfnbuf)*bufsize);
|
LPWSTR modfnbuf = (LPWSTR)malloc(sizeof(*modfnbuf)*bufsize);
|
||||||
DWORD modfnlen = GetModuleFileNameW(0, modfnbuf, bufsize);
|
DWORD modfnlen = GetModuleFileNameW(0, modfnbuf, bufsize);
|
||||||
|
@ -104,8 +164,7 @@ void __cdecl dumper_thread(void *data)
|
||||||
}
|
}
|
||||||
aegisub_filename_prefix = CanonicalFileName(std::wstring(modfnbuf, modfnbuf+modfnlen));
|
aegisub_filename_prefix = CanonicalFileName(std::wstring(modfnbuf, modfnbuf+modfnlen));
|
||||||
free(modfnbuf);
|
free(modfnbuf);
|
||||||
}
|
|
||||||
{
|
|
||||||
// Chomp it at the last backslash and append "aegisub"
|
// Chomp it at the last backslash and append "aegisub"
|
||||||
size_t backslash_pos = aegisub_filename_prefix.rfind(L'\\');
|
size_t backslash_pos = aegisub_filename_prefix.rfind(L'\\');
|
||||||
if (backslash_pos == std::wstring::npos)
|
if (backslash_pos == std::wstring::npos)
|
||||||
|
@ -114,19 +173,9 @@ void __cdecl dumper_thread(void *data)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
aegisub_filename_prefix.erase(backslash_pos+1);
|
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
|
// Get pids of all processes
|
||||||
std::vector<DWORD> process_ids;
|
|
||||||
{
|
|
||||||
size_t pidlist_size = 128;
|
size_t pidlist_size = 128;
|
||||||
size_t pidlist_count = 0;
|
size_t pidlist_count = 0;
|
||||||
do {
|
do {
|
||||||
|
@ -141,6 +190,21 @@ void __cdecl dumper_thread(void *data)
|
||||||
} while (pidlist_count == pidlist_size);
|
} while (pidlist_count == pidlist_size);
|
||||||
process_ids.resize(pidlist_count);
|
process_ids.resize(pidlist_count);
|
||||||
}
|
}
|
||||||
|
else // target_pid given
|
||||||
|
{
|
||||||
|
SendMessageW(dtd->hwndDlg, DNM_INFOMSG, 0, (LPARAM)L"Searching for crash target...");
|
||||||
|
|
||||||
|
// just add the single PID to the list, most of the magic happens in the dumping loop
|
||||||
|
process_ids.push_back(dtd->target_pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
// Build a string useful for making filenames more unique
|
// Build a string useful for making filenames more unique
|
||||||
std::wstring timestring;
|
std::wstring timestring;
|
||||||
|
@ -188,7 +252,7 @@ void __cdecl dumper_thread(void *data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check it's relevant
|
// Check it's relevant
|
||||||
if (procfn.find(aegisub_filename_prefix) != 0)
|
if (dtd->target_pid == 0 && procfn.find(aegisub_filename_prefix) != 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Pick a filename to write
|
// Pick a filename to write
|
||||||
|
@ -205,6 +269,55 @@ void __cdecl dumper_thread(void *data)
|
||||||
// Tell about our exploits
|
// Tell about our exploits
|
||||||
SendMessageW(dtd->hwndDlg, DNM_DUMPSTARTED, (WPARAM)*ppid, (LPARAM)procfn_basename.c_str());
|
SendMessageW(dtd->hwndDlg, DNM_DUMPSTARTED, (WPARAM)*ppid, (LPARAM)procfn_basename.c_str());
|
||||||
|
|
||||||
|
MINIDUMP_EXCEPTION_INFORMATION exception_information = {0};
|
||||||
|
MINIDUMP_USER_STREAM_INFORMATION *user_stream_information = 0;
|
||||||
|
if (dtd->target_pid != 0)
|
||||||
|
{
|
||||||
|
// sanity check: we can't easily work with different-bitness processes
|
||||||
|
BOOL target_proc_is_wow64 = FALSE;
|
||||||
|
IsWow64Process(proc, &target_proc_is_wow64);
|
||||||
|
if (target_proc_is_wow64 != dumper_proc_is_wow64)
|
||||||
|
{
|
||||||
|
SendMessageW(dtd->hwndDlg, DNM_INFOMSG, 0, (LPARAM)(
|
||||||
|
target_proc_is_wow64
|
||||||
|
? L"Target process is 32 bit, but crash dumper is 64 bit. Cannot include all information in dump."
|
||||||
|
: L"Target process is 64 bit, but crash dumper is 32 bit. Cannot include all infromation in dump."
|
||||||
|
));
|
||||||
|
goto skip_advanced_dump;
|
||||||
|
}
|
||||||
|
|
||||||
|
AegisubCrashInfo crashinfo;
|
||||||
|
if (ReadProcessMemory(proc, (LPCVOID)dtd->target_infoblock, &crashinfo, sizeof(crashinfo.base), 0) == FALSE ||
|
||||||
|
ReadProcessMemory(proc, (LPCVOID)dtd->target_infoblock, &crashinfo, crashinfo.base.sz, 0) == FALSE)
|
||||||
|
{
|
||||||
|
SendMessageW(dtd->hwndDlg, DNM_INFOMSG, 0, (LPARAM)L"Failed to read detailed crash information. Proceeding with basic dump.");
|
||||||
|
goto skip_advanced_dump;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill in exception_pointers stuff
|
||||||
|
exception_information.ThreadId = crashinfo.base.exception_thread_id;
|
||||||
|
exception_information.ExceptionPointers = crashinfo.base.exception_pointers;
|
||||||
|
exception_information.ClientPointers = TRUE;
|
||||||
|
|
||||||
|
// use complex information if available
|
||||||
|
if (sizeof(crashinfo) != crashinfo.base.sz)
|
||||||
|
{
|
||||||
|
SendMessageW(dtd->hwndDlg, DNM_INFOMSG, 0, (LPARAM)L"Detailed crash information is unsupported version, only using exception information (if present).");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// design pattern: allocate some memory and don't plan to ever free it
|
||||||
|
user_stream_information = new MINIDUMP_USER_STREAM_INFORMATION;
|
||||||
|
user_stream_information->UserStreamArray = new MINIDUMP_USER_STREAM[1];
|
||||||
|
user_stream_information->UserStreamCount = 1;
|
||||||
|
|
||||||
|
user_stream_information->UserStreamArray[0].Type = CommentStreamW;
|
||||||
|
user_stream_information->UserStreamArray[0].Buffer = crashinfo.unhandled_cpp_exception_text;
|
||||||
|
user_stream_information->UserStreamArray[0].BufferSize = sizeof(crashinfo.unhandled_cpp_exception_text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
skip_advanced_dump:
|
||||||
|
|
||||||
std::wstring dumpfile_name =
|
std::wstring dumpfile_name =
|
||||||
dumpfile_folder +
|
dumpfile_folder +
|
||||||
procfn_basename + L'-' +
|
procfn_basename + L'-' +
|
||||||
|
@ -219,7 +332,9 @@ void __cdecl dumper_thread(void *data)
|
||||||
*ppid,
|
*ppid,
|
||||||
dumpfile,
|
dumpfile,
|
||||||
MINIDUMP_TYPE(MiniDumpWithThreadInfo|MiniDumpIgnoreInaccessibleMemory|MiniDumpWithIndirectlyReferencedMemory),
|
MINIDUMP_TYPE(MiniDumpWithThreadInfo|MiniDumpIgnoreInaccessibleMemory|MiniDumpWithIndirectlyReferencedMemory),
|
||||||
0, 0, 0);
|
exception_information.ExceptionPointers ? &exception_information : 0,
|
||||||
|
user_stream_information,
|
||||||
|
0);
|
||||||
|
|
||||||
SendMessageW(dtd->hwndDlg, DNM_DUMPFINISHED, 0, (LPARAM)dumpfile_name.c_str());
|
SendMessageW(dtd->hwndDlg, DNM_DUMPFINISHED, 0, (LPARAM)dumpfile_name.c_str());
|
||||||
}
|
}
|
||||||
|
@ -262,6 +377,10 @@ INT_PTR CALLBACK dialog_msghandler(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARA
|
||||||
AddStringToListbox(hwndDlg, std::wstring(L" Finished dump: ") + (wchar_t const *)lParam);
|
AddStringToListbox(hwndDlg, std::wstring(L" Finished dump: ") + (wchar_t const *)lParam);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
|
||||||
|
case DNM_INFOMSG:
|
||||||
|
AddStringToListbox(hwndDlg, (wchar_t const *)lParam);
|
||||||
|
return true;
|
||||||
|
|
||||||
case WM_COMMAND:
|
case WM_COMMAND:
|
||||||
if (LOWORD(wParam) == IDCLOSE && HIWORD(wParam) == BN_CLICKED)
|
if (LOWORD(wParam) == IDCLOSE && HIWORD(wParam) == BN_CLICKED)
|
||||||
{
|
{
|
||||||
|
@ -289,7 +408,7 @@ INT_PTR CALLBACK dialog_msghandler(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARA
|
||||||
|
|
||||||
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' ""version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
|
#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)
|
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
|
||||||
{
|
{
|
||||||
CoInitializeEx(0, COINIT_MULTITHREADED);
|
CoInitializeEx(0, COINIT_MULTITHREADED);
|
||||||
INITCOMMONCONTROLSEX iccx = {
|
INITCOMMONCONTROLSEX iccx = {
|
||||||
|
@ -303,7 +422,50 @@ int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLi
|
||||||
if (hwndDlg == 0)
|
if (hwndDlg == 0)
|
||||||
ExitProcess(2);
|
ExitProcess(2);
|
||||||
|
|
||||||
DumperThreadData dtd = { hwndDlg, hInstance };
|
// todo: figure out a commandline format and parse it
|
||||||
|
// todo: what about exception data? shared memory?
|
||||||
|
|
||||||
|
// idea: SetUnhandledExceptionFilter() in Aegisub.
|
||||||
|
// When it hits, fill in a global storage struct with various useful information including
|
||||||
|
// pointer to EXCEPTION_POINTERS struct, then launch w32dumper with arguments:
|
||||||
|
// -crash <pid> <address_of_global_struct>
|
||||||
|
// Since the size of the struct is known and Aegisub will be OpenProcess()'d with VM_READ
|
||||||
|
// privileges anyway, we can ReadProcessMemory() the struct out and parse it for interesting
|
||||||
|
// information. The EXCEPTION_POINTERS pointer will be to the address in Aegisub's VM but
|
||||||
|
// that's okay, MiniDumpWriteDump() can handle that.
|
||||||
|
// Last problem is then to make sure that Aegisub stays in the correct state (with exception
|
||||||
|
// pointers valid and all that), maybe just wait for the w32dumper process, then abort? Will
|
||||||
|
// other threads continue running then? If so, should they all be suspended?
|
||||||
|
//
|
||||||
|
// linkdump:
|
||||||
|
// SetUnhandledExceptionFilter: <http://msdn.microsoft.com/en-us/library/windows/desktop/ms680634%28v=vs.85%29.aspx>
|
||||||
|
// ReadProcessMemory: <http://msdn.microsoft.com/en-us/library/windows/desktop/ms680553%28v=vs.85%29.aspx>
|
||||||
|
|
||||||
|
DumperThreadData dtd = { hwndDlg };
|
||||||
|
|
||||||
|
{
|
||||||
|
int argc = 0;
|
||||||
|
// this will allocate a bit of memory, it's a waste of time to free it since the allocation
|
||||||
|
// disappears anyway when the process exits, and this process generally shouldn't be long-lived
|
||||||
|
// and this isn't a recurring allocation either.
|
||||||
|
wchar_t **argv = CommandLineToArgvW(GetCommandLineW(), &argc);
|
||||||
|
|
||||||
|
// accept command line args "-crash <pid> <infoblock_address>"
|
||||||
|
// infoblock_address is given as decimal, for simplicity
|
||||||
|
if (argc >= 4 && wcscmp(argv[1], L"-crash") == 0)
|
||||||
|
{
|
||||||
|
if (try_str2uint(argv[2], dtd.target_pid) && try_str2uint(argv[3], dtd.target_infoblock))
|
||||||
|
{
|
||||||
|
// well we got some values, the thread will act on them, no more to do here...
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dtd.target_pid = 0;
|
||||||
|
dtd.target_infoblock = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uintptr_t dumper_thread_handle = _beginthread(dumper_thread, 0, &dtd);
|
uintptr_t dumper_thread_handle = _beginthread(dumper_thread, 0, &dtd);
|
||||||
ShowWindow(hwndDlg, SW_SHOWNORMAL);
|
ShowWindow(hwndDlg, SW_SHOWNORMAL);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue