/* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "stdafx.h" #include "utilities/crashdump.h" #if defined(_WIN32) && defined(WITHDUMPGEN) #include #include #include #include #include #include #include "utilities/Globals.h" #include "utilities/cpu_features.h" #include "utilities/crashdump_channel.h" #include "version_info.h" #ifdef _MSC_VER #include #pragma comment(lib, "Dbghelp.lib") #pragma comment(lib, "psapi.lib") #endif namespace { // the crashes reported without an exception of the system constexpr DWORD code_abort{0xE0450001}; constexpr DWORD code_purecall{0xE0450002}; constexpr DWORD code_invalid_parameter{0xE0450003}; // the reports kept in crashdumps/, the older ones are removed at the start constexpr std::size_t reports_kept{10}; // "crash_YYYY-MM-DD_HH-MM-SS", the start of the names of the files of a report constexpr std::size_t report_stamp_length{25}; // the named values, written by the simulator as it goes and read without a lock when it crashes struct info_entry { char name[48]; char value[464]; }; info_entry info_entries[48]; std::atomic info_used{0}; std::mutex info_mutex; // the recent lines of the log, written under the lock of the log char log_ring[24 * 1024]; std::size_t log_written{0}; wchar_t log_paths[2][MAX_PATH * 2]{}; // taken at the installation, when it is still safe to wchar_t report_directory[MAX_PATH * 2]{}; wchar_t executable_stem[MAX_PATH]{}; char executable_path[MAX_PATH * 3]{}; char processor_name[49]{}; char system_version[96]{}; SYSTEMTIME start_time{}; DWORD main_thread_id{0}; // the report. with eu07_starter the block is shared with it and the starter writes the minidump crashdump::channel_data local_channel{}; crashdump::channel_data *channel{&local_channel}; HANDLE starter_request{nullptr}; HANDLE starter_done{nullptr}; // the report is written by a thread started beforehand: the crashed one may have no stack left, and a thread started // after a crash may wait forever for the lock of the loader the crashed one holds HANDLE report_request{nullptr}; HANDLE report_written{nullptr}; HANDLE report_done{nullptr}; HANDLE reporter_thread{nullptr}; DWORD reporter_thread_id{0}; volatile LONG crash_started{0}; EXCEPTION_POINTERS *crash_exception{nullptr}; DWORD crash_thread_id{0}; struct text_buffer { char *data; std::size_t capacity; std::size_t length; }; void append(text_buffer &Buffer, char const *Format, ...) { if (Buffer.length + 1 >= Buffer.capacity) { return; } va_list arguments; va_start(arguments, Format); auto const written{std::vsnprintf(Buffer.data + Buffer.length, Buffer.capacity - Buffer.length, Format, arguments)}; va_end(arguments); if (written > 0) { Buffer.length = std::min(Buffer.length + static_cast(written), Buffer.capacity - 1); } } void append_wide(text_buffer &Buffer, wchar_t const *Text) { if (Buffer.length + 1 >= Buffer.capacity) { return; } auto const written{::WideCharToMultiByte(CP_UTF8, 0, Text, -1, Buffer.data + Buffer.length, static_cast(Buffer.capacity - Buffer.length), nullptr, nullptr)}; if (written > 0) { // the count includes the terminating zero Buffer.length += static_cast(written) - 1; } Buffer.data[Buffer.length] = '\0'; } char const *exception_name(DWORD const Code) { switch (Code) { case EXCEPTION_ACCESS_VIOLATION: return "access violation"; case EXCEPTION_STACK_OVERFLOW: return "stack overflow"; case EXCEPTION_ILLEGAL_INSTRUCTION: return "illegal instruction"; case EXCEPTION_PRIV_INSTRUCTION: return "privileged instruction"; case EXCEPTION_IN_PAGE_ERROR: return "in page error"; case EXCEPTION_INT_DIVIDE_BY_ZERO: return "integer division by zero"; case EXCEPTION_INT_OVERFLOW: return "integer overflow"; case EXCEPTION_FLT_DIVIDE_BY_ZERO: case EXCEPTION_FLT_INVALID_OPERATION: case EXCEPTION_FLT_OVERFLOW: case EXCEPTION_FLT_UNDERFLOW: case EXCEPTION_FLT_INEXACT_RESULT: case EXCEPTION_FLT_DENORMAL_OPERAND: case EXCEPTION_FLT_STACK_CHECK: return "floating point exception"; case EXCEPTION_DATATYPE_MISALIGNMENT: return "datatype misalignment"; case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "array bounds exceeded"; case EXCEPTION_BREAKPOINT: return "breakpoint"; case 0xC0000374: return "heap corruption"; case 0xC0000409: return "stack buffer overrun"; case 0xE06D7363: return "unhandled C++ exception"; case code_abort: return "abort() called"; case code_purecall: return "pure virtual function call"; case code_invalid_parameter: return "invalid parameter passed to a C runtime function"; default: return "unknown"; } } #if defined(_MSC_VER) && defined(_WIN64) // the type and the message of an unhandled C++ exception, read from the structures msvc throws it with (image relative on x64). // guarded, the exception may come from anywhere void describe_cpp_exception(EXCEPTION_RECORD const &Record, text_buffer &Buffer) { if ((Record.ExceptionCode != 0xE06D7363) || (Record.NumberParameters < 4) || (Record.ExceptionInformation[0] != 0x19930520)) { return; } __try { auto const object{Record.ExceptionInformation[1]}; auto const throwinfo{Record.ExceptionInformation[2]}; auto const imagebase{Record.ExceptionInformation[3]}; if ((throwinfo == 0) || (imagebase == 0)) { return; } // ThrowInfo: attributes, unwind, forward compat, catchable type array auto const *types{reinterpret_cast(imagebase + reinterpret_cast(throwinfo)[3])}; auto const count{types[0]}; for (int index = 0; (index < count) && (index < 32); ++index) { // CatchableType: properties, type descriptor, displacement (mdisp, pdisp, vdisp), size, copy function auto const *type{reinterpret_cast(imagebase + types[1 + index])}; // TypeDescriptor: vftable, spare, decorated name auto const *name{reinterpret_cast(imagebase + type[1]) + 2 * sizeof(void *)}; if (index == 0) { append(Buffer, " C++ exception type: %.200s\n", name); } if ((std::strcmp(name, ".?AVexception@std@@") == 0) && (type[3] == -1)) { auto const *exception{reinterpret_cast(object + type[2])}; append(Buffer, " C++ exception message: %.1000s\n", exception->what()); break; } } } __except (EXCEPTION_EXECUTE_HANDLER) { append(Buffer, " (the C++ exception could not be read)\n"); } } #endif // the module and the offset in it. through the memory map rather than the module list of the loader, whose lock the crashed thread may hold void describe_address(void const *Address, text_buffer &Buffer) { MEMORY_BASIC_INFORMATION region{}; wchar_t path[MAX_PATH * 2]; if ((::VirtualQuery(Address, ®ion, sizeof(region)) == 0) || (region.Type != MEM_IMAGE) || (::GetMappedFileNameW(::GetCurrentProcess(), region.AllocationBase, path, MAX_PATH * 2) == 0)) { append(Buffer, " at 0x%p, outside of the modules\n", Address); return; } path[MAX_PATH * 2 - 1] = L'\0'; auto const *name{std::wcsrchr(path, L'\\')}; append(Buffer, " at 0x%p, ", Address); append_wide(Buffer, name != nullptr ? name + 1 : path); append(Buffer, "+0x%llx\n", static_cast(reinterpret_cast(Address) - reinterpret_cast(region.AllocationBase))); } void describe_crash(crashdump::channel_data &Data, SYSTEMTIME const &Now) { text_buffer buffer{Data.comment, crashdump::comment_capacity, 0}; Data.comment[0] = '\0'; append(buffer, "MaSzyna crash report\n\n"); append(buffer, "version: %s\n", Global.asVersion.c_str()); append(buffer, "build: %s, committed at %s\n", GIT_HASH, SRC_DATE); append(buffer, "executable: %s\n", executable_path); append(buffer, "started: %04u-%02u-%02u %02u:%02u:%02u\n", start_time.wYear, start_time.wMonth, start_time.wDay, start_time.wHour, start_time.wMinute, start_time.wSecond); append(buffer, "crashed: %04u-%02u-%02u %02u:%02u:%02u\n", Now.wYear, Now.wMonth, Now.wDay, Now.wHour, Now.wMinute, Now.wSecond); append(buffer, "processor: %s\n", processor_name); append(buffer, "system: %s\n\n", system_version); if ((crash_exception != nullptr) && (crash_exception->ExceptionRecord != nullptr)) { auto const &record{*crash_exception->ExceptionRecord}; append(buffer, "exception: 0x%08lX, %s\n", static_cast(record.ExceptionCode), exception_name(record.ExceptionCode)); describe_address(record.ExceptionAddress, buffer); if (((record.ExceptionCode == EXCEPTION_ACCESS_VIOLATION) || (record.ExceptionCode == EXCEPTION_IN_PAGE_ERROR)) && (record.NumberParameters >= 2)) { auto const operation{record.ExceptionInformation[0]}; append(buffer, " %s of address 0x%p\n", operation == 0 ? "read" : (operation == 8 ? "execution" : "write"), reinterpret_cast(record.ExceptionInformation[1])); } #if defined(_MSC_VER) && defined(_WIN64) describe_cpp_exception(record, buffer); #endif } append(buffer, "thread: %lu%s\n\n", static_cast(crash_thread_id), crash_thread_id == main_thread_id ? " (main)" : ""); PROCESS_MEMORY_COUNTERS_EX memory{}; memory.cb = sizeof(memory); if (::GetProcessMemoryInfo(::GetCurrentProcess(), reinterpret_cast(&memory), sizeof(memory)) != FALSE) { append(buffer, "process memory: %llu MB private, %llu MB working set, %llu MB peak working set\n", static_cast(memory.PrivateUsage >> 20), static_cast(memory.WorkingSetSize >> 20), static_cast(memory.PeakWorkingSetSize >> 20)); } MEMORYSTATUSEX system{}; system.dwLength = sizeof(system); if (::GlobalMemoryStatusEx(&system) != FALSE) { append(buffer, "system memory: %llu MB of %llu MB free, %llu MB of %llu MB of the page file free\n", static_cast(system.ullAvailPhys >> 20), static_cast(system.ullTotalPhys >> 20), static_cast(system.ullAvailPageFile >> 20), static_cast(system.ullTotalPageFile >> 20)); } append(buffer, "dump: %s\n\n", Data.full_memory != 0 ? "full memory" : "small"); append(buffer, "info:\n"); auto const infocount{std::min(info_used.load(), std::size(info_entries))}; for (std::size_t index = 0; index < infocount; ++index) { append(buffer, " %.47s: %.463s\n", info_entries[index].name, info_entries[index].value); } append(buffer, "\nrecent log:\n"); auto const written{log_written}; auto const capacity{std::size(log_ring)}; if (written <= capacity) { append(buffer, "%.*s", static_cast(written), log_ring); } else { // the ring went around: from the oldest whole line on auto const start{written % capacity}; auto skip{start}; while ((skip < capacity) && (log_ring[skip] != '\n')) { ++skip; } if (skip < capacity) { append(buffer, "%.*s", static_cast(capacity - skip - 1), log_ring + skip + 1); } append(buffer, "%.*s", static_cast(start), log_ring); } Data.comment_length = static_cast(buffer.length); } void write_file(wchar_t const *Path, char const *Text, std::size_t const Length) { auto const file{::CreateFileW(Path, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)}; if (file == INVALID_HANDLE_VALUE) { return; } DWORD written{0}; ::WriteFile(file, Text, static_cast(Length), &written, nullptr); ::CloseHandle(file); } void write_report() { auto &data{*channel}; SYSTEMTIME now; ::GetLocalTime(&now); // crashdumps\crash__