mirror of
https://github.com/citron-neo/emulator.git
synced 2026-07-05 15:21:57 -07:00
Handle unreachable NCE patch relocations
This commit is contained in:
@@ -199,6 +199,10 @@ bool IsNceEnabled() {
|
||||
return is_nce_enabled;
|
||||
}
|
||||
|
||||
void DisableNceForCurrentProcess() {
|
||||
is_nce_enabled = false;
|
||||
}
|
||||
|
||||
bool IsDockedMode() {
|
||||
return values.use_docked_mode.GetValue() == Settings::ConsoleMode::Docked;
|
||||
}
|
||||
|
||||
@@ -833,6 +833,7 @@ bool IsGPULevelNormal();
|
||||
bool IsFastmemEnabled();
|
||||
bool IsCpuUltraLowAccuracy();
|
||||
void SetNceEnabled(bool is_64bit);
|
||||
void DisableNceForCurrentProcess();
|
||||
bool IsNceEnabled();
|
||||
|
||||
bool IsDockedMode();
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/signal_chain.h"
|
||||
#include "core/arm/nce/arm_nce.h"
|
||||
#include "core/arm/nce/interpreter_visitor.h"
|
||||
@@ -24,6 +27,34 @@ namespace {
|
||||
struct sigaction g_orig_bus_action;
|
||||
struct sigaction g_orig_segv_action;
|
||||
|
||||
void ForwardSignalToOriginalAction(int sig, siginfo_t* info, void* raw_context,
|
||||
const struct sigaction& original) {
|
||||
if ((original.sa_flags & SA_SIGINFO) != 0 && original.sa_sigaction != nullptr) {
|
||||
original.sa_sigaction(sig, info, raw_context);
|
||||
return;
|
||||
}
|
||||
|
||||
if (original.sa_handler == SIG_IGN) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (original.sa_handler == SIG_DFL) {
|
||||
Common::SigAction(sig, &original, nullptr);
|
||||
syscall(SYS_tkill, gettid(), sig);
|
||||
return;
|
||||
}
|
||||
|
||||
if (original.sa_handler != nullptr) {
|
||||
original.sa_handler(sig);
|
||||
return;
|
||||
}
|
||||
|
||||
struct sigaction default_action {};
|
||||
default_action.sa_handler = SIG_DFL;
|
||||
Common::SigAction(sig, &default_action, nullptr);
|
||||
syscall(SYS_tkill, gettid(), sig);
|
||||
}
|
||||
|
||||
// Verify assembly offsets.
|
||||
using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters;
|
||||
static_assert(offsetof(NativeExecutionParameters, native_context) == TpidrEl0NativeContext);
|
||||
@@ -31,11 +62,43 @@ static_assert(offsetof(NativeExecutionParameters, lock) == TpidrEl0Lock);
|
||||
static_assert(offsetof(NativeExecutionParameters, magic) == TpidrEl0TlsMagic);
|
||||
|
||||
fpsimd_context* GetFloatingPointState(mcontext_t& host_ctx) {
|
||||
_aarch64_ctx* header = reinterpret_cast<_aarch64_ctx*>(&host_ctx.__reserved);
|
||||
while (header->magic != FPSIMD_MAGIC) {
|
||||
header = reinterpret_cast<_aarch64_ctx*>(reinterpret_cast<char*>(header) + header->size);
|
||||
auto* const begin = reinterpret_cast<char*>(&host_ctx.__reserved);
|
||||
auto* const end = begin + sizeof(host_ctx.__reserved);
|
||||
|
||||
for (auto* ptr = begin; ptr + sizeof(_aarch64_ctx) <= end;) {
|
||||
auto* header = reinterpret_cast<_aarch64_ctx*>(ptr);
|
||||
|
||||
if (header->magic == FPSIMD_MAGIC) {
|
||||
if (header->size >= sizeof(fpsimd_context) &&
|
||||
static_cast<size_t>(end - ptr) >= sizeof(fpsimd_context)) {
|
||||
return reinterpret_cast<fpsimd_context*>(header);
|
||||
}
|
||||
|
||||
LOG_CRITICAL(Core_ARM,
|
||||
"Malformed NCE signal frame FPSIMD context: size={:#x}, "
|
||||
"remaining={:#x}",
|
||||
header->size, static_cast<size_t>(end - ptr));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (header->magic == 0 && header->size == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (header->size < sizeof(_aarch64_ctx) ||
|
||||
header->size > static_cast<size_t>(end - ptr)) {
|
||||
LOG_CRITICAL(Core_ARM,
|
||||
"Malformed NCE signal frame context entry: magic={:#x}, size={:#x}, "
|
||||
"remaining={:#x}",
|
||||
header->magic, header->size, static_cast<size_t>(end - ptr));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ptr += header->size;
|
||||
}
|
||||
return reinterpret_cast<fpsimd_context*>(header);
|
||||
|
||||
LOG_CRITICAL(Core_ARM, "NCE signal frame is missing FPSIMD context");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
using namespace Common::Literals;
|
||||
@@ -53,6 +116,9 @@ void* ArmNce::RestoreGuestContext(void* raw_context) {
|
||||
|
||||
// Retrieve the host floating point state.
|
||||
auto* fpctx = GetFloatingPointState(host_ctx);
|
||||
if (fpctx == nullptr) {
|
||||
UNREACHABLE_MSG("NCE cannot restore guest context without FPSIMD signal state");
|
||||
}
|
||||
|
||||
// Save host callee-saved registers.
|
||||
std::memcpy(guest_ctx->host_ctx.host_saved_vregs.data(), &fpctx->vregs[8],
|
||||
@@ -82,6 +148,9 @@ void ArmNce::SaveGuestContext(GuestContext* guest_ctx, void* raw_context) {
|
||||
|
||||
// Retrieve the host floating point state.
|
||||
auto* fpctx = GetFloatingPointState(host_ctx);
|
||||
if (fpctx == nullptr) {
|
||||
UNREACHABLE_MSG("NCE cannot save guest context without FPSIMD signal state");
|
||||
}
|
||||
|
||||
// Save all guest registers except tpidr_el0.
|
||||
std::memcpy(guest_ctx->cpu_registers.data(), host_ctx.regs, sizeof(host_ctx.regs));
|
||||
@@ -115,14 +184,26 @@ bool ArmNce::HandleFailedGuestFault(GuestContext* guest_ctx, void* raw_info, voi
|
||||
// We can't handle the access, so determine why we crashed.
|
||||
const bool is_prefetch_abort = host_ctx.pc == reinterpret_cast<u64>(info->si_addr);
|
||||
|
||||
// For data aborts, skip the instruction and return to guest code.
|
||||
// This will allow games to continue in many scenarios where they would otherwise crash.
|
||||
if (!is_prefetch_abort) {
|
||||
host_ctx.pc += 4;
|
||||
return true;
|
||||
u32 instruction{};
|
||||
std::memcpy(&instruction, reinterpret_cast<const void*>(host_ctx.pc), sizeof(instruction));
|
||||
LOG_CRITICAL(Core_ARM,
|
||||
"Unhandled NCE data abort: pc={:#x}, fault_addr={:#x}, instruction={:#010x}",
|
||||
host_ctx.pc, reinterpret_cast<u64>(info->si_addr), instruction);
|
||||
guest_ctx->esr_el1.fetch_or(static_cast<u64>(HaltReason::DataAbort));
|
||||
|
||||
// Forcibly mark the context as locked. We are still running.
|
||||
auto& thread_params = guest_ctx->parent->m_running_thread->GetNativeExecutionParameters();
|
||||
thread_params.lock.store(SpinLockLocked);
|
||||
|
||||
// Return to host.
|
||||
SaveGuestContext(guest_ctx, raw_context);
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is a prefetch abort.
|
||||
LOG_CRITICAL(Core_ARM, "Unhandled NCE prefetch abort: pc={:#x}, fault_addr={:#x}",
|
||||
host_ctx.pc, reinterpret_cast<u64>(info->si_addr));
|
||||
guest_ctx->esr_el1.fetch_or(static_cast<u64>(HaltReason::PrefetchAbort));
|
||||
|
||||
// Forcibly mark the context as locked. We are still running.
|
||||
@@ -141,6 +222,9 @@ bool ArmNce::HandleFailedGuestFault(GuestContext* guest_ctx, void* raw_info, voi
|
||||
bool ArmNce::HandleGuestAlignmentFault(GuestContext* guest_ctx, void* raw_info, void* raw_context) {
|
||||
auto& host_ctx = static_cast<ucontext_t*>(raw_context)->uc_mcontext;
|
||||
auto* fpctx = GetFloatingPointState(host_ctx);
|
||||
if (fpctx == nullptr) {
|
||||
UNREACHABLE_MSG("NCE cannot handle guest alignment fault without FPSIMD signal state");
|
||||
}
|
||||
auto& memory = guest_ctx->system->ApplicationMemory();
|
||||
|
||||
// Match and execute an instruction.
|
||||
@@ -171,11 +255,13 @@ bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, voi
|
||||
}
|
||||
|
||||
void ArmNce::HandleHostAlignmentFault(int sig, void* raw_info, void* raw_context) {
|
||||
return g_orig_bus_action.sa_sigaction(sig, static_cast<siginfo_t*>(raw_info), raw_context);
|
||||
ForwardSignalToOriginalAction(sig, static_cast<siginfo_t*>(raw_info), raw_context,
|
||||
g_orig_bus_action);
|
||||
}
|
||||
|
||||
void ArmNce::HandleHostAccessFault(int sig, void* raw_info, void* raw_context) {
|
||||
return g_orig_segv_action.sa_sigaction(sig, static_cast<siginfo_t*>(raw_info), raw_context);
|
||||
ForwardSignalToOriginalAction(sig, static_cast<siginfo_t*>(raw_info), raw_context,
|
||||
g_orig_segv_action);
|
||||
}
|
||||
|
||||
void ArmNce::LockThread(Kernel::KThread* thread) {
|
||||
@@ -302,7 +388,7 @@ void ArmNce::Initialize() {
|
||||
alignment_fault_action.sa_sigaction =
|
||||
reinterpret_cast<HandlerType>(&ArmNce::GuestAlignmentFaultSignalHandler);
|
||||
alignment_fault_action.sa_mask = signal_mask;
|
||||
Common::SigAction(GuestAlignmentFaultSignal, &alignment_fault_action, nullptr);
|
||||
Common::SigAction(GuestAlignmentFaultSignal, &alignment_fault_action, &g_orig_bus_action);
|
||||
|
||||
struct sigaction access_fault_action {};
|
||||
access_fault_action.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART;
|
||||
|
||||
+131
-27
@@ -42,6 +42,35 @@ Patcher::Patcher() : c(m_patch_instructions) {
|
||||
|
||||
Patcher::~Patcher() = default;
|
||||
|
||||
bool Patcher::IsValidBranchOffset(ptrdiff_t offset) noexcept {
|
||||
return offset >= MinRelativeBranchOffset && offset <= MaxRelativeBranchOffset &&
|
||||
offset % sizeof(u32) == 0;
|
||||
}
|
||||
|
||||
ptrdiff_t Patcher::CalculateBranchToPatchOffset(PatchMode patch_mode, size_t patch_size,
|
||||
size_t remaining_program_size,
|
||||
const Relocation& rel) noexcept {
|
||||
if (patch_mode == PatchMode::PreText) {
|
||||
return rel.patch_offset - static_cast<ptrdiff_t>(patch_size) -
|
||||
static_cast<ptrdiff_t>(rel.module_offset);
|
||||
}
|
||||
|
||||
return static_cast<ptrdiff_t>(remaining_program_size) -
|
||||
static_cast<ptrdiff_t>(rel.module_offset) + rel.patch_offset;
|
||||
}
|
||||
|
||||
ptrdiff_t Patcher::CalculateBranchToModuleOffset(PatchMode patch_mode, size_t patch_size,
|
||||
size_t remaining_program_size,
|
||||
const Relocation& rel) noexcept {
|
||||
if (patch_mode == PatchMode::PreText) {
|
||||
return static_cast<ptrdiff_t>(patch_size) - rel.patch_offset +
|
||||
static_cast<ptrdiff_t>(rel.module_offset);
|
||||
}
|
||||
|
||||
return static_cast<ptrdiff_t>(rel.module_offset) -
|
||||
static_cast<ptrdiff_t>(remaining_program_size) - rel.patch_offset;
|
||||
}
|
||||
|
||||
bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
|
||||
const Kernel::CodeSet::Segment& code) {
|
||||
// If we have patched modules but cannot reach the new module, then it needs its own patcher.
|
||||
@@ -53,6 +82,7 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
|
||||
// Add a new module patch to our list
|
||||
modules.emplace_back();
|
||||
curr_patch = &modules.back();
|
||||
curr_patch->image_size = image_size;
|
||||
|
||||
// The first word of the patch section is always a branch to the first instruction of the
|
||||
// module.
|
||||
@@ -120,6 +150,10 @@ bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
|
||||
// Determine patching mode for the final relocation step
|
||||
total_program_size += image_size;
|
||||
this->mode = image_size > MaxRelativeBranch ? PatchMode::PreText : PatchMode::PostData;
|
||||
if (mode == PatchMode::PreText) {
|
||||
ASSERT(modules.size() == 1);
|
||||
ASSERT(total_program_size == image_size);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -135,11 +169,6 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
|
||||
const auto text_words =
|
||||
std::span<u32>{reinterpret_cast<u32*>(text.data()), text.size() / sizeof(u32)};
|
||||
|
||||
const auto IsValidBranchOffset = [](ptrdiff_t offset) {
|
||||
return offset >= MinRelativeBranchOffset && offset <= MaxRelativeBranchOffset &&
|
||||
offset % sizeof(u32) == 0;
|
||||
};
|
||||
|
||||
const auto LogBranchRelocation = [&](const char* kind, ptrdiff_t branch_offset,
|
||||
const Relocation& rel) {
|
||||
if (IsValidBranchOffset(branch_offset)) {
|
||||
@@ -159,29 +188,23 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
|
||||
|
||||
const auto ApplyBranchToPatchRelocation = [&](u32* target, const Relocation& rel) {
|
||||
oaknut::CodeGenerator rc{target};
|
||||
ptrdiff_t branch_offset;
|
||||
if (mode == PatchMode::PreText) {
|
||||
branch_offset = rel.patch_offset - static_cast<ptrdiff_t>(patch_size) -
|
||||
static_cast<ptrdiff_t>(rel.module_offset);
|
||||
} else {
|
||||
branch_offset = static_cast<ptrdiff_t>(total_program_size) -
|
||||
static_cast<ptrdiff_t>(rel.module_offset) + rel.patch_offset;
|
||||
}
|
||||
const ptrdiff_t branch_offset =
|
||||
CalculateBranchToPatchOffset(mode, patch_size, total_program_size, rel);
|
||||
LogBranchRelocation("module_to_patch", branch_offset, rel);
|
||||
if (!IsValidBranchOffset(branch_offset)) {
|
||||
UNREACHABLE_MSG("NCE branch relocation must be validated before RelocateAndCopy");
|
||||
}
|
||||
rc.B(branch_offset);
|
||||
};
|
||||
|
||||
const auto ApplyBranchToModuleRelocation = [&](u32* target, const Relocation& rel) {
|
||||
oaknut::CodeGenerator rc{target};
|
||||
ptrdiff_t branch_offset;
|
||||
if (mode == PatchMode::PreText) {
|
||||
branch_offset = static_cast<ptrdiff_t>(patch_size) - rel.patch_offset +
|
||||
static_cast<ptrdiff_t>(rel.module_offset);
|
||||
} else {
|
||||
branch_offset = static_cast<ptrdiff_t>(rel.module_offset) -
|
||||
static_cast<ptrdiff_t>(total_program_size) - rel.patch_offset;
|
||||
}
|
||||
const ptrdiff_t branch_offset =
|
||||
CalculateBranchToModuleOffset(mode, patch_size, total_program_size, rel);
|
||||
LogBranchRelocation("patch_to_module", branch_offset, rel);
|
||||
if (!IsValidBranchOffset(branch_offset)) {
|
||||
UNREACHABLE_MSG("NCE branch relocation must be validated before RelocateAndCopy");
|
||||
}
|
||||
rc.B(branch_offset);
|
||||
};
|
||||
|
||||
@@ -202,7 +225,10 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
|
||||
};
|
||||
|
||||
// We are now ready to relocate!
|
||||
auto& patch = modules[m_relocate_module_index++];
|
||||
ASSERT(m_relocate_module_index < modules.size());
|
||||
const size_t module_index = m_relocate_module_index++;
|
||||
auto& patch = modules[module_index];
|
||||
const size_t module_image_size = patch.image_size;
|
||||
for (const Relocation& rel : patch.m_branch_to_patch_relocations) {
|
||||
ApplyBranchToPatchRelocation(text_words.data() + rel.module_offset / sizeof(u32), rel);
|
||||
}
|
||||
@@ -229,20 +255,24 @@ bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
|
||||
|
||||
// Remove the patched module size from the total. This is done so total_program_size
|
||||
// always represents the distance from the currently patched module to the patch section.
|
||||
total_program_size -= image_size;
|
||||
total_program_size -= module_image_size;
|
||||
|
||||
// Only copy to the program image of the last module
|
||||
if (m_relocate_module_index == modules.size()) {
|
||||
if (this->mode == PatchMode::PreText) {
|
||||
if (image_size != total_program_size) {
|
||||
const size_t expected_image_size = module_image_size + patch_size;
|
||||
if (image_size != expected_image_size || total_program_size != 0) {
|
||||
LOG_CRITICAL(Core_ARM,
|
||||
"NCE PreText final copy size mismatch: image_size={:#x}, "
|
||||
"NCE PreText final copy state mismatch: image_size={:#x}, "
|
||||
"expected_image_size={:#x}, module_image_size={:#x}, "
|
||||
"total_program_size={:#x}, patch_size={:#x}, relocate_module={}, "
|
||||
"modules={}, load_base={:#x}",
|
||||
image_size, total_program_size, patch_size, m_relocate_module_index,
|
||||
image_size, expected_image_size, module_image_size,
|
||||
total_program_size, patch_size, m_relocate_module_index,
|
||||
modules.size(), GetInteger(load_base));
|
||||
}
|
||||
ASSERT(image_size == total_program_size);
|
||||
ASSERT(image_size == expected_image_size);
|
||||
ASSERT(total_program_size == 0);
|
||||
std::memcpy(program_image.data(), m_patch_instructions.data(),
|
||||
m_patch_instructions.size() * sizeof(u32));
|
||||
} else {
|
||||
@@ -260,6 +290,80 @@ size_t Patcher::GetSectionSize() const noexcept {
|
||||
return Common::AlignUp(m_patch_instructions.size() * sizeof(u32), Core::Memory::CITRON_PAGESIZE);
|
||||
}
|
||||
|
||||
bool Patcher::CanRelocateBranches() const noexcept {
|
||||
if (modules.empty() || mode == PatchMode::None) {
|
||||
LOG_CRITICAL(Core_ARM,
|
||||
"NCE branch relocation preflight called without patched modules: mode={}, "
|
||||
"modules={}, total_program_size={:#x}",
|
||||
mode, modules.size(), total_program_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t patch_size = GetSectionSize();
|
||||
|
||||
const auto LogInvalidBranch = [&](const char* kind, size_t module_index,
|
||||
size_t remaining_program_size,
|
||||
const Relocation& rel, ptrdiff_t branch_offset) {
|
||||
LOG_CRITICAL(Core_ARM,
|
||||
"NCE branch relocation preflight failed: kind={}, mode={}, "
|
||||
"relocate_module={}, modules={}, branch_offset={:#x}, valid_range=[{:#x}, "
|
||||
"{:#x}], patch_offset={:#x}, module_offset={:#x}, patch_size={:#x}, "
|
||||
"image_size={:#x}, total_program_size={:#x}",
|
||||
kind, mode, module_index, modules.size(), branch_offset,
|
||||
MinRelativeBranchOffset, MaxRelativeBranchOffset, rel.patch_offset,
|
||||
rel.module_offset, patch_size, modules[module_index].image_size,
|
||||
remaining_program_size);
|
||||
};
|
||||
|
||||
size_t remaining_program_size = total_program_size;
|
||||
for (size_t module_index = 0; module_index < modules.size(); module_index++) {
|
||||
const auto& patch = modules[module_index];
|
||||
if (patch.image_size > remaining_program_size) {
|
||||
LOG_CRITICAL(Core_ARM,
|
||||
"NCE branch relocation preflight size state mismatch: mode={}, "
|
||||
"relocate_module={}, modules={}, image_size={:#x}, "
|
||||
"remaining_program_size={:#x}, total_program_size={:#x}",
|
||||
mode, module_index, modules.size(), patch.image_size,
|
||||
remaining_program_size, total_program_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const Relocation& rel : patch.m_branch_to_patch_relocations) {
|
||||
const ptrdiff_t branch_offset =
|
||||
CalculateBranchToPatchOffset(mode, patch_size, remaining_program_size, rel);
|
||||
|
||||
if (!IsValidBranchOffset(branch_offset)) {
|
||||
LogInvalidBranch("module_to_patch", module_index, remaining_program_size, rel,
|
||||
branch_offset);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const Relocation& rel : patch.m_branch_to_module_relocations) {
|
||||
const ptrdiff_t branch_offset =
|
||||
CalculateBranchToModuleOffset(mode, patch_size, remaining_program_size, rel);
|
||||
|
||||
if (!IsValidBranchOffset(branch_offset)) {
|
||||
LogInvalidBranch("patch_to_module", module_index, remaining_program_size, rel,
|
||||
branch_offset);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
remaining_program_size -= patch.image_size;
|
||||
}
|
||||
|
||||
if (remaining_program_size != 0) {
|
||||
LOG_CRITICAL(Core_ARM,
|
||||
"NCE branch relocation preflight size state mismatch after scan: mode={}, "
|
||||
"modules={}, remaining_program_size={:#x}, total_program_size={:#x}",
|
||||
mode, modules.size(), remaining_program_size, total_program_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Patcher::WriteLoadContext() {
|
||||
// This function was called, which modifies X30, so use that as a scratch register.
|
||||
// SP contains the guest X30, so save our return X30 to SP + 8, since we have allocated 16 bytes
|
||||
|
||||
@@ -36,6 +36,7 @@ public:
|
||||
bool RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code,
|
||||
Kernel::PhysicalMemory& program_image, EntryTrampolines* out_trampolines);
|
||||
size_t GetSectionSize() const noexcept;
|
||||
bool CanRelocateBranches() const noexcept;
|
||||
|
||||
[[nodiscard]] PatchMode GetPatchMode() const noexcept {
|
||||
return mode;
|
||||
@@ -84,7 +85,18 @@ private:
|
||||
uintptr_t module_offset; ///< Offset in bytes from the start of the text section.
|
||||
};
|
||||
|
||||
[[nodiscard]] static bool IsValidBranchOffset(ptrdiff_t offset) noexcept;
|
||||
[[nodiscard]] static ptrdiff_t CalculateBranchToPatchOffset(PatchMode patch_mode,
|
||||
size_t patch_size,
|
||||
size_t remaining_program_size,
|
||||
const Relocation& rel) noexcept;
|
||||
[[nodiscard]] static ptrdiff_t CalculateBranchToModuleOffset(PatchMode patch_mode,
|
||||
size_t patch_size,
|
||||
size_t remaining_program_size,
|
||||
const Relocation& rel) noexcept;
|
||||
|
||||
struct ModulePatch {
|
||||
size_t image_size{};
|
||||
std::vector<Trampoline> m_trampolines;
|
||||
std::vector<Relocation> m_branch_to_patch_relocations{};
|
||||
std::vector<Relocation> m_branch_to_module_relocations{};
|
||||
|
||||
@@ -128,22 +128,14 @@ void PhysicalCore::RunThread(Kernel::KThread* thread) {
|
||||
|
||||
// Notify the debugger and go to sleep on data abort.
|
||||
if (data_abort) {
|
||||
if (system.DebuggerEnabled()) {
|
||||
if (system.DebuggerEnabled() && interface->HaltedWatchpoint() != nullptr) {
|
||||
system.GetDebugger().NotifyThreadWatchpoint(thread, *interface->HaltedWatchpoint());
|
||||
} else {
|
||||
// Enhanced data abort handling for Nintendo SDK crashes
|
||||
LOG_WARNING(Core_ARM, "Data abort detected - checking if recoverable...");
|
||||
|
||||
// For Nintendo SDK crashes, try to continue execution
|
||||
// Many data aborts in Nintendo SDK are recoverable
|
||||
LOG_INFO(Core_ARM, "Attempting to continue execution after data abort");
|
||||
// Don't suspend the thread, let it continue
|
||||
LOG_CRITICAL(Core_ARM, "Data abort halted execution");
|
||||
}
|
||||
|
||||
if (system.DebuggerEnabled()) {
|
||||
thread->RequestSuspend(SuspendType::Debug);
|
||||
return;
|
||||
}
|
||||
thread->RequestSuspend(SuspendType::Debug);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle system calls.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include "common/logging.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
@@ -29,7 +30,12 @@ namespace Loader {
|
||||
|
||||
struct PatchCollection {
|
||||
explicit PatchCollection(bool is_application_) : is_application{is_application_} {
|
||||
Reset();
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
module_patcher_indices.fill(-1);
|
||||
patchers.clear();
|
||||
patchers.emplace_back();
|
||||
}
|
||||
|
||||
@@ -41,6 +47,10 @@ struct PatchCollection {
|
||||
}
|
||||
|
||||
size_t GetTotalPatchSize() const {
|
||||
if (!is_application || !Settings::IsNceEnabled()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t total_size{};
|
||||
#ifdef HAS_NCE
|
||||
for (auto& patcher : patchers) {
|
||||
@@ -62,6 +72,21 @@ struct PatchCollection {
|
||||
return static_cast<s32>(patchers.size()) - 1;
|
||||
}
|
||||
|
||||
bool CanRelocateBranches() const {
|
||||
#ifdef HAS_NCE
|
||||
if (!is_application || !Settings::IsNceEnabled()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const auto& patcher : patchers) {
|
||||
if (!patcher.CanRelocateBranches()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_application;
|
||||
std::vector<Core::NCE::Patcher> patchers;
|
||||
std::array<s32, 13> module_patcher_indices{};
|
||||
@@ -185,30 +210,57 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
|
||||
"subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7",
|
||||
"subsdk8", "subsdk9", "sdk"};
|
||||
|
||||
std::size_t code_size{};
|
||||
|
||||
// Define an nce patch context for each potential module.
|
||||
PatchCollection patch_ctx{is_application};
|
||||
|
||||
// Use the NSO module loader to figure out the code layout
|
||||
for (size_t i = 0; i < static_modules.size(); i++) {
|
||||
const auto& module = static_modules[i];
|
||||
const FileSys::VirtualFile module_file{dir->GetFile(module)};
|
||||
if (!module_file) {
|
||||
continue;
|
||||
const auto CalculateCodeLayout = [&]() -> std::optional<std::size_t> {
|
||||
std::size_t next_code_size{};
|
||||
patch_ctx.Reset();
|
||||
|
||||
// Use the NSO module loader to figure out the code layout.
|
||||
for (size_t i = 0; i < static_modules.size(); i++) {
|
||||
const auto& module = static_modules[i];
|
||||
const FileSys::VirtualFile module_file{dir->GetFile(module)};
|
||||
if (!module_file) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
|
||||
const auto tentative_next_load_addr = AppLoader_NSO::LoadModule(
|
||||
process, system, *module_file, next_code_size, should_pass_arguments, false, {},
|
||||
patch_ctx.GetPatchers(), patch_ctx.GetLastIndex());
|
||||
if (!tentative_next_load_addr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
patch_ctx.SaveIndex(i);
|
||||
next_code_size = *tentative_next_load_addr;
|
||||
}
|
||||
|
||||
const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
|
||||
const auto tentative_next_load_addr = AppLoader_NSO::LoadModule(
|
||||
process, system, *module_file, code_size, should_pass_arguments, false, {},
|
||||
patch_ctx.GetPatchers(), patch_ctx.GetLastIndex());
|
||||
if (!tentative_next_load_addr) {
|
||||
return next_code_size;
|
||||
};
|
||||
|
||||
auto calculated_code_size = CalculateCodeLayout();
|
||||
if (!calculated_code_size) {
|
||||
return {ResultStatus::ErrorLoadingNSO, {}};
|
||||
}
|
||||
|
||||
#ifdef HAS_NCE
|
||||
if (!patch_ctx.CanRelocateBranches()) {
|
||||
LOG_WARNING(Loader,
|
||||
"NCE patch layout contains an out-of-range branch relocation for "
|
||||
"program_id={:016X}; falling back to Dynarmic for this boot",
|
||||
metadata.GetTitleID());
|
||||
Settings::DisableNceForCurrentProcess();
|
||||
|
||||
calculated_code_size = CalculateCodeLayout();
|
||||
if (!calculated_code_size) {
|
||||
return {ResultStatus::ErrorLoadingNSO, {}};
|
||||
}
|
||||
|
||||
patch_ctx.SaveIndex(i);
|
||||
code_size = *tentative_next_load_addr;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::size_t code_size = *calculated_code_size;
|
||||
|
||||
// Enable direct memory mapping in case of NCE.
|
||||
const u64 fastmem_base = [&]() -> size_t {
|
||||
|
||||
Reference in New Issue
Block a user