diff --git a/build/Aegisub/Aegisub.vcxproj b/build/Aegisub/Aegisub.vcxproj index ebd159711..86b816e76 100644 --- a/build/Aegisub/Aegisub.vcxproj +++ b/build/Aegisub/Aegisub.vcxproj @@ -348,7 +348,10 @@ - + + true + + diff --git a/build/Aegisub/Aegisub.vcxproj.filters b/build/Aegisub/Aegisub.vcxproj.filters index bb8fba4db..29ce46093 100644 --- a/build/Aegisub/Aegisub.vcxproj.filters +++ b/build/Aegisub/Aegisub.vcxproj.filters @@ -1238,6 +1238,9 @@ Utilities\Logging + + Utilities\Logging + diff --git a/src/crash_writer_minidump.cpp b/src/crash_writer_minidump.cpp new file mode 100644 index 000000000..da07e5a01 --- /dev/null +++ b/src/crash_writer_minidump.cpp @@ -0,0 +1,153 @@ +// Copyright (c) 2014, Thomas Goyne +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// +// Aegisub Project http://www.aegisub.org/ + +#include "config.h" + +#include "crash_writer.h" + +#include "version.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +extern EXCEPTION_POINTERS *wxGlobalSEInformation; + +namespace { +wchar_t crash_dump_path[MAX_PATH]; +agi::fs::path crashlog_path; + +using MiniDumpWriteDump = BOOL(WINAPI *)( + HANDLE hProcess, + DWORD dwPid, + HANDLE hFile, + MINIDUMP_TYPE DumpType, + CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam); + +struct dump_thread_state { + std::mutex start_mutex; + std::condition_variable start_cv; + + std::atomic exit = false; + EXCEPTION_POINTERS *ep = nullptr; + DWORD thread_id = 0; + + // Must be last so everything else is initialized before it + std::thread thread; + + dump_thread_state() : thread([&] { main(); }) { } + + void main() { + auto module = LoadLibrary(L"dbghelp.dll"); + if (!module) return; + + auto fn = reinterpret_cast(GetProcAddress(module, "MiniDumpWriteDump")); + if (!fn) { + FreeLibrary(module); + return; + } + + std::unique_lock lock(start_mutex); + start_cv.wait(lock, [&] { return ep || exit; }); + if (ep) + write_dump(fn); + FreeLibrary(module); + } + + void write_dump(MiniDumpWriteDump fn) { + auto file = CreateFile(crash_dump_path, + GENERIC_WRITE, + 0, // no sharing + nullptr, + CREATE_NEW, + FILE_ATTRIBUTE_NORMAL, + nullptr); + if (file == INVALID_HANDLE_VALUE) return; + + MINIDUMP_EXCEPTION_INFORMATION info; + info.ThreadId = thread_id; + info.ExceptionPointers = ep; + info.ClientPointers = FALSE; + + fn(GetCurrentProcess(), GetCurrentProcessId(), file, MiniDumpNormal, &info, nullptr, nullptr); + + CloseHandle(file); + } +}; + +std::unique_ptr dump_thread; +} + +namespace crash_writer { +void Initialize(agi::fs::path const& path) { + crashlog_path = path / "crashlog.txt"; + + auto dump_path = path / "crashdumps"; + agi::fs::CreateDirectory(dump_path); + + const auto path_str = (dump_path / GetVersionNumber()).wstring(); + wcscpy_s(crash_dump_path, path_str.c_str()); + auto len = path_str.size(); + + const auto t = time(nullptr); + struct tm tm; + localtime_s(&tm, &t); + + len += wcsftime(crash_dump_path + len, MAX_PATH - len, L"-%Y-%m-%d-%H-%M-%S-", &tm); + len += swprintf_s(crash_dump_path + len, MAX_PATH - len, L"%d", GetCurrentProcessId()); + wcscpy_s(crash_dump_path + len, MAX_PATH - len, L".dmp"); + + if (!dump_thread) + dump_thread = agi::util::make_unique(); +} + +void Cleanup() { + dump_thread->exit = true; + dump_thread->start_cv.notify_all(); + dump_thread->thread.join(); + dump_thread.reset(); +} + +void Write() { + dump_thread->ep = wxGlobalSEInformation; + dump_thread->thread_id = GetCurrentThreadId(); + dump_thread->start_cv.notify_all(); + dump_thread->thread.join(); + dump_thread.reset(); +} + +void Write(std::string const& error) { + boost::filesystem::ofstream file(crashlog_path, std::ios::app); + if (file.is_open()) { + file << agi::util::strftime("--- %y-%m-%d %H:%M:%S ------------------\n"); + file << boost::format("VER - %s\n") % GetAegisubLongVersionString(); + file << boost::format("EXC - Aegisub has crashed with unhandled exception \"%s\".\n") % error; + file << "----------------------------------------\n\n"; + file.close(); + } +} +} diff --git a/src/main.cpp b/src/main.cpp index 05a7672b9..3437f1d7b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -311,6 +311,7 @@ int AegisubApp::OnExit() { // Keep this last! delete agi::log::log; + crash_writer::Cleanup(); return wxApp::OnExit(); }