/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2021 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include #include "xenia/base/console.h" #include "xenia/base/cvar.h" #include "xenia/base/main_win.h" #include "xenia/base/platform_win.h" #include "xenia/kernel/kernel_state.cc" #include "xenia/ui/windowed_app.h" #include "xenia/ui/windowed_app_context_win.h" #include "version.h" #include DEFINE_bool(enable_console, false, "Open a console window with the main window", "Logging"); static uintptr_t g_xenia_exe_base = 0; static size_t g_xenia_exe_size = 0; static HMODULE probe_for_module(void* addr) { // get 65k aligned addr downwards to probe for MZ uintptr_t base = reinterpret_cast(addr) & ~0xFFFFULL; constexpr unsigned max_search_iters = (64 * (1024 * 1024)) / 65536; // search down at most 64 mb (we do it in // batches of 64k so its pretty quick). i think its reasonable to // expect no module will be > 64mb // break if access violation thrown, we're definitely not a PE in that case __try { for (unsigned i = 0; i < max_search_iters; ++i) { if (*reinterpret_cast(base) == 'ZM') { return reinterpret_cast(base); } else { base -= 65536; } } return nullptr; } __except (EXCEPTION_EXECUTE_HANDLER) { return nullptr; } } static constexpr auto XENIA_ERROR_LANGUAGE = MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT); struct HostExceptionReport { _EXCEPTION_POINTERS* const ExceptionInfo; size_t Report_Scratchpos; const DWORD last_win32_error; const NTSTATUS last_ntstatus; const int errno_value; char Report_Scratchbuffer[2048]; unsigned int address_format_ring_index; char formatted_addresses[16][128]; void AddString(const char* s); static char* ChompNewlines(char* s); HostExceptionReport(_EXCEPTION_POINTERS* _ExceptionInfo) : ExceptionInfo(_ExceptionInfo), Report_Scratchpos(0u), last_win32_error(GetLastError()), #if XE_ARCH_AMD64 last_ntstatus(__readgsdword(0x1250)), #elif XE_ARCH_ARM64 // TEB.LastStatusValue at offset 0x1250 (same as x64). // ARM64 uses x18 register for TEB base instead of GS segment. last_ntstatus(__readx18dword(0x1250)), #endif errno_value(errno), address_format_ring_index(0) { memset(Report_Scratchbuffer, 0, sizeof(Report_Scratchbuffer)); } void DisplayExceptionMessage() { MessageBoxA(nullptr, Report_Scratchbuffer, "Unhandled Exception in Xenia", MB_ICONERROR); } const char* GetFormattedAddress(uintptr_t address); const char* GetFormattedAddress(PVOID address) { return GetFormattedAddress(reinterpret_cast(address)); } }; char* HostExceptionReport::ChompNewlines(char* s) { if (!s) { return nullptr; } unsigned read_pos = 0; unsigned write_pos = 0; while (true) { char current = s[read_pos++]; if (current == '\n') { continue; } s[write_pos++] = current; if (!current) { break; } } return s; } void HostExceptionReport::AddString(const char* s) { size_t ln = strlen(s); for (size_t i = 0; i < ln; ++i) { Report_Scratchbuffer[i + Report_Scratchpos] = s[i]; } Report_Scratchpos += ln; } const char* HostExceptionReport::GetFormattedAddress(uintptr_t address) { char (¤t_buffer)[128] = formatted_addresses[address_format_ring_index++ % 16]; /* if (address >= g_xenia_exe_base && address - g_xenia_exe_base < g_xenia_exe_size) { uintptr_t offset = address - g_xenia_exe_base; sprintf_s(current_buffer, "xenia_canary.exe+%llX", offset); } else */ { HMODULE hmod_for = probe_for_module((void*)address); if (hmod_for) { // get the module filename, then chomp off all but the actual file name // (full path is obtained) char tmp_module_name[MAX_PATH + 1]; GetModuleFileNameA(hmod_for, tmp_module_name, sizeof(tmp_module_name)); size_t search_back = strlen(tmp_module_name); // hunt backwards for the last sep while (tmp_module_name[--search_back] != '\\'); // MessageBoxA(nullptr, tmp_module_name, "ffds", MB_OK); sprintf_s(current_buffer, "%s+%llX", tmp_module_name + search_back + 1, address - reinterpret_cast(hmod_for)); } else { sprintf_s(current_buffer, "0x%llX", address); } } return current_buffer; } using ExceptionInfoCategoryHandler = bool (*)(HostExceptionReport* report); static char* Ntstatus_msg(NTSTATUS status) { char* statusmsg = nullptr; FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, GetModuleHandleA("ntdll.dll"), status, XENIA_ERROR_LANGUAGE, (LPSTR)&statusmsg, 0, NULL); return statusmsg; } static bool exception_pointers_handler(HostExceptionReport* report) { PVOID exception_addr = report->ExceptionInfo->ExceptionRecord->ExceptionAddress; #if XE_ARCH_AMD64 DWORD64 last_stackpointer = report->ExceptionInfo->ContextRecord->Rsp; DWORD64 last_rip = report->ExceptionInfo->ContextRecord->Rip; #elif XE_ARCH_ARM64 DWORD64 last_stackpointer = report->ExceptionInfo->ContextRecord->Sp; DWORD64 last_rip = report->ExceptionInfo->ContextRecord->Pc; #endif DWORD except_code = report->ExceptionInfo->ExceptionRecord->ExceptionCode; std::string build = ( #ifdef XE_BUILD_IS_PR "PR#" XE_BUILD_PR_NUMBER " - " #endif XE_BUILD_BRANCH "@" XE_BUILD_COMMIT_SHORT " on " XE_BUILD_DATE); std::string title_info = "Title not started yet."; if (xe::kernel::kernel_state()) { if (xe::kernel::kernel_state()->emulator()->is_title_open()) { const uint32_t title_id = xe::kernel::kernel_state()->title_id(); const std::string title_name = xe::kernel::kernel_state()->emulator()->title_name(); const std::string title_version = xe::kernel::kernel_state()->emulator()->title_version(); title_info = fmt::format("{} ({:08X}) - {}", title_name, title_id, title_version); } } const std::string except_message = fmt::format( "Exception encountered!\nTitle Info: {}\nBuild: {}\nException address: " "{}\nStackpointer: {}\nInstruction pointer: {}\nExceptionCode: 0x{} " "({})\n", title_info, build.c_str(), report->GetFormattedAddress(exception_addr), report->GetFormattedAddress(last_stackpointer), report->GetFormattedAddress(last_rip), except_code, HostExceptionReport::ChompNewlines(Ntstatus_msg(except_code))); report->AddString(except_message.c_str()); return true; } static bool exception_win32_error_handle(HostExceptionReport* report) { if (!report->last_win32_error) { return false; // no error, nothing to do } // todo: formatmessage char win32_error_buf[512]; // its ok if we dont free statusmsg, we're exiting anyway char* statusmsg = nullptr; FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, report->last_win32_error, XENIA_ERROR_LANGUAGE, (LPSTR)&statusmsg, 0, nullptr); sprintf_s(win32_error_buf, "Last Win32 Error: 0x%X (%s)\n", report->last_win32_error, HostExceptionReport::ChompNewlines(statusmsg)); report->AddString(win32_error_buf); return true; } static bool exception_ntstatus_error_handle(HostExceptionReport* report) { if (!report->last_ntstatus) { return false; } // todo: formatmessage char win32_error_buf[512]; sprintf_s(win32_error_buf, "Last NTSTATUS: 0x%X (%s)\n", report->last_ntstatus, Ntstatus_msg(report->last_ntstatus)); report->AddString(win32_error_buf); return true; } static bool exception_cerror_handle(HostExceptionReport* report) { if (!report->errno_value) { return false; } char errno_buffer[512]; sprintf_s(errno_buffer, "Last errno value: 0x%X (%s)\n", report->errno_value, strerror(report->errno_value)); report->AddString(errno_buffer); return true; } static bool thread_name_handle(HostExceptionReport* report) { // ll GetThreadDescription(HANDLE hThread, PWSTR *ppszThreadDescription) FARPROC description_getter = GetProcAddress(GetModuleHandleA("kernel32.dll"), "GetThreadDescription"); if (!description_getter) { return false; } PWSTR descr = nullptr; reinterpret_cast(description_getter)( GetCurrentThread(), &descr); if (!descr) { return false; } char result_buffer[512]; sprintf_s(result_buffer, "Faulting thread name: %ws\n", descr); report->AddString(result_buffer); return true; } static ExceptionInfoCategoryHandler host_exception_category_handlers[] = { exception_pointers_handler, exception_win32_error_handle, exception_ntstatus_error_handle, exception_cerror_handle, thread_name_handle}; LONG _UnhandledExceptionFilter(_EXCEPTION_POINTERS* ExceptionInfo) { HostExceptionReport report{ExceptionInfo}; for (auto&& handler : host_exception_category_handlers) { __try { if (!handler(&report)) { continue; } } __except (EXCEPTION_EXECUTE_HANDLER) { report.AddString("\n"); } } report.DisplayExceptionMessage(); return EXCEPTION_CONTINUE_SEARCH; } int WINAPI wWinMain(HINSTANCE hinstance, HINSTANCE hinstance_prev, LPWSTR command_line, int show_cmd) { MODULEINFO modinfo; GetModuleInformation(GetCurrentProcess(), (HMODULE)hinstance, &modinfo, sizeof(MODULEINFO)); g_xenia_exe_base = reinterpret_cast(hinstance); g_xenia_exe_size = modinfo.SizeOfImage; int result; SetUnhandledExceptionFilter(_UnhandledExceptionFilter); { xe::ui::Win32WindowedAppContext app_context(hinstance, show_cmd); // TODO(Triang3l): Initialize creates a window. Set DPI awareness via the // manifest. if (!app_context.Initialize()) { return EXIT_FAILURE; } std::unique_ptr app = xe::ui::GetWindowedAppCreator()(app_context); if (!xe::ParseWin32LaunchArguments(false, app->GetPositionalOptionsUsage(), app->GetPositionalOptions(), nullptr)) { return EXIT_FAILURE; } // Initialize COM on the UI thread with the apartment-threaded concurrency // model, so dialogs can be used. if (FAILED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED))) { return EXIT_FAILURE; } xe::InitializeWin32App(app->GetName()); if (app->OnInitialize()) { // TODO(Triang3l): Rework this, need to initialize the console properly, // disable has_console_attached_ by default in windowed apps, and attach // only if needed. if (cvars::enable_console) { xe::AttachConsole(); } result = app_context.RunMainMessageLoop(); } else { result = EXIT_FAILURE; } app->InvokeOnDestroy(); } // Logging may still be needed in the destructors. xe::ShutdownWin32App(); CoUninitialize(); return result; }