/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2022 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "xenia/base/debugging.h" #include "xenia/base/logging.h" #include "xenia/emulator.h" #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_private.h" #include "xenia/kernel/xthread.h" #include "xenia/ui/imgui_dialog.h" #include "xenia/ui/imgui_drawer.h" #include "xenia/ui/window.h" #include "xenia/ui/windowed_app_context.h" namespace xe { namespace kernel { namespace xboxkrnl { void DbgBreakPoint_entry() { xe::debugging::Break(); } DECLARE_XBOXKRNL_EXPORT2(DbgBreakPoint, kDebug, kStub, kImportant); // https://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx typedef struct { xe::be type; xe::be name_ptr; xe::be thread_id; xe::be flags; } X_THREADNAME_INFO; static_assert_size(X_THREADNAME_INFO, 0x10); void HandleSetThreadName(pointer_t record) { // SetThreadName. FFS. // https://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx // TODO(benvanik): check record->number_parameters to make sure it's a // correct size. auto thread_info = reinterpret_cast(&record->exception_information[0]); assert_true(thread_info->type == 0x1000); if (!thread_info->name_ptr) { XELOGD("SetThreadName called with null name_ptr"); return; } // 4D5307D6 (and its demo) has a bug where it ends up passing freed memory for // the name, so at the point of SetThreadName it's filled with junk. // TODO(gibbed): cvar for thread name encoding for conversion, some games use // SJIS and there's no way to automatically know this. auto name = std::string( kernel_memory()->TranslateVirtual(thread_info->name_ptr)); std::replace_if( name.begin(), name.end(), [](auto c) { return c < 32 || c > 127; }, '?'); object_ref thread; if (thread_info->thread_id == -1) { // Current thread. thread = retain_object(XThread::GetCurrentThread()); } else { // Lookup thread by ID. thread = kernel_state()->GetThreadByID(thread_info->thread_id); } if (thread) { XELOGD("SetThreadName({}, {})", thread->thread_id(), name); thread->set_name(name); } // TODO(benvanik): unwinding required here? } typedef struct { xe::be mdisp; xe::be pdisp; xe::be vdisp; } x_PMD; typedef struct { xe::be properties; xe::be type_ptr; x_PMD this_displacement; xe::be size_or_offset; xe::be copy_function_ptr; } x_s__CatchableType; typedef struct { xe::be number_catchable_types; xe::be catchable_type_ptrs[1]; } x_s__CatchableTypeArray; typedef struct { xe::be attributes; xe::be unwind_ptr; xe::be forward_compat_ptr; xe::be catchable_type_array_ptr; } x_s__ThrowInfo; void HandleCppException(pointer_t record) { // C++ exception. // https://blogs.msdn.com/b/oldnewthing/archive/2010/07/30/10044061.aspx // http://www.drdobbs.com/visual-c-exception-handling-instrumentat/184416600 // http://www.openrce.org/articles/full_view/21 assert_true(record->number_parameters == 3); assert_true(record->exception_information[0] == 0x19930520); auto thrown_ptr = record->exception_information[1]; auto thrown = kernel_memory()->TranslateVirtual(thrown_ptr); auto vftable_ptr = *reinterpret_cast*>(thrown); auto throw_info_ptr = record->exception_information[2]; auto throw_info = kernel_memory()->TranslateVirtual(throw_info_ptr); auto catchable_types = kernel_memory()->TranslateVirtual( throw_info->catchable_type_array_ptr); // xe::debugging::Break(); XELOGE("Guest attempted to throw a C++ exception!"); } void RtlRaiseException_entry(pointer_t record) { switch (record->code) { case 0x406D1388: { HandleSetThreadName(record); return; } case 0xE06D7363: { HandleCppException(record); return; } } // TODO(benvanik): unwinding. // This is going to suck. // xe::debugging::Break(); // RtlRaiseException definitely wasn't a noreturn function, we can return // safe-ish XELOGE("Guest attempted to trigger a breakpoint!"); } DECLARE_XBOXKRNL_EXPORT2(RtlRaiseException, kDebug, kStub, kImportant); void KeBugCheckEx_entry(dword_t code, dword_t param1, dword_t param2, dword_t param3, dword_t param4) { auto msg = fmt::format("*** STOP: 0x{:08X} (0x{:08X}, 0x{:08X}, 0x{:08X}, 0x{:08X})", static_cast(code), static_cast(param1), static_cast(param2), static_cast(param3), static_cast(param4)); XELOGE("{}", msg); fflush(stdout); if (xe::debugging::IsDebuggerAttached()) { xe::debugging::Break(); } // Show crash dialog and suspend the guest thread instead of killing the // host process. auto current_thread = kernel::XThread::GetCurrentThread(); const auto* emulator = kernel_state()->emulator(); auto* display_window = emulator->display_window(); auto* imgui_drawer = emulator->imgui_drawer(); if (display_window && imgui_drawer) { auto dlg_msg = fmt::format( "The guest kernel has crashed (KeBugCheck).\n\n{}\n\n" "The faulting thread has been suspended.", msg); display_window->app_context().CallInUIThreadSynchronous( [imgui_drawer, &dlg_msg]() { xe::ui::ImGuiDialog::ShowMessageBox(imgui_drawer, "Guest Kernel Crash", dlg_msg); }); } if (current_thread) { current_thread->Suspend(nullptr); } } DECLARE_XBOXKRNL_EXPORT2(KeBugCheckEx, kDebug, kStub, kImportant); void KeBugCheck_entry(dword_t code) { KeBugCheckEx_entry(code, 0, 0, 0, 0); } DECLARE_XBOXKRNL_EXPORT2(KeBugCheck, kDebug, kImplemented, kImportant); } // namespace xboxkrnl } // namespace kernel } // namespace xe DECLARE_XBOXKRNL_EMPTY_REGISTER_EXPORTS(Debug);