mirror of
https://github.com/citron-neo/emulator.git
synced 2026-07-05 15:21:57 -07:00
Merge pull request #248 from Splaser/main
fix: Handle unreachable NCE branch relocations with safe Dynarmic fallback
This commit is contained in:
+50
@@ -0,0 +1,50 @@
|
||||
// SPDX-FileCopyrightText: 2026 Citron Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.citron.citron_emu.features.settings.model.view
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import org.citron.citron_emu.features.settings.model.AbstractStringSetting
|
||||
|
||||
class LogFilterSetting(
|
||||
private val stringSetting: AbstractStringSetting,
|
||||
@StringRes titleId: Int = 0,
|
||||
titleString: String = "",
|
||||
@StringRes descriptionId: Int = 0,
|
||||
descriptionString: String = "",
|
||||
private val customChoice: String
|
||||
) : SettingsItem(stringSetting, titleId, titleString, descriptionId, descriptionString) {
|
||||
override val type = TYPE_LOG_FILTER
|
||||
|
||||
private val presetValues = arrayOf(
|
||||
"*:Warning",
|
||||
"*:Info",
|
||||
"*:Debug",
|
||||
"*:Trace",
|
||||
"*:Warning Service.Audio:Debug HW.GPU:Debug"
|
||||
)
|
||||
|
||||
val customIndex: Int
|
||||
get() = presetValues.size
|
||||
|
||||
val choices: Array<String>
|
||||
get() = presetValues + customChoice
|
||||
|
||||
val selectedValueIndex: Int
|
||||
get() {
|
||||
val index = presetValues.indexOf(getSelectedValue())
|
||||
return if (index >= 0) index else customIndex
|
||||
}
|
||||
|
||||
fun getValueAt(index: Int): String =
|
||||
if (index >= 0 && index < presetValues.size) presetValues[index] else ""
|
||||
|
||||
fun getSelectedValue(needsGlobal: Boolean = false) = stringSetting.getString(needsGlobal)
|
||||
|
||||
fun getDisplayValue(): String {
|
||||
val value = getSelectedValue()
|
||||
return value.ifEmpty { customChoice }
|
||||
}
|
||||
|
||||
fun setSelectedValue(value: String) = stringSetting.setString(value)
|
||||
}
|
||||
+1
@@ -94,6 +94,7 @@ abstract class SettingsItem(
|
||||
const val TYPE_INT_SINGLE_CHOICE = 9
|
||||
const val TYPE_INPUT_PROFILE = 10
|
||||
const val TYPE_STRING_INPUT = 11
|
||||
const val TYPE_LOG_FILTER = 12
|
||||
|
||||
const val FASTMEM_COMBINED = "fastmem_combined"
|
||||
|
||||
|
||||
+12
-1
@@ -53,7 +53,9 @@ class SettingsAdapter(
|
||||
SwitchSettingViewHolder(ListItemSettingSwitchBinding.inflate(inflater), this)
|
||||
}
|
||||
|
||||
SettingsItem.TYPE_SINGLE_CHOICE, SettingsItem.TYPE_STRING_SINGLE_CHOICE -> {
|
||||
SettingsItem.TYPE_SINGLE_CHOICE,
|
||||
SettingsItem.TYPE_STRING_SINGLE_CHOICE,
|
||||
SettingsItem.TYPE_LOG_FILTER -> {
|
||||
SingleChoiceViewHolder(ListItemSettingBinding.inflate(inflater), this)
|
||||
}
|
||||
|
||||
@@ -129,6 +131,15 @@ class SettingsAdapter(
|
||||
).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
|
||||
}
|
||||
|
||||
fun onLogFilterClick(item: LogFilterSetting, position: Int) {
|
||||
SettingsDialogFragment.newInstance(
|
||||
settingsViewModel,
|
||||
item,
|
||||
SettingsItem.TYPE_LOG_FILTER,
|
||||
position
|
||||
).show(fragment.childFragmentManager, SettingsDialogFragment.TAG)
|
||||
}
|
||||
|
||||
fun onIntSingleChoiceClick(item: IntSingleChoiceSetting, position: Int) {
|
||||
SettingsDialogFragment.newInstance(
|
||||
settingsViewModel,
|
||||
|
||||
+43
@@ -21,6 +21,7 @@ import org.citron.citron_emu.features.input.model.AnalogDirection
|
||||
import org.citron.citron_emu.features.settings.model.view.AnalogInputSetting
|
||||
import org.citron.citron_emu.features.settings.model.view.ButtonInputSetting
|
||||
import org.citron.citron_emu.features.settings.model.view.IntSingleChoiceSetting
|
||||
import org.citron.citron_emu.features.settings.model.view.LogFilterSetting
|
||||
import org.citron.citron_emu.features.settings.model.view.SettingsItem
|
||||
import org.citron.citron_emu.features.settings.model.view.SingleChoiceSetting
|
||||
import org.citron.citron_emu.features.settings.model.view.SliderSetting
|
||||
@@ -154,6 +155,26 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
||||
.create()
|
||||
}
|
||||
|
||||
SettingsItem.TYPE_LOG_FILTER -> {
|
||||
val item = settingsViewModel.clickedItem as LogFilterSetting
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(item.title)
|
||||
.setSingleChoiceItems(item.choices, item.selectedValueIndex, this)
|
||||
.create()
|
||||
}
|
||||
|
||||
TYPE_LOG_FILTER_CUSTOM -> {
|
||||
stringInputBinding = DialogEditTextBinding.inflate(layoutInflater)
|
||||
val item = settingsViewModel.clickedItem as LogFilterSetting
|
||||
stringInputBinding.editText.setText(item.getSelectedValue())
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(item.title)
|
||||
.setView(stringInputBinding.root)
|
||||
.setPositiveButton(android.R.string.ok, this)
|
||||
.setNegativeButton(android.R.string.cancel, defaultCancelListener)
|
||||
.create()
|
||||
}
|
||||
|
||||
SettingsItem.TYPE_INT_SINGLE_CHOICE -> {
|
||||
val item = settingsViewModel.clickedItem as IntSingleChoiceSetting
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
@@ -174,6 +195,7 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
||||
return when (type) {
|
||||
SettingsItem.TYPE_SLIDER -> sliderBinding.root
|
||||
SettingsItem.TYPE_STRING_INPUT -> stringInputBinding.root
|
||||
TYPE_LOG_FILTER_CUSTOM -> stringInputBinding.root
|
||||
else -> super.onCreateView(inflater, container, savedInstanceState)
|
||||
}
|
||||
}
|
||||
@@ -206,6 +228,26 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
||||
scSetting.setSelectedValue(value)
|
||||
}
|
||||
|
||||
is LogFilterSetting -> {
|
||||
val logFilterSetting = settingsViewModel.clickedItem as LogFilterSetting
|
||||
if (type == TYPE_LOG_FILTER_CUSTOM) {
|
||||
logFilterSetting.setSelectedValue(
|
||||
(stringInputBinding.editText.text ?: "").toString()
|
||||
)
|
||||
} else if (which == logFilterSetting.customIndex) {
|
||||
dismiss()
|
||||
newInstance(
|
||||
settingsViewModel,
|
||||
logFilterSetting,
|
||||
TYPE_LOG_FILTER_CUSTOM,
|
||||
position
|
||||
).show(parentFragmentManager, TAG)
|
||||
return
|
||||
} else {
|
||||
logFilterSetting.setSelectedValue(logFilterSetting.getValueAt(which))
|
||||
}
|
||||
}
|
||||
|
||||
is IntSingleChoiceSetting -> {
|
||||
val scSetting = settingsViewModel.clickedItem as IntSingleChoiceSetting
|
||||
val value = scSetting.getValueAt(which)
|
||||
@@ -265,6 +307,7 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener
|
||||
const val TAG = "SettingsDialogFragment"
|
||||
|
||||
const val TYPE_RESET_SETTING = -1
|
||||
const val TYPE_LOG_FILTER_CUSTOM = -2
|
||||
|
||||
const val TITLE = "Title"
|
||||
const val TYPE = "Type"
|
||||
|
||||
+3
-2
@@ -1003,10 +1003,11 @@ class SettingsFragmentPresenter(
|
||||
|
||||
add(HeaderSetting(R.string.logging))
|
||||
add(
|
||||
StringInputSetting(
|
||||
LogFilterSetting(
|
||||
StringSetting.LOG_FILTER,
|
||||
titleId = R.string.log_filter,
|
||||
descriptionId = R.string.log_filter_description
|
||||
descriptionId = R.string.log_filter_description,
|
||||
customChoice = context.getString(R.string.log_filter_custom)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
+9
@@ -6,6 +6,7 @@ package org.citron.citron_emu.features.settings.ui.viewholder
|
||||
import android.view.View
|
||||
import org.citron.citron_emu.databinding.ListItemSettingBinding
|
||||
import org.citron.citron_emu.features.settings.model.view.IntSingleChoiceSetting
|
||||
import org.citron.citron_emu.features.settings.model.view.LogFilterSetting
|
||||
import org.citron.citron_emu.features.settings.model.view.SettingsItem
|
||||
import org.citron.citron_emu.features.settings.model.view.SingleChoiceSetting
|
||||
import org.citron.citron_emu.features.settings.model.view.StringSingleChoiceSetting
|
||||
@@ -39,6 +40,10 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
|
||||
binding.textSettingValue.text = item.getSelectedValue()
|
||||
}
|
||||
|
||||
is LogFilterSetting -> {
|
||||
binding.textSettingValue.text = item.getDisplayValue()
|
||||
}
|
||||
|
||||
is IntSingleChoiceSetting -> {
|
||||
binding.textSettingValue.text = item.getChoiceAt(item.getSelectedValue())
|
||||
}
|
||||
@@ -73,6 +78,10 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
|
||||
)
|
||||
}
|
||||
|
||||
is LogFilterSetting -> {
|
||||
adapter.onLogFilterClick(setting as LogFilterSetting, bindingAdapterPosition)
|
||||
}
|
||||
|
||||
is IntSingleChoiceSetting -> {
|
||||
adapter.onIntSingleChoiceClick(
|
||||
setting as IntSingleChoiceSetting,
|
||||
|
||||
@@ -208,12 +208,17 @@ jstring Java_org_citron_citron_1emu_utils_NativeConfig_getString(JNIEnv* env, jo
|
||||
|
||||
void Java_org_citron_citron_1emu_utils_NativeConfig_setString(JNIEnv* env, jobject obj, jstring jkey,
|
||||
jstring value) {
|
||||
const auto key = Common::Android::GetJString(env, jkey);
|
||||
auto setting = getSetting<std::string>(env, jkey);
|
||||
if (setting == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
setting->SetValue(Common::Android::GetJString(env, value));
|
||||
auto setting_value = Common::Android::GetJString(env, value);
|
||||
if (key == Settings::values.log_filter.GetLabel()) {
|
||||
setting_value = Common::Log::CanonicalizeFilterString(setting_value);
|
||||
}
|
||||
setting->SetValue(setting_value);
|
||||
}
|
||||
|
||||
jboolean Java_org_citron_citron_1emu_utils_NativeConfig_getIsRuntimeModifiable(JNIEnv* env, jobject obj,
|
||||
|
||||
@@ -272,7 +272,8 @@
|
||||
<string name="fastmem">Fastmem</string>
|
||||
<string name="logging">Logging</string>
|
||||
<string name="log_filter">Log Filter</string>
|
||||
<string name="log_filter_description">Configure which log messages to display. Format: <class>:<level>. Example: *:Info Service:Debug</string>
|
||||
<string name="log_filter_custom">Custom...</string>
|
||||
<string name="log_filter_description">Configure which log messages to display. Format: <class>:<level>. Example: *:Warning Service:Debug</string>
|
||||
|
||||
<!-- Audio settings strings -->
|
||||
<string name="audio_output_engine">Output engine</string>
|
||||
|
||||
+61
-6
@@ -4,6 +4,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <atomic>
|
||||
#include <cctype>
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
#include <cstdlib>
|
||||
@@ -95,11 +96,23 @@ std::string FormatLogMessage(const Entry& entry) noexcept {
|
||||
}
|
||||
} // namespace
|
||||
namespace {
|
||||
template <typename It>
|
||||
bool ComparePartialStringCaseInsensitive(It begin, It end, const char* other) noexcept {
|
||||
for (; begin != end && *other != '\0'; ++begin, ++other) {
|
||||
const auto lhs = static_cast<unsigned char>(*begin);
|
||||
const auto rhs = static_cast<unsigned char>(*other);
|
||||
if (std::tolower(lhs) != std::tolower(rhs)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return (begin == end) == (*other == '\0');
|
||||
}
|
||||
|
||||
template <typename It>
|
||||
Level GetLevelByName(const It begin, const It end) noexcept {
|
||||
for (u32 i = 0; i < u32(Level::Count); ++i)
|
||||
if (auto const name = GetLevelName(Level(i));
|
||||
Common::ComparePartialString(begin, end, name))
|
||||
ComparePartialStringCaseInsensitive(begin, end, name))
|
||||
return Level(i);
|
||||
return Level::Count;
|
||||
}
|
||||
@@ -107,7 +120,7 @@ template <typename It>
|
||||
Class GetClassByName(const It begin, const It end) noexcept {
|
||||
for (u32 i = 0; i < u32(Class::Count); ++i)
|
||||
if (auto const name = GetLogClassName(Class(i));
|
||||
Common::ComparePartialString(begin, end, name))
|
||||
ComparePartialStringCaseInsensitive(begin, end, name))
|
||||
return Class(i);
|
||||
return Class::Count;
|
||||
}
|
||||
@@ -115,13 +128,13 @@ template <typename Iterator>
|
||||
bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) noexcept {
|
||||
auto level_separator = std::find(begin, end, ':');
|
||||
if (level_separator == end) {
|
||||
LOG_ERROR(Log, "Invalid log filter. Must specify a log level after `:`: {}",
|
||||
std::string(begin, end));
|
||||
LOG_WARNING(Log, "Invalid log filter. Must specify a log level after `:`: {}",
|
||||
std::string(begin, end));
|
||||
return false;
|
||||
}
|
||||
const Level level = GetLevelByName(level_separator + 1, end);
|
||||
if (level == Level::Count) {
|
||||
LOG_ERROR(Log, "Unknown log level in filter: {}", std::string(begin, end));
|
||||
LOG_WARNING(Log, "Unknown log level in filter: {}", std::string(begin, end));
|
||||
return false;
|
||||
}
|
||||
if (Common::ComparePartialString(begin, level_separator, "*")) {
|
||||
@@ -130,7 +143,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) noexcept {
|
||||
}
|
||||
const Class log_class = GetClassByName(begin, level_separator);
|
||||
if (log_class == Class::Count) {
|
||||
LOG_ERROR(Log, "Unknown log class in filter: {}", std::string(begin, end));
|
||||
LOG_WARNING(Log, "Unknown log class in filter: {}", std::string(begin, end));
|
||||
return false;
|
||||
}
|
||||
instance.SetClassLevel(log_class, level);
|
||||
@@ -159,6 +172,48 @@ void Filter::ParseFilterString(std::string_view filter_view) noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
std::string CanonicalizeFilterString(std::string_view filter_view) {
|
||||
std::string canonicalized;
|
||||
auto it = filter_view.cbegin();
|
||||
while (it != filter_view.cend()) {
|
||||
auto end = std::find(it, filter_view.cend(), ' ');
|
||||
if (end != it) {
|
||||
if (!canonicalized.empty()) {
|
||||
canonicalized.push_back(' ');
|
||||
}
|
||||
|
||||
auto level_separator = std::find(it, end, ':');
|
||||
if (level_separator == end) {
|
||||
canonicalized.append(it, end);
|
||||
} else {
|
||||
if (ComparePartialStringCaseInsensitive(it, level_separator, "*")) {
|
||||
canonicalized.push_back('*');
|
||||
} else {
|
||||
const Class log_class = GetClassByName(it, level_separator);
|
||||
if (log_class == Class::Count) {
|
||||
canonicalized.append(it, level_separator);
|
||||
} else {
|
||||
canonicalized.append(GetLogClassName(log_class));
|
||||
}
|
||||
}
|
||||
|
||||
canonicalized.push_back(':');
|
||||
|
||||
const Level level = GetLevelByName(level_separator + 1, end);
|
||||
if (level == Level::Count) {
|
||||
canonicalized.append(level_separator + 1, end);
|
||||
} else {
|
||||
canonicalized.append(GetLevelName(level));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (end != filter_view.cend()) // Skip over the whitespace
|
||||
++end;
|
||||
it = end;
|
||||
}
|
||||
return canonicalized;
|
||||
}
|
||||
|
||||
namespace {
|
||||
/// @brief Trims up to and including the last of ../, ..\, src/, src\ in a string
|
||||
/// do not be fooled this isn't generating new strings on .rodata :)
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <fmt/ranges.h>
|
||||
#include "common/swap.h"
|
||||
@@ -67,6 +69,7 @@ struct Filter {
|
||||
}
|
||||
std::array<Level, std::size_t(Class::Count)> class_levels;
|
||||
};
|
||||
[[nodiscard]] std::string CanonicalizeFilterString(std::string_view filter_view);
|
||||
void Initialize() noexcept;
|
||||
void Start() noexcept;
|
||||
void Stop() noexcept;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -778,7 +778,7 @@ struct Values {
|
||||
Setting<bool> perform_vulkan_check{linkage, true, "perform_vulkan_check", Category::Debugging};
|
||||
|
||||
// Miscellaneous
|
||||
Setting<std::string> log_filter{linkage, "*:Info", "log_filter", Category::Debugging};
|
||||
Setting<std::string> log_filter{linkage, "*:Warning", "log_filter", Category::Debugging};
|
||||
Setting<bool> use_dev_keys{linkage, false, "use_dev_keys", Category::Miscellaneous};
|
||||
|
||||
// Network
|
||||
@@ -840,6 +840,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,57 @@ namespace {
|
||||
struct sigaction g_orig_bus_action;
|
||||
struct sigaction g_orig_segv_action;
|
||||
|
||||
template <typename Callback>
|
||||
void InvokeOriginalActionWithMask(int sig, const struct sigaction& original, Callback&& callback) {
|
||||
sigset_t callback_mask = original.sa_mask;
|
||||
sigset_t previous_mask;
|
||||
|
||||
if ((original.sa_flags & SA_NODEFER) == 0) {
|
||||
sigaddset(&callback_mask, sig);
|
||||
}
|
||||
|
||||
sigprocmask(SIG_BLOCK, &callback_mask, &previous_mask);
|
||||
|
||||
if ((original.sa_flags & SA_NODEFER) != 0) {
|
||||
sigset_t nodefer_mask;
|
||||
sigemptyset(&nodefer_mask);
|
||||
sigaddset(&nodefer_mask, sig);
|
||||
sigprocmask(SIG_UNBLOCK, &nodefer_mask, nullptr);
|
||||
}
|
||||
|
||||
callback();
|
||||
sigprocmask(SIG_SETMASK, &previous_mask, nullptr);
|
||||
}
|
||||
|
||||
void ForwardSignalToOriginalAction(int sig, siginfo_t* info, void* raw_context,
|
||||
const struct sigaction& original) {
|
||||
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_flags & SA_SIGINFO) != 0 && original.sa_sigaction != nullptr) {
|
||||
InvokeOriginalActionWithMask(sig, original,
|
||||
[&] { original.sa_sigaction(sig, info, raw_context); });
|
||||
return;
|
||||
}
|
||||
|
||||
if ((original.sa_flags & SA_SIGINFO) == 0 && original.sa_handler != nullptr) {
|
||||
InvokeOriginalActionWithMask(sig, original, [&] { 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 +85,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 +139,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 +171,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));
|
||||
@@ -116,13 +208,15 @@ bool ArmNce::HandleFailedGuestFault(GuestContext* guest_ctx, void* raw_info, voi
|
||||
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.
|
||||
// This preserves the historical NCE behavior while branch relocation fallback is tested.
|
||||
if (!is_prefetch_abort) {
|
||||
host_ctx.pc += 4;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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 +235,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 +268,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 +401,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;
|
||||
|
||||
+159
-11
@@ -4,6 +4,7 @@
|
||||
#include "common/arm64/native_clock.h"
|
||||
#include "common/bit_cast.h"
|
||||
#include "common/literals.h"
|
||||
#include "common/logging.h"
|
||||
#include "core/arm/nce/arm_nce.h"
|
||||
#include "core/arm/nce/guest_context.h"
|
||||
#include "core/arm/nce/instructions.h"
|
||||
@@ -20,6 +21,9 @@ using namespace oaknut::util;
|
||||
using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters;
|
||||
|
||||
constexpr size_t MaxRelativeBranch = 128_MiB;
|
||||
constexpr ptrdiff_t MinRelativeBranchOffset = -static_cast<ptrdiff_t>(MaxRelativeBranch);
|
||||
constexpr ptrdiff_t MaxRelativeBranchOffset =
|
||||
static_cast<ptrdiff_t>(MaxRelativeBranch) - sizeof(u32);
|
||||
constexpr u32 ModuleCodeIndex = 0x24 / sizeof(u32);
|
||||
|
||||
Patcher::Patcher() : c(m_patch_instructions) {
|
||||
@@ -38,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.
|
||||
@@ -49,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.
|
||||
@@ -116,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;
|
||||
}
|
||||
|
||||
@@ -131,22 +169,43 @@ 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 LogBranchRelocation = [&](const char* kind, ptrdiff_t branch_offset,
|
||||
const Relocation& rel) {
|
||||
if (IsValidBranchOffset(branch_offset)) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_CRITICAL(Core_ARM,
|
||||
"NCE branch relocation is outside AArch64 B range: 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}, load_base={:#x}",
|
||||
kind, mode, m_relocate_module_index, modules.size(), branch_offset,
|
||||
MinRelativeBranchOffset, MaxRelativeBranchOffset, rel.patch_offset,
|
||||
rel.module_offset, patch_size, image_size, total_program_size,
|
||||
GetInteger(load_base));
|
||||
};
|
||||
|
||||
const auto ApplyBranchToPatchRelocation = [&](u32* target, const Relocation& rel) {
|
||||
oaknut::CodeGenerator rc{target};
|
||||
if (mode == PatchMode::PreText) {
|
||||
rc.B(rel.patch_offset - patch_size - rel.module_offset);
|
||||
} else {
|
||||
rc.B(total_program_size - 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};
|
||||
if (mode == PatchMode::PreText) {
|
||||
rc.B(patch_size - rel.patch_offset + rel.module_offset);
|
||||
} else {
|
||||
rc.B(rel.module_offset - 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);
|
||||
};
|
||||
|
||||
const auto RebasePatch = [&](ptrdiff_t patch_offset) {
|
||||
@@ -166,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);
|
||||
}
|
||||
@@ -193,12 +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) {
|
||||
ASSERT(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 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, expected_image_size, module_image_size,
|
||||
total_program_size, patch_size, m_relocate_module_index,
|
||||
modules.size(), GetInteger(load_base));
|
||||
}
|
||||
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 {
|
||||
@@ -216,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{};
|
||||
|
||||
@@ -1118,6 +1118,10 @@ void System::Exit() {
|
||||
}
|
||||
|
||||
void System::ApplySettings() {
|
||||
Common::Log::Filter filter;
|
||||
filter.ParseFilterString(Settings::values.log_filter.GetValue());
|
||||
Common::Log::SetGlobalFilter(filter);
|
||||
|
||||
impl->RefreshTime(*this);
|
||||
|
||||
if (IsPoweredOn()) {
|
||||
|
||||
@@ -129,18 +129,11 @@ void PhysicalCore::RunThread(Kernel::KThread* thread) {
|
||||
// Notify the debugger and go to sleep on data abort.
|
||||
if (data_abort) {
|
||||
if (system.DebuggerEnabled()) {
|
||||
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
|
||||
}
|
||||
|
||||
if (system.DebuggerEnabled()) {
|
||||
if (const auto* watchpoint = interface->HaltedWatchpoint(); watchpoint != nullptr) {
|
||||
system.GetDebugger().NotifyThreadWatchpoint(thread, *watchpoint);
|
||||
} else {
|
||||
system.GetDebugger().NotifyThreadStopped(thread);
|
||||
}
|
||||
thread->RequestSuspend(SuspendType::Debug);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -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{};
|
||||
@@ -175,6 +200,9 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
|
||||
}
|
||||
metadata.Print();
|
||||
|
||||
const FileSys::PatchManager pm{metadata.GetTitleID(), system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
|
||||
// Enable NCE only for applications with 39-bit address space.
|
||||
const bool is_39bit =
|
||||
metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is39Bit;
|
||||
@@ -185,30 +213,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, pm,
|
||||
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 {
|
||||
@@ -232,8 +287,6 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
|
||||
modules.clear();
|
||||
const VAddr base_address{GetInteger(process.GetEntryPoint())};
|
||||
VAddr next_load_addr{base_address};
|
||||
const FileSys::PatchManager pm{metadata.GetTitleID(), system.GetFileSystemController(),
|
||||
system.GetContentProvider()};
|
||||
for (size_t i = 0; i < static_modules.size(); i++) {
|
||||
const auto& module = static_modules[i];
|
||||
const FileSys::VirtualFile module_file{dir->GetFile(module)};
|
||||
|
||||
+50
-2
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
@@ -148,7 +149,10 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
|
||||
|
||||
// Apply patches if necessary
|
||||
const auto name = nso_file.GetName();
|
||||
if (pm && (pm->HasNSOPatch(nso_header.build_id, name) || Settings::values.dump_nso)) {
|
||||
const bool has_nso_patch = pm && pm->HasNSOPatch(nso_header.build_id, name);
|
||||
const bool should_patch_nso =
|
||||
has_nso_patch || (load_into_process && Settings::values.dump_nso);
|
||||
if (pm && should_patch_nso) {
|
||||
std::span<u8> patchable_section(program_image.data() + module_start,
|
||||
program_image.size() - module_start);
|
||||
std::vector<u8> pi_header(sizeof(NSOHeader) + patchable_section.size());
|
||||
@@ -158,6 +162,21 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
|
||||
|
||||
pi_header = pm->PatchNSO(pi_header, name);
|
||||
|
||||
if (pi_header.size() < sizeof(NSOHeader)) {
|
||||
LOG_ERROR(Loader, "Patched NSO is too small: module={}, patched_size={:#x}", name,
|
||||
pi_header.size());
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto patched_size = pi_header.size() - sizeof(NSOHeader);
|
||||
if (patched_size != patchable_section.size()) {
|
||||
LOG_ERROR(Loader,
|
||||
"Patched NSO size mismatch: module={}, original_size={:#x}, "
|
||||
"patched_size={:#x}",
|
||||
name, patchable_section.size(), patched_size);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), patchable_section.data());
|
||||
}
|
||||
|
||||
@@ -172,7 +191,36 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
|
||||
}
|
||||
} else if (patch) {
|
||||
// Relocate code patch and copy to the program_image.
|
||||
if (patch->RelocateAndCopy(load_base, code, program_image, &process.GetPostHandlers())) {
|
||||
const auto build_id = Common::HexToString(nso_header.build_id);
|
||||
LOG_DEBUG(Loader,
|
||||
"NCE relocating NSO module: name={}, build_id={}, patch_index={}, mode={}, "
|
||||
"load_base={:#x}, image_size={:#x}, code_offset={:#x}, code_size={:#x}, "
|
||||
"patch_section_size={:#x}",
|
||||
name, build_id, patch_index, patch->GetPatchMode(), load_base,
|
||||
program_image.size(), code.offset, code.size, patch->GetSectionSize());
|
||||
|
||||
bool copied_patch_section;
|
||||
try {
|
||||
copied_patch_section =
|
||||
patch->RelocateAndCopy(load_base, code, program_image, &process.GetPostHandlers());
|
||||
} catch (const std::exception& ex) {
|
||||
LOG_CRITICAL(Loader,
|
||||
"NCE failed while relocating NSO module: name={}, build_id={}, "
|
||||
"patch_index={}, mode={}, load_base={:#x}, image_size={:#x}, "
|
||||
"code_offset={:#x}, code_size={:#x}, patch_section_size={:#x}, "
|
||||
"exception={}",
|
||||
name, build_id, patch_index, patch->GetPatchMode(), load_base,
|
||||
program_image.size(), code.offset, code.size, patch->GetSectionSize(),
|
||||
ex.what());
|
||||
throw;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Loader,
|
||||
"NCE relocated NSO module: name={}, build_id={}, patch_index={}, "
|
||||
"copied_patch_section={}, final_image_size={:#x}",
|
||||
name, build_id, patch_index, copied_patch_section, program_image.size());
|
||||
|
||||
if (copied_patch_section) {
|
||||
// Update patch section.
|
||||
auto& patch_segment = codeset.PatchSegment();
|
||||
patch_segment.addr =
|
||||
|
||||
+1
-1
@@ -1065,7 +1065,7 @@ bool Memory::InvalidateNCE(Common::ProcessAddress vaddr, size_t size) {
|
||||
u8* const ptr = impl->GetPointerImpl(
|
||||
GetInteger(vaddr),
|
||||
[&] {
|
||||
LOG_ERROR(HW_Memory, "Unmapped InvalidateNCE for {} bytes @ {:#x}", size,
|
||||
LOG_DEBUG(HW_Memory, "Unmapped InvalidateNCE for {} bytes @ {:#x}", size,
|
||||
GetInteger(vaddr));
|
||||
mapped = false;
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user