mirror of
https://github.com/azahar-emu/azahar.git
synced 2026-06-06 02:33:44 -04:00
Revamp GDB implemenation and add a some minor debug features (#2086)
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux-x86_64 (appimage) (push) Waiting to run
citra-build / linux-x86_64 (appimage-wayland) (push) Waiting to run
citra-build / linux-x86_64 (gcc-nopch) (push) Waiting to run
citra-build / linux-arm64 (clang) (push) Waiting to run
citra-build / linux-arm64 (gcc-nopch) (push) Waiting to run
citra-build / macos (push) Waiting to run
citra-build / windows (msvc) (push) Waiting to run
citra-build / windows (msys2) (push) Waiting to run
citra-build / android (googleplay) (push) Waiting to run
citra-build / android (vanilla) (push) Waiting to run
citra-build / docker (push) Waiting to run
citra-format / clang-format (push) Waiting to run
citra-libretro / android (push) Waiting to run
citra-libretro / linux (push) Waiting to run
citra-libretro / windows (push) Waiting to run
citra-libretro / macos (arm64) (push) Waiting to run
citra-libretro / macos (x86_64) (push) Waiting to run
citra-libretro / ios (push) Waiting to run
citra-libretro / tvos (push) Waiting to run
citra-transifex / transifex (push) Waiting to run
Some checks are pending
citra-build / source (push) Waiting to run
citra-build / linux-x86_64 (appimage) (push) Waiting to run
citra-build / linux-x86_64 (appimage-wayland) (push) Waiting to run
citra-build / linux-x86_64 (gcc-nopch) (push) Waiting to run
citra-build / linux-arm64 (clang) (push) Waiting to run
citra-build / linux-arm64 (gcc-nopch) (push) Waiting to run
citra-build / macos (push) Waiting to run
citra-build / windows (msvc) (push) Waiting to run
citra-build / windows (msys2) (push) Waiting to run
citra-build / android (googleplay) (push) Waiting to run
citra-build / android (vanilla) (push) Waiting to run
citra-build / docker (push) Waiting to run
citra-format / clang-format (push) Waiting to run
citra-libretro / android (push) Waiting to run
citra-libretro / linux (push) Waiting to run
citra-libretro / windows (push) Waiting to run
citra-libretro / macos (arm64) (push) Waiting to run
citra-libretro / macos (x86_64) (push) Waiting to run
citra-libretro / ios (push) Waiting to run
citra-libretro / tvos (push) Waiting to run
citra-transifex / transifex (push) Waiting to run
This commit is contained in:
parent
5ddbaeae23
commit
b540725090
43 changed files with 1671 additions and 466 deletions
|
|
@ -99,7 +99,7 @@ endif()
|
||||||
|
|
||||||
# Track which options were explicitly set by the user (for libretro conflict detection)
|
# Track which options were explicitly set by the user (for libretro conflict detection)
|
||||||
set(_LIBRETRO_INCOMPATIBLE_OPTIONS
|
set(_LIBRETRO_INCOMPATIBLE_OPTIONS
|
||||||
ENABLE_SDL2 ENABLE_QT ENABLE_WEB_SERVICE ENABLE_SCRIPTING
|
ENABLE_SDL2 ENABLE_QT ENABLE_WEB_SERVICE ENABLE_SCRIPTING ENABLE_GDBSTUB
|
||||||
ENABLE_OPENAL ENABLE_ROOM ENABLE_ROOM_STANDALONE ENABLE_CUBEB ENABLE_LIBUSB)
|
ENABLE_OPENAL ENABLE_ROOM ENABLE_ROOM_STANDALONE ENABLE_CUBEB ENABLE_LIBUSB)
|
||||||
set(_USER_SET_OPTIONS "")
|
set(_USER_SET_OPTIONS "")
|
||||||
foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS)
|
foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS)
|
||||||
|
|
@ -122,6 +122,7 @@ CMAKE_DEPENDENT_OPTION(ENABLE_ROOM_STANDALONE "Enable generating a standalone de
|
||||||
|
|
||||||
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
|
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
|
||||||
option(ENABLE_SCRIPTING "Enable RPC server for scripting" ON)
|
option(ENABLE_SCRIPTING "Enable RPC server for scripting" ON)
|
||||||
|
option(ENABLE_GDBSTUB "Enable GDB stub for emulated applications" ON)
|
||||||
|
|
||||||
CMAKE_DEPENDENT_OPTION(ENABLE_CUBEB "Enables the cubeb audio backend" ON "NOT IOS" OFF)
|
CMAKE_DEPENDENT_OPTION(ENABLE_CUBEB "Enables the cubeb audio backend" ON "NOT IOS" OFF)
|
||||||
option(ENABLE_OPENAL "Enables the OpenAL audio backend" ON)
|
option(ENABLE_OPENAL "Enables the OpenAL audio backend" ON)
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@ foreach(KEY IN ITEMS
|
||||||
"log_filter"
|
"log_filter"
|
||||||
"log_regex_filter"
|
"log_regex_filter"
|
||||||
"toggle_unique_data_console_type"
|
"toggle_unique_data_console_type"
|
||||||
|
"break_on_unmapped_memory_access"
|
||||||
"use_integer_scaling"
|
"use_integer_scaling"
|
||||||
"layouts_to_cycle"
|
"layouts_to_cycle"
|
||||||
"camera_inner_flip"
|
"camera_inner_flip"
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ COPY . /var/azahar-src
|
||||||
RUN mkdir builddir && cd builddir && \
|
RUN mkdir builddir && cd builddir && \
|
||||||
cmake /var/azahar-src -G Ninja \
|
cmake /var/azahar-src -G Ninja \
|
||||||
-DENABLE_QT=OFF \
|
-DENABLE_QT=OFF \
|
||||||
|
-DENABLE_GDBSTUB=OFF \
|
||||||
-DENABLE_TESTS=OFF \
|
-DENABLE_TESTS=OFF \
|
||||||
-DENABLE_ROOM=ON \
|
-DENABLE_ROOM=ON \
|
||||||
-DENABLE_ROOM_STANDALONE=ON \
|
-DENABLE_ROOM_STANDALONE=ON \
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,8 @@ android {
|
||||||
"-DENABLE_QT=0", // Don't use QT
|
"-DENABLE_QT=0", // Don't use QT
|
||||||
"-DENABLE_SDL2=0", // Don't use SDL
|
"-DENABLE_SDL2=0", // Don't use SDL
|
||||||
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
|
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
|
||||||
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON" // Support Android 15 16KiB page sizes
|
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", // Support Android 15 16KiB page sizes
|
||||||
|
"-DENABLE_GDBSTUB=OFF", // Disable GDB stub
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import android.view.Surface
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
|
|
@ -317,6 +318,12 @@ object NativeLibrary {
|
||||||
canContinue = false
|
canContinue = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CoreError.ErrorCoreExceptionRaised -> {
|
||||||
|
title = emulationActivity.getString(R.string.fatal_error)
|
||||||
|
message = emulationActivity.getString(R.string.fatal_error_message)
|
||||||
|
canContinue = false
|
||||||
|
}
|
||||||
|
|
||||||
CoreError.ErrorUnknown -> {
|
CoreError.ErrorUnknown -> {
|
||||||
title = emulationActivity.getString(R.string.fatal_error)
|
title = emulationActivity.getString(R.string.fatal_error)
|
||||||
message = emulationActivity.getString(R.string.fatal_error_message)
|
message = emulationActivity.getString(R.string.fatal_error_message)
|
||||||
|
|
@ -439,7 +446,7 @@ object NativeLibrary {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resultCode == EmulationErrorDialogFragment.ShutdownRequested) {
|
if (resultCode == CoreError.ShutdownRequested.value) {
|
||||||
emulationActivity.finish()
|
emulationActivity.finish()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -458,23 +465,26 @@ object NativeLibrary {
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
emulationActivity = requireActivity() as EmulationActivity
|
emulationActivity = requireActivity() as EmulationActivity
|
||||||
|
|
||||||
var captionId = R.string.loader_error_invalid_format
|
|
||||||
val result = requireArguments().getInt(RESULT_CODE)
|
val result = requireArguments().getInt(RESULT_CODE)
|
||||||
if (result == ErrorLoader_ErrorEncrypted) {
|
var captionString = getString(R.string.loader_error_invalid_format)
|
||||||
captionId = R.string.loader_error_encrypted
|
if (result == CoreError.ErrorLoader_ErrorEncrypted.value) {
|
||||||
|
captionString = getString(R.string.loader_error_encrypted)
|
||||||
}
|
}
|
||||||
if (result == ErrorArticDisconnected) {
|
if (result == CoreError.ErrorArticDisconnected.value) {
|
||||||
captionId = R.string.artic_base
|
captionString = getString(R.string.artic_base)
|
||||||
}
|
}
|
||||||
|
|
||||||
val alert = MaterialAlertDialogBuilder(requireContext())
|
val alert = MaterialAlertDialogBuilder(requireContext())
|
||||||
.setTitle(captionId)
|
.setTitle(captionString)
|
||||||
.setMessage(
|
.setMessage(
|
||||||
Html.fromHtml(
|
Html.fromHtml(
|
||||||
if (result == ErrorArticDisconnected)
|
if (result == CoreError.ErrorArticDisconnected.value)
|
||||||
CitraApplication.appContext.resources.getString(R.string.artic_server_comm_error)
|
getString(R.string.artic_server_comm_error)
|
||||||
|
else if (result == CoreError.ErrorLoader_ErrorEncrypted.value)
|
||||||
|
getString(R.string.loader_error_encrypted_desc)
|
||||||
else
|
else
|
||||||
CitraApplication.appContext.resources.getString(R.string.redump_games),
|
getString(R.string.loader_error_generic,
|
||||||
|
getString(CoreError.fromInt(result).stringRes), result),
|
||||||
Html.FROM_HTML_MODE_LEGACY
|
Html.FROM_HTML_MODE_LEGACY
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -496,21 +506,6 @@ object NativeLibrary {
|
||||||
|
|
||||||
const val RESULT_CODE = "resultcode"
|
const val RESULT_CODE = "resultcode"
|
||||||
|
|
||||||
const val Success = 0
|
|
||||||
const val ErrorNotInitialized = 1
|
|
||||||
const val ErrorGetLoader = 2
|
|
||||||
const val ErrorSystemMode = 3
|
|
||||||
const val ErrorLoader = 4
|
|
||||||
const val ErrorLoader_ErrorEncrypted = 5
|
|
||||||
const val ErrorLoader_ErrorInvalidFormat = 6
|
|
||||||
const val ErrorLoader_ErrorGBATitle = 7
|
|
||||||
const val ErrorSystemFiles = 8
|
|
||||||
const val ErrorSavestate = 9
|
|
||||||
const val ErrorArticDisconnected = 10
|
|
||||||
const val ErrorN3DSApplication = 11
|
|
||||||
const val ShutdownRequested = 12
|
|
||||||
const val ErrorUnknown = 13
|
|
||||||
|
|
||||||
fun newInstance(resultCode: Int): EmulationErrorDialogFragment {
|
fun newInstance(resultCode: Int): EmulationErrorDialogFragment {
|
||||||
val args = Bundle()
|
val args = Bundle()
|
||||||
args.putInt(RESULT_CODE, resultCode)
|
args.putInt(RESULT_CODE, resultCode)
|
||||||
|
|
@ -857,12 +852,29 @@ object NativeLibrary {
|
||||||
FileUtil.deleteDocument(path)
|
FileUtil.deleteDocument(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class CoreError {
|
enum class CoreError(val value: Int, @StringRes val stringRes: Int) {
|
||||||
ErrorSystemFiles,
|
Success(0, R.string.core_error_success),
|
||||||
ErrorSavestate,
|
ErrorNotInitialized(1, R.string.core_error_not_initialized),
|
||||||
ErrorArticDisconnected,
|
ErrorGetLoader(2, R.string.core_error_get_loader),
|
||||||
ErrorN3DSApplication,
|
ErrorSystemMode(3, R.string.core_error_system_mode),
|
||||||
ErrorUnknown
|
ErrorLoader(4, R.string.core_error_loader),
|
||||||
|
ErrorLoader_ErrorEncrypted(5, R.string.core_error_loader_encrypted),
|
||||||
|
ErrorLoader_ErrorInvalidFormat(6, R.string.core_error_loader_invalid_format),
|
||||||
|
ErrorLoader_ErrorGBATitle(7, R.string.core_error_loader_gba_title),
|
||||||
|
ErrorSystemFiles(8, R.string.core_error_system_files),
|
||||||
|
ErrorSavestate(9, R.string.core_error_savestate),
|
||||||
|
ErrorArticDisconnected(10, R.string.core_error_artic_disconnected),
|
||||||
|
ErrorN3DSApplication(11, R.string.core_error_n3ds_application),
|
||||||
|
ErrorCoreExceptionRaised(12, R.string.core_error_core_exception_raised),
|
||||||
|
ErrorMemoryExceptionRaised(13, R.string.core_error_memory_exception_raised),
|
||||||
|
ShutdownRequested(14, R.string.core_error_shutdown_requested),
|
||||||
|
ErrorUnknown(15, R.string.core_error_unknown);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromInt(value: Int): CoreError {
|
||||||
|
return entries.find { it.value == value } ?: ErrorUnknown
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class InstallStatus {
|
enum class InstallStatus {
|
||||||
|
|
|
||||||
|
|
@ -317,6 +317,7 @@ void Config::ReadValues() {
|
||||||
ReadSetting("Debugging", Settings::values.instant_debug_log);
|
ReadSetting("Debugging", Settings::values.instant_debug_log);
|
||||||
ReadSetting("Debugging", Settings::values.enable_rpc_server);
|
ReadSetting("Debugging", Settings::values.enable_rpc_server);
|
||||||
ReadSetting("Debugging", Settings::values.toggle_unique_data_console_type);
|
ReadSetting("Debugging", Settings::values.toggle_unique_data_console_type);
|
||||||
|
ReadSetting("Debugging", Settings::values.break_on_unmapped_memory_access);
|
||||||
|
|
||||||
for (const auto& service_module : Service::service_module_map) {
|
for (const auto& service_module : Service::service_module_map) {
|
||||||
bool use_lle =
|
bool use_lle =
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,9 @@ constexpr std::array android_config_omitted_keys = {
|
||||||
Keys::audio_encoder_options,
|
Keys::audio_encoder_options,
|
||||||
Keys::audio_bitrate,
|
Keys::audio_bitrate,
|
||||||
Keys::last_artic_base_addr, // On Android, this value is stored as a "preference"
|
Keys::last_artic_base_addr, // On Android, this value is stored as a "preference"
|
||||||
|
Keys::break_on_unmapped_memory_access, // Does nothing as the error is ignored
|
||||||
|
Keys::use_gdbstub, // GDB functionality disabled by deafult on Android
|
||||||
|
Keys::gdbstub_port,
|
||||||
};
|
};
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
|
|
@ -531,10 +534,6 @@ static const char* android_config_default_file_content = (BOOST_HANA_STRING(R"(
|
||||||
# 0 (default): Off, 1: On
|
# 0 (default): Off, 1: On
|
||||||
)") DECLARE_KEY(renderer_debug) BOOST_HANA_STRING(R"(
|
)") DECLARE_KEY(renderer_debug) BOOST_HANA_STRING(R"(
|
||||||
|
|
||||||
# Port for listening to GDB connections.
|
|
||||||
)") DECLARE_KEY(use_gdbstub) BOOST_HANA_STRING(R"(
|
|
||||||
)") DECLARE_KEY(gdbstub_port) BOOST_HANA_STRING(R"(
|
|
||||||
|
|
||||||
# Flush log output on every message
|
# Flush log output on every message
|
||||||
# Immediately commits the debug log to file. Use this if Azahar crashes and the log output is being cut.
|
# Immediately commits the debug log to file. Use this if Azahar crashes and the log output is being cut.
|
||||||
)") DECLARE_KEY(instant_debug_log) BOOST_HANA_STRING(R"(
|
)") DECLARE_KEY(instant_debug_log) BOOST_HANA_STRING(R"(
|
||||||
|
|
|
||||||
|
|
@ -393,7 +393,6 @@
|
||||||
<string name="learn_more">Learn More</string>
|
<string name="learn_more">Learn More</string>
|
||||||
<string name="close">Close</string>
|
<string name="close">Close</string>
|
||||||
<string name="reset_to_default">Reset to Default</string>
|
<string name="reset_to_default">Reset to Default</string>
|
||||||
<string name="redump_games"><![CDATA[Please follow the guides to redump your <a href="https://web.archive.org/web/20240304210021/https://citra-emu.org/wiki/dumping-game-cartridges/">game cartridges</a> or <a href="https://web.archive.org/web/20240304210011/https://citra-emu.org/wiki/dumping-installed-titles/">installed titles</a>.]]></string>
|
|
||||||
<string name="option_default">Default</string>
|
<string name="option_default">Default</string>
|
||||||
<string name="none">None</string>
|
<string name="none">None</string>
|
||||||
<string name="auto">Auto</string>
|
<string name="auto">Auto</string>
|
||||||
|
|
@ -437,9 +436,27 @@
|
||||||
|
|
||||||
<!-- ROM loading errors -->
|
<!-- ROM loading errors -->
|
||||||
<string name="loader_error_encrypted">Your ROM is Encrypted</string>
|
<string name="loader_error_encrypted">Your ROM is Encrypted</string>
|
||||||
|
<string name="loader_error_encrypted_desc"><![CDATA[Azahar does not support encrypted ROMS. Read our <a href="https://azahar-emu.org/blog/game-loading-changes/">blog post</a> for more information.]]></string>
|
||||||
<string name="loader_error_invalid_format">Invalid ROM format</string>
|
<string name="loader_error_invalid_format">Invalid ROM format</string>
|
||||||
<string name="loader_error_file_not_found">ROM file does not exist</string>
|
<string name="loader_error_file_not_found">ROM file does not exist</string>
|
||||||
<string name="no_game_present">No bootable game present!</string>
|
<string name="no_game_present">No bootable game present!</string>
|
||||||
|
<string name="loader_error_generic">An error occurred while loading ROM: \"%s (%d)\"</string>
|
||||||
|
<string name="core_error_success">Success</string>
|
||||||
|
<string name="core_error_not_initialized">Not initialized</string>
|
||||||
|
<string name="core_error_get_loader">Loader for file not found, incompatible file type</string>
|
||||||
|
<string name="core_error_system_mode">Failed to parse file</string>
|
||||||
|
<string name="core_error_loader">Generic loader error</string>
|
||||||
|
<string name="core_error_loader_encrypted">Encrypted file</string>
|
||||||
|
<string name="core_error_loader_invalid_format">Corrupted file</string>
|
||||||
|
<string name="core_error_loader_gba_title">File is GBA title</string>
|
||||||
|
<string name="core_error_system_files">Missing system files</string>
|
||||||
|
<string name="core_error_savestate">Savestate failed</string>
|
||||||
|
<string name="core_error_artic_disconnected">Artic Base disconnected</string>
|
||||||
|
<string name="core_error_n3ds_application">File is New 3DS application</string>
|
||||||
|
<string name="core_error_core_exception_raised">Core exception raised</string>
|
||||||
|
<string name="core_error_memory_exception_raised">Memory exception raised</string>
|
||||||
|
<string name="core_error_shutdown_requested">Shutdown requested</string>
|
||||||
|
<string name="core_error_unknown">Unknown error</string>
|
||||||
|
|
||||||
<!-- Emulation Menu -->
|
<!-- Emulation Menu -->
|
||||||
<string name="emulation_menu_help">Press Back to access the menu.</string>
|
<string name="emulation_menu_help">Press Back to access the menu.</string>
|
||||||
|
|
|
||||||
|
|
@ -855,10 +855,11 @@ void GMainWindow::InitializeHotkeys() {
|
||||||
|
|
||||||
// QAction Hotkeys
|
// QAction Hotkeys
|
||||||
const auto link_action_shortcut = [&](QAction* action, const QString& action_name,
|
const auto link_action_shortcut = [&](QAction* action, const QString& action_name,
|
||||||
const bool primary_only = false) {
|
const bool primary_only = false,
|
||||||
|
const bool auto_repeat = false) {
|
||||||
static const QString main_window = QStringLiteral("Main Window");
|
static const QString main_window = QStringLiteral("Main Window");
|
||||||
action->setShortcut(hotkey_registry.GetKeySequence(main_window, action_name));
|
action->setShortcut(hotkey_registry.GetKeySequence(main_window, action_name));
|
||||||
action->setAutoRepeat(false);
|
action->setAutoRepeat(auto_repeat);
|
||||||
this->addAction(action);
|
this->addAction(action);
|
||||||
if (!primary_only)
|
if (!primary_only)
|
||||||
secondary_window->addAction(action);
|
secondary_window->addAction(action);
|
||||||
|
|
@ -875,6 +876,9 @@ void GMainWindow::InitializeHotkeys() {
|
||||||
link_action_shortcut(ui->action_Show_Status_Bar, QStringLiteral("Toggle Status Bar"));
|
link_action_shortcut(ui->action_Show_Status_Bar, QStringLiteral("Toggle Status Bar"));
|
||||||
link_action_shortcut(ui->action_Fullscreen, fullscreen, true);
|
link_action_shortcut(ui->action_Fullscreen, fullscreen, true);
|
||||||
link_action_shortcut(ui->action_Capture_Screenshot, QStringLiteral("Capture Screenshot"));
|
link_action_shortcut(ui->action_Capture_Screenshot, QStringLiteral("Capture Screenshot"));
|
||||||
|
link_action_shortcut(ui->action_Debug_Pause, QStringLiteral("Debug Pause"));
|
||||||
|
link_action_shortcut(ui->action_Debug_Resume, QStringLiteral("Debug Resume"));
|
||||||
|
link_action_shortcut(ui->action_Debug_Step, QStringLiteral("Debug Step"), false, true);
|
||||||
link_action_shortcut(ui->action_Screen_Layout_Swap_Screens, QStringLiteral("Swap Screens"));
|
link_action_shortcut(ui->action_Screen_Layout_Swap_Screens, QStringLiteral("Swap Screens"));
|
||||||
link_action_shortcut(ui->action_Screen_Layout_Upright_Screens,
|
link_action_shortcut(ui->action_Screen_Layout_Upright_Screens,
|
||||||
QStringLiteral("Rotate Screens Upright"));
|
QStringLiteral("Rotate Screens Upright"));
|
||||||
|
|
@ -1189,6 +1193,23 @@ void GMainWindow::ConnectMenuEvents() {
|
||||||
connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot);
|
connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot);
|
||||||
connect_menu(ui->action_Dump_Video, &GMainWindow::OnDumpVideo);
|
connect_menu(ui->action_Dump_Video, &GMainWindow::OnDumpVideo);
|
||||||
|
|
||||||
|
// Tools debug
|
||||||
|
connect_menu(ui->action_Debug_Pause, [this] {
|
||||||
|
if (emu_thread) {
|
||||||
|
emu_thread->SetRunning(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect_menu(ui->action_Debug_Resume, [this] {
|
||||||
|
if (emu_thread) {
|
||||||
|
emu_thread->SetRunning(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect_menu(ui->action_Debug_Step, [this] {
|
||||||
|
if (emu_thread) {
|
||||||
|
emu_thread->ExecStep();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Tools
|
// Tools
|
||||||
connect_menu(ui->action_Compress_ROM_File, &GMainWindow::OnCompressFile);
|
connect_menu(ui->action_Compress_ROM_File, &GMainWindow::OnCompressFile);
|
||||||
connect_menu(ui->action_Decompress_ROM_File, &GMainWindow::OnDecompressFile);
|
connect_menu(ui->action_Decompress_ROM_File, &GMainWindow::OnDecompressFile);
|
||||||
|
|
@ -3880,6 +3901,18 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
|
||||||
.c_str());
|
.c_str());
|
||||||
error_severity_icon = QMessageBox::Icon::Critical;
|
error_severity_icon = QMessageBox::Icon::Critical;
|
||||||
can_continue = false;
|
can_continue = false;
|
||||||
|
} else if (result == Core::System::ResultStatus::ErrorCoreExceptionRaised) {
|
||||||
|
title = tr("An exception occurred");
|
||||||
|
message = tr("An exception occurred while executing the emulated application.\n\n");
|
||||||
|
message += QString::fromStdString(details);
|
||||||
|
error_severity_icon = QMessageBox::Icon::Critical;
|
||||||
|
can_continue = false;
|
||||||
|
} else if (result == Core::System::ResultStatus::ErrorMemoryExceptionRaised) {
|
||||||
|
title = tr("An invalid memory access occurred");
|
||||||
|
message =
|
||||||
|
tr("An invalid memory access occurred while executing the emulated application.\n\n");
|
||||||
|
message += QString::fromStdString(details);
|
||||||
|
error_severity_icon = QMessageBox::Icon::Critical;
|
||||||
} else {
|
} else {
|
||||||
title = tr("Fatal Error");
|
title = tr("Fatal Error");
|
||||||
message = tr("A fatal error occurred. "
|
message = tr("A fatal error occurred. "
|
||||||
|
|
|
||||||
|
|
@ -57,12 +57,15 @@ const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> QtConfi
|
||||||
// This must be in alphabetical order according to action name as it must have the same order as
|
// This must be in alphabetical order according to action name as it must have the same order as
|
||||||
// UISetting::values.shortcuts, which is alphabetically ordered.
|
// UISetting::values.shortcuts, which is alphabetically ordered.
|
||||||
// clang-format off
|
// clang-format off
|
||||||
const std::array<UISettings::Shortcut, 38> QtConfig::default_hotkeys {{
|
const std::array<UISettings::Shortcut, 41> QtConfig::default_hotkeys {{
|
||||||
{QStringLiteral("Advance Frame"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}},
|
{QStringLiteral("Advance Frame"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}},
|
||||||
{QStringLiteral("Audio Mute/Unmute"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}},
|
{QStringLiteral("Audio Mute/Unmute"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}},
|
||||||
{QStringLiteral("Audio Volume Down"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WindowShortcut}},
|
{QStringLiteral("Audio Volume Down"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WindowShortcut}},
|
||||||
{QStringLiteral("Audio Volume Up"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WindowShortcut}},
|
{QStringLiteral("Audio Volume Up"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WindowShortcut}},
|
||||||
{QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}},
|
{QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}},
|
||||||
|
{QStringLiteral("Debug Pause"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F4"),Qt::WidgetWithChildrenShortcut}},
|
||||||
|
{QStringLiteral("Debug Resume"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F5"),Qt::WidgetWithChildrenShortcut}},
|
||||||
|
{QStringLiteral("Debug Step"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F6"),Qt::WidgetWithChildrenShortcut}},
|
||||||
{QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}},
|
{QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}},
|
||||||
{QStringLiteral("Decrease 3D Factor"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+-"), Qt::ApplicationShortcut}},
|
{QStringLiteral("Decrease 3D Factor"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+-"), Qt::ApplicationShortcut}},
|
||||||
{QStringLiteral("Decrease Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("-"), Qt::ApplicationShortcut}},
|
{QStringLiteral("Decrease Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("-"), Qt::ApplicationShortcut}},
|
||||||
|
|
@ -510,6 +513,7 @@ void QtConfig::ReadDebuggingValues() {
|
||||||
ReadBasicSetting(Settings::values.instant_debug_log);
|
ReadBasicSetting(Settings::values.instant_debug_log);
|
||||||
ReadBasicSetting(Settings::values.enable_rpc_server);
|
ReadBasicSetting(Settings::values.enable_rpc_server);
|
||||||
ReadBasicSetting(Settings::values.toggle_unique_data_console_type);
|
ReadBasicSetting(Settings::values.toggle_unique_data_console_type);
|
||||||
|
ReadBasicSetting(Settings::values.break_on_unmapped_memory_access);
|
||||||
|
|
||||||
qt_config->beginGroup(QStringLiteral("LLE"));
|
qt_config->beginGroup(QStringLiteral("LLE"));
|
||||||
for (const auto& service_module : Service::service_module_map) {
|
for (const auto& service_module : Service::service_module_map) {
|
||||||
|
|
@ -1096,6 +1100,7 @@ void QtConfig::SaveDebuggingValues() {
|
||||||
WriteBasicSetting(Settings::values.instant_debug_log);
|
WriteBasicSetting(Settings::values.instant_debug_log);
|
||||||
WriteBasicSetting(Settings::values.enable_rpc_server);
|
WriteBasicSetting(Settings::values.enable_rpc_server);
|
||||||
WriteBasicSetting(Settings::values.toggle_unique_data_console_type);
|
WriteBasicSetting(Settings::values.toggle_unique_data_console_type);
|
||||||
|
WriteBasicSetting(Settings::values.break_on_unmapped_memory_access);
|
||||||
|
|
||||||
qt_config->beginGroup(QStringLiteral("LLE"));
|
qt_config->beginGroup(QStringLiteral("LLE"));
|
||||||
for (const auto& service_module : Settings::values.lle_modules) {
|
for (const auto& service_module : Settings::values.lle_modules) {
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ public:
|
||||||
|
|
||||||
static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
|
static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
|
||||||
static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs;
|
static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs;
|
||||||
static const std::array<UISettings::Shortcut, 38> default_hotkeys;
|
static const std::array<UISettings::Shortcut, 41> default_hotkeys;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Initialize(const std::string& config_name);
|
void Initialize(const std::string& config_name);
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
#include "common/file_util.h"
|
#include "common/file_util.h"
|
||||||
#include "common/logging/backend.h"
|
#include "common/logging/backend.h"
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
|
#include "core/core.h"
|
||||||
#include "ui_configure_debug.h"
|
#include "ui_configure_debug.h"
|
||||||
#ifdef ENABLE_VULKAN
|
#ifdef ENABLE_VULKAN
|
||||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||||
|
|
@ -33,6 +34,17 @@ ConfigureDebug::ConfigureDebug(bool is_powered_on_, QWidget* parent)
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
SetConfiguration();
|
SetConfiguration();
|
||||||
|
|
||||||
|
connect(ui->toggle_gdbstub, &QCheckBox::clicked,
|
||||||
|
[this](bool checked) { ui->debug_next_process->setEnabled(checked); });
|
||||||
|
|
||||||
|
connect(ui->debug_next_process, &QCheckBox::clicked, [](bool checked) {
|
||||||
|
if (checked) {
|
||||||
|
Core::System::GetInstance().SetDebugNextProcessFlag();
|
||||||
|
} else {
|
||||||
|
Core::System::GetInstance().ClearDebugNextProcessFlag();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
connect(ui->open_log_button, &QPushButton::clicked, []() {
|
connect(ui->open_log_button, &QPushButton::clicked, []() {
|
||||||
QString path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LogDir));
|
QString path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LogDir));
|
||||||
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
||||||
|
|
@ -87,6 +99,10 @@ ConfigureDebug::ConfigureDebug(bool is_powered_on_, QWidget* parent)
|
||||||
ui->clock_speed_label->setVisible(Settings::IsConfiguringGlobal());
|
ui->clock_speed_label->setVisible(Settings::IsConfiguringGlobal());
|
||||||
ui->clock_speed_combo->setVisible(!Settings::IsConfiguringGlobal());
|
ui->clock_speed_combo->setVisible(!Settings::IsConfiguringGlobal());
|
||||||
|
|
||||||
|
#ifndef ENABLE_GDBSTUB
|
||||||
|
ui->gdb_groupbox->setVisible(false);
|
||||||
|
#endif
|
||||||
|
|
||||||
SetupPerGameUI();
|
SetupPerGameUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,6 +110,9 @@ ConfigureDebug::~ConfigureDebug() = default;
|
||||||
|
|
||||||
void ConfigureDebug::SetConfiguration() {
|
void ConfigureDebug::SetConfiguration() {
|
||||||
ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub.GetValue());
|
ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub.GetValue());
|
||||||
|
if (!ui->toggle_gdbstub->isChecked()) {
|
||||||
|
ui->debug_next_process->setEnabled(false);
|
||||||
|
}
|
||||||
ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub.GetValue());
|
ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub.GetValue());
|
||||||
ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port.GetValue());
|
ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port.GetValue());
|
||||||
ui->toggle_console->setEnabled(!is_powered_on);
|
ui->toggle_console->setEnabled(!is_powered_on);
|
||||||
|
|
@ -112,6 +131,8 @@ void ConfigureDebug::SetConfiguration() {
|
||||||
#endif // !ENABLE_SCRIPTING
|
#endif // !ENABLE_SCRIPTING
|
||||||
ui->toggle_unique_data_console_type->setChecked(
|
ui->toggle_unique_data_console_type->setChecked(
|
||||||
Settings::values.toggle_unique_data_console_type.GetValue());
|
Settings::values.toggle_unique_data_console_type.GetValue());
|
||||||
|
ui->break_on_unmapped_memory_access->setChecked(
|
||||||
|
Settings::values.break_on_unmapped_memory_access.GetValue());
|
||||||
|
|
||||||
ui->toggle_renderer_debug->setChecked(Settings::values.renderer_debug.GetValue());
|
ui->toggle_renderer_debug->setChecked(Settings::values.renderer_debug.GetValue());
|
||||||
ui->toggle_dump_command_buffers->setChecked(Settings::values.dump_command_buffers.GetValue());
|
ui->toggle_dump_command_buffers->setChecked(Settings::values.dump_command_buffers.GetValue());
|
||||||
|
|
@ -133,6 +154,10 @@ void ConfigureDebug::SetConfiguration() {
|
||||||
ui->clock_display_label->setText(
|
ui->clock_display_label->setText(
|
||||||
QStringLiteral("%1%").arg(Settings::values.cpu_clock_percentage.GetValue()));
|
QStringLiteral("%1%").arg(Settings::values.cpu_clock_percentage.GetValue()));
|
||||||
ui->instant_debug_log->setChecked(Settings::values.instant_debug_log.GetValue());
|
ui->instant_debug_log->setChecked(Settings::values.instant_debug_log.GetValue());
|
||||||
|
|
||||||
|
if (Core::System::GetInstance().GetDebugNextProcessFlag()) {
|
||||||
|
ui->debug_next_process->setChecked(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureDebug::ApplyConfiguration() {
|
void ConfigureDebug::ApplyConfiguration() {
|
||||||
|
|
@ -153,6 +178,8 @@ void ConfigureDebug::ApplyConfiguration() {
|
||||||
Settings::values.enable_rpc_server = ui->enable_rpc_server->isChecked();
|
Settings::values.enable_rpc_server = ui->enable_rpc_server->isChecked();
|
||||||
Settings::values.toggle_unique_data_console_type =
|
Settings::values.toggle_unique_data_console_type =
|
||||||
ui->toggle_unique_data_console_type->isChecked();
|
ui->toggle_unique_data_console_type->isChecked();
|
||||||
|
Settings::values.break_on_unmapped_memory_access =
|
||||||
|
ui->break_on_unmapped_memory_access->isChecked();
|
||||||
Settings::values.renderer_debug = ui->toggle_renderer_debug->isChecked();
|
Settings::values.renderer_debug = ui->toggle_renderer_debug->isChecked();
|
||||||
Settings::values.dump_command_buffers = ui->toggle_dump_command_buffers->isChecked();
|
Settings::values.dump_command_buffers = ui->toggle_dump_command_buffers->isChecked();
|
||||||
Settings::values.instant_debug_log = ui->instant_debug_log->isChecked();
|
Settings::values.instant_debug_log = ui->instant_debug_log->isChecked();
|
||||||
|
|
@ -174,10 +201,11 @@ void ConfigureDebug::SetupPerGameUI() {
|
||||||
ConfigurationShared::SetHighlight(ui->clock_speed_widget, index == 1);
|
ConfigurationShared::SetHighlight(ui->clock_speed_widget, index == 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
ui->groupBox->setVisible(false);
|
ui->gdb_groupbox->setVisible(false);
|
||||||
ui->groupBox_2->setVisible(false);
|
ui->groupBox_2->setVisible(false);
|
||||||
ui->enable_rpc_server->setVisible(false);
|
ui->enable_rpc_server->setVisible(false);
|
||||||
ui->toggle_unique_data_console_type->setVisible(false);
|
ui->toggle_unique_data_console_type->setVisible(false);
|
||||||
|
ui->break_on_unmapped_memory_access->setVisible(false);
|
||||||
ui->toggle_cpu_jit->setVisible(false);
|
ui->toggle_cpu_jit->setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
<item>
|
<item>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="groupBox">
|
<widget class="QGroupBox" name="gdb_groupbox">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>GDB</string>
|
<string>GDB</string>
|
||||||
</property>
|
</property>
|
||||||
|
|
@ -60,6 +60,13 @@
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="debug_next_process">
|
||||||
|
<property name="text">
|
||||||
|
<string>Pause next non-sysmodule process at start</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
@ -299,6 +306,16 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QCheckBox" name="break_on_unmapped_memory_access">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string><html><head/><body><p>Pauses emulation and shows an error message if an unmapped memory access is detected.</p></body></html></string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Break on unmapped memory access</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
|
||||||
|
|
@ -206,6 +206,16 @@
|
||||||
<addaction name="action_Advance_Frame"/>
|
<addaction name="action_Advance_Frame"/>
|
||||||
<addaction name="menu_Movie"/>
|
<addaction name="menu_Movie"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
|
<widget class="QMenu" name="menu_Debug">
|
||||||
|
<property name="title">
|
||||||
|
<string>Debug</string>
|
||||||
|
</property>
|
||||||
|
<addaction name="action_Debug_Pause"/>
|
||||||
|
<addaction name="action_Debug_Resume"/>
|
||||||
|
<addaction name="action_Debug_Step"/>
|
||||||
|
</widget>
|
||||||
|
<addaction name="menu_Debug"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
<addaction name="action_Capture_Screenshot"/>
|
<addaction name="action_Capture_Screenshot"/>
|
||||||
<addaction name="action_Dump_Video"/>
|
<addaction name="action_Dump_Video"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
|
|
@ -453,6 +463,30 @@
|
||||||
<string>Capture Screenshot</string>
|
<string>Capture Screenshot</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="action_Debug_Pause">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Debug Pause</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="action_Debug_Resume">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Debug Resume</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="action_Debug_Step">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Debug Step</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
<action name="action_Dump_Video">
|
<action name="action_Dump_Video">
|
||||||
<property name="checkable">
|
<property name="checkable">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ add_library(citra_common STATIC
|
||||||
microprofile.cpp
|
microprofile.cpp
|
||||||
microprofile.h
|
microprofile.h
|
||||||
microprofileui.h
|
microprofileui.h
|
||||||
|
optional_helper.h
|
||||||
param_package.cpp
|
param_package.cpp
|
||||||
param_package.h
|
param_package.h
|
||||||
play_time_manager.cpp
|
play_time_manager.cpp
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2020 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
|
@ -153,7 +153,9 @@ private:
|
||||||
void serialize(Archive& ar, const unsigned int) {
|
void serialize(Archive& ar, const unsigned int) {
|
||||||
ar & backing_mem;
|
ar & backing_mem;
|
||||||
ar & offset;
|
ar & offset;
|
||||||
|
if (Archive::is_loading::value) {
|
||||||
Init();
|
Init();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
friend class boost::serialization::access;
|
friend class boost::serialization::access;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
42
src/common/optional_helper.h
Normal file
42
src/common/optional_helper.h
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
template <typename T>
|
||||||
|
struct is_optional_trait : std::false_type {
|
||||||
|
using value_type = T;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct is_optional_trait<std::optional<T>> : std::true_type {
|
||||||
|
using value_type = T;
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if T is a std::optional, false otherwise.
|
||||||
|
* For example:
|
||||||
|
* using Test1 = u32;
|
||||||
|
* using Test2 = std::optional<u32>;
|
||||||
|
* is_optional_type<Test1> -> false
|
||||||
|
* is_optional_type<Test2> -> true
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
inline constexpr bool is_optional_type = detail::is_optional_trait<T>::value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the inner type of T if it is a std::optional, or T itself if it is not.
|
||||||
|
* For example:
|
||||||
|
* using Test1 = u32;
|
||||||
|
* using Test2 = std::optional<u32>;
|
||||||
|
* optional_value_type<Test1> -> u32
|
||||||
|
* optional_value_type<Test2> -> u32
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
using optional_inner_or_type = typename detail::is_optional_trait<T>::value_type;
|
||||||
|
|
@ -166,6 +166,8 @@ void LogSettings() {
|
||||||
log_setting("Debugging_InstantDebugLog", values.instant_debug_log.GetValue());
|
log_setting("Debugging_InstantDebugLog", values.instant_debug_log.GetValue());
|
||||||
log_setting("Debugging_ToggleUniqueDataConsoleType",
|
log_setting("Debugging_ToggleUniqueDataConsoleType",
|
||||||
values.toggle_unique_data_console_type.GetValue());
|
values.toggle_unique_data_console_type.GetValue());
|
||||||
|
log_setting("Debugging_BreakOnUnmappedMemoryAccess",
|
||||||
|
values.break_on_unmapped_memory_access.GetValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsConfiguringGlobal() {
|
bool IsConfiguringGlobal() {
|
||||||
|
|
|
||||||
|
|
@ -643,6 +643,7 @@ struct Values {
|
||||||
Setting<bool> instant_debug_log{false, Keys::instant_debug_log};
|
Setting<bool> instant_debug_log{false, Keys::instant_debug_log};
|
||||||
Setting<bool> enable_rpc_server{false, Keys::enable_rpc_server};
|
Setting<bool> enable_rpc_server{false, Keys::enable_rpc_server};
|
||||||
Setting<bool> toggle_unique_data_console_type{false, Keys::toggle_unique_data_console_type};
|
Setting<bool> toggle_unique_data_console_type{false, Keys::toggle_unique_data_console_type};
|
||||||
|
Setting<bool> break_on_unmapped_memory_access{false, Keys::break_on_unmapped_memory_access};
|
||||||
|
|
||||||
// Miscellaneous
|
// Miscellaneous
|
||||||
Setting<std::string> log_filter{"*:Info", Keys::log_filter};
|
Setting<std::string> log_filter{"*:Info", Keys::log_filter};
|
||||||
|
|
|
||||||
|
|
@ -122,8 +122,7 @@ void BuildCompleteFilename(std::string& _CompleteFilename, const std::string& _P
|
||||||
_CompleteFilename += _Filename;
|
_CompleteFilename += _Filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> SplitString(const std::string& str, const char delim) {
|
static std::vector<std::string> SplitString(std::istringstream& iss, const char delim) {
|
||||||
std::istringstream iss(str);
|
|
||||||
std::vector<std::string> output(1);
|
std::vector<std::string> output(1);
|
||||||
|
|
||||||
while (std::getline(iss, *output.rbegin(), delim)) {
|
while (std::getline(iss, *output.rbegin(), delim)) {
|
||||||
|
|
@ -134,6 +133,16 @@ std::vector<std::string> SplitString(const std::string& str, const char delim) {
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> SplitString(std::string_view str, const char delim) {
|
||||||
|
std::istringstream iss{std::string(str)};
|
||||||
|
return SplitString(iss, delim);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> SplitString(const std::string& str, const char delim) {
|
||||||
|
std::istringstream iss(str);
|
||||||
|
return SplitString(iss, delim);
|
||||||
|
}
|
||||||
|
|
||||||
std::string TabsToSpaces(int tab_size, std::string in) {
|
std::string TabsToSpaces(int tab_size, std::string in) {
|
||||||
std::size_t i = 0;
|
std::size_t i = 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ namespace Common {
|
||||||
|
|
||||||
[[nodiscard]] bool EndsWith(const std::string& value, const std::string& ending);
|
[[nodiscard]] bool EndsWith(const std::string& value, const std::string& ending);
|
||||||
|
|
||||||
|
[[nodiscard]] std::vector<std::string> SplitString(std::string_view str, const char delim);
|
||||||
[[nodiscard]] std::vector<std::string> SplitString(const std::string& str, const char delim);
|
[[nodiscard]] std::vector<std::string> SplitString(const std::string& str, const char delim);
|
||||||
|
|
||||||
// "C:/Windows/winhelp.exe" to "C:/Windows/", "winhelp", ".exe"
|
// "C:/Windows/winhelp.exe" to "C:/Windows/", "winhelp", ".exe"
|
||||||
|
|
|
||||||
|
|
@ -126,10 +126,6 @@ add_library(citra_core STATIC
|
||||||
frontend/image_interface.cpp
|
frontend/image_interface.cpp
|
||||||
frontend/image_interface.h
|
frontend/image_interface.h
|
||||||
frontend/input.h
|
frontend/input.h
|
||||||
gdbstub/gdbstub.cpp
|
|
||||||
gdbstub/gdbstub.h
|
|
||||||
gdbstub/hio.cpp
|
|
||||||
gdbstub/hio.h
|
|
||||||
hle/applets/applet.cpp
|
hle/applets/applet.cpp
|
||||||
hle/applets/applet.h
|
hle/applets/applet.h
|
||||||
hle/applets/erreula.cpp
|
hle/applets/erreula.cpp
|
||||||
|
|
@ -529,6 +525,16 @@ if (ENABLE_SCRIPTING)
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (ENABLE_GDBSTUB)
|
||||||
|
target_compile_definitions(citra_core PUBLIC -DENABLE_GDBSTUB)
|
||||||
|
target_sources(citra_core PRIVATE
|
||||||
|
gdbstub/gdbstub.cpp
|
||||||
|
gdbstub/gdbstub.h
|
||||||
|
gdbstub/hio.cpp
|
||||||
|
gdbstub/hio.h
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
if ("x86_64" IN_LIST ARCHITECTURE OR "arm64" IN_LIST ARCHITECTURE)
|
if ("x86_64" IN_LIST ARCHITECTURE OR "arm64" IN_LIST ARCHITECTURE)
|
||||||
target_sources(citra_core PRIVATE
|
target_sources(citra_core PRIVATE
|
||||||
arm/dynarmic/arm_dynarmic.cpp
|
arm/dynarmic/arm_dynarmic.cpp
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2014 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
|
@ -186,6 +186,13 @@ public:
|
||||||
/// Prepare core for thread reschedule (if needed to correctly handle state)
|
/// Prepare core for thread reschedule (if needed to correctly handle state)
|
||||||
virtual void PrepareReschedule() = 0;
|
virtual void PrepareReschedule() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the backend allows to break with single instruction accuracy
|
||||||
|
* when Run() is used. If false is returned, the user should expect
|
||||||
|
* innaccuracies with memory watchpoints access exceptions.
|
||||||
|
*/
|
||||||
|
virtual bool HasSingleInstructionBreakAccuracy() = 0;
|
||||||
|
|
||||||
Core::Timing::Timer& GetTimer() {
|
Core::Timing::Timer& GetTimer() {
|
||||||
return *timer;
|
return *timer;
|
||||||
}
|
}
|
||||||
|
|
@ -198,12 +205,28 @@ public:
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the core to not being runnable until its break condition is handled.
|
||||||
|
*/
|
||||||
|
void SetBreakFlag() {
|
||||||
|
break_flag = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sets the core being runnable after its break condition was handled.
|
||||||
|
*/
|
||||||
|
void ClearBreakFlag() {
|
||||||
|
break_flag = false;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// This us used for serialization. Returning nullptr is valid if page tables are not used.
|
// This us used for serialization. Returning nullptr is valid if page tables are not used.
|
||||||
virtual std::shared_ptr<Memory::PageTable> GetPageTable() const = 0;
|
virtual std::shared_ptr<Memory::PageTable> GetPageTable() const = 0;
|
||||||
|
|
||||||
std::shared_ptr<Core::Timing::Timer> timer;
|
std::shared_ptr<Core::Timing::Timer> timer;
|
||||||
|
|
||||||
|
bool break_flag{};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
u32 id;
|
u32 id;
|
||||||
|
|
||||||
|
|
@ -213,6 +236,7 @@ private:
|
||||||
void save(Archive& ar, const unsigned int file_version) const {
|
void save(Archive& ar, const unsigned int file_version) const {
|
||||||
ar << timer;
|
ar << timer;
|
||||||
ar << id;
|
ar << id;
|
||||||
|
ar << break_flag;
|
||||||
const auto page_table = GetPageTable();
|
const auto page_table = GetPageTable();
|
||||||
ar << page_table;
|
ar << page_table;
|
||||||
for (int i = 0; i < 15; i++) {
|
for (int i = 0; i < 15; i++) {
|
||||||
|
|
@ -258,6 +282,7 @@ private:
|
||||||
ClearInstructionCache();
|
ClearInstructionCache();
|
||||||
ar >> timer;
|
ar >> timer;
|
||||||
ar >> id;
|
ar >> id;
|
||||||
|
ar >> break_flag;
|
||||||
std::shared_ptr<Memory::PageTable> page_table{};
|
std::shared_ptr<Memory::PageTable> page_table{};
|
||||||
ar >> page_table;
|
ar >> page_table;
|
||||||
SetPageTable(page_table);
|
SetPageTable(page_table);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
// Copyright 2016 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <csignal>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <dynarmic/interface/A32/a32.h>
|
#include <dynarmic/interface/A32/a32.h>
|
||||||
#include <dynarmic/interface/optimization_flags.h>
|
#include <dynarmic/interface/optimization_flags.h>
|
||||||
|
|
@ -13,10 +14,20 @@
|
||||||
#include "core/arm/dynarmic/arm_tick_counts.h"
|
#include "core/arm/dynarmic/arm_tick_counts.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
#include "core/gdbstub/gdbstub.h"
|
#include "core/gdbstub/gdbstub.h"
|
||||||
|
#endif
|
||||||
#include "core/hle/kernel/svc.h"
|
#include "core/hle/kernel/svc.h"
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
|
|
||||||
|
#ifndef SIGILL
|
||||||
|
constexpr u32 SIGILL = 4;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SIGTRAP
|
||||||
|
constexpr u32 SIGTRAP = 5;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
class DynarmicUserCallbacks final : public Dynarmic::A32::UserCallbacks {
|
class DynarmicUserCallbacks final : public Dynarmic::A32::UserCallbacks {
|
||||||
|
|
@ -25,6 +36,10 @@ public:
|
||||||
: parent(parent), svc_context(parent.system), memory(parent.memory) {}
|
: parent(parent), svc_context(parent.system), memory(parent.memory) {}
|
||||||
~DynarmicUserCallbacks() = default;
|
~DynarmicUserCallbacks() = default;
|
||||||
|
|
||||||
|
std::optional<std::uint32_t> MemoryReadCode(VAddr vaddr) override {
|
||||||
|
return memory.Read32OrNullopt(vaddr);
|
||||||
|
}
|
||||||
|
|
||||||
std::uint8_t MemoryRead8(VAddr vaddr) override {
|
std::uint8_t MemoryRead8(VAddr vaddr) override {
|
||||||
return memory.Read8(vaddr);
|
return memory.Read8(vaddr);
|
||||||
}
|
}
|
||||||
|
|
@ -82,12 +97,13 @@ public:
|
||||||
case Dynarmic::A32::Exception::NoExecuteFault:
|
case Dynarmic::A32::Exception::NoExecuteFault:
|
||||||
break;
|
break;
|
||||||
case Dynarmic::A32::Exception::Breakpoint:
|
case Dynarmic::A32::Exception::Breakpoint:
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
if (GDBStub::IsConnected()) {
|
if (GDBStub::IsConnected()) {
|
||||||
parent.jit->HaltExecution();
|
|
||||||
parent.SetPC(pc);
|
parent.SetPC(pc);
|
||||||
parent.ServeBreak();
|
parent.ServeBreak(SIGTRAP);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
case Dynarmic::A32::Exception::SendEvent:
|
case Dynarmic::A32::Exception::SendEvent:
|
||||||
case Dynarmic::A32::Exception::SendEventLocal:
|
case Dynarmic::A32::Exception::SendEventLocal:
|
||||||
|
|
@ -99,11 +115,40 @@ public:
|
||||||
case Dynarmic::A32::Exception::PreloadInstruction:
|
case Dynarmic::A32::Exception::PreloadInstruction:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
LOG_CRITICAL(Debug, "r{:02d} = {:08X}", i, parent.GetReg(i));
|
static constexpr auto ExceptionToString = [](Dynarmic::A32::Exception e) -> std::string {
|
||||||
|
switch (e) {
|
||||||
|
case Dynarmic::A32::Exception::UndefinedInstruction:
|
||||||
|
return "UndefinedInstruction";
|
||||||
|
case Dynarmic::A32::Exception::UnpredictableInstruction:
|
||||||
|
return "UnpredictableInstruction";
|
||||||
|
case Dynarmic::A32::Exception::DecodeError:
|
||||||
|
return "DecodeError";
|
||||||
|
case Dynarmic::A32::Exception::NoExecuteFault:
|
||||||
|
return "NoExecuteFault";
|
||||||
|
case Dynarmic::A32::Exception::Breakpoint:
|
||||||
|
return "Breakpoint";
|
||||||
|
default:
|
||||||
|
return fmt::format("Unknown({})", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
parent.SetPC(pc);
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
|
if (GDBStub::IsConnected()) {
|
||||||
|
parent.ServeBreak(SIGILL);
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
std::string error;
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
error += fmt::format("r{:02d} = {:08X}\n", i, parent.GetReg(i));
|
||||||
|
}
|
||||||
|
error += fmt::format("ExceptionRaised(exception = {}, pc = {:08X})",
|
||||||
|
ExceptionToString(exception), pc);
|
||||||
|
parent.system.SetStatus(Core::System::ResultStatus::ErrorCoreExceptionRaised,
|
||||||
|
error.c_str());
|
||||||
}
|
}
|
||||||
ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})", exception,
|
|
||||||
pc, MemoryReadCode(pc).value());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddTicks(std::uint64_t ticks) override {
|
void AddTicks(std::uint64_t ticks) override {
|
||||||
|
|
@ -138,16 +183,19 @@ MICROPROFILE_DEFINE(ARM_Jit, "ARM JIT", "ARM JIT", MP_RGB(255, 64, 64));
|
||||||
void ARM_Dynarmic::Run() {
|
void ARM_Dynarmic::Run() {
|
||||||
ASSERT(memory.GetCurrentPageTable() == current_page_table);
|
ASSERT(memory.GetCurrentPageTable() == current_page_table);
|
||||||
MICROPROFILE_SCOPE(ARM_Jit);
|
MICROPROFILE_SCOPE(ARM_Jit);
|
||||||
|
if (break_flag) [[unlikely]] {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
jit->Run();
|
jit->Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ARM_Dynarmic::Step() {
|
void ARM_Dynarmic::Step() {
|
||||||
jit->Step();
|
if (break_flag) [[unlikely]] {
|
||||||
|
return;
|
||||||
if (GDBStub::IsConnected()) {
|
|
||||||
ServeBreak();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jit->Step();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ARM_Dynarmic::SetPC(u32 pc) {
|
void ARM_Dynarmic::SetPC(u32 pc) {
|
||||||
|
|
@ -294,11 +342,10 @@ void ARM_Dynarmic::SetPageTable(const std::shared_ptr<Memory::PageTable>& page_t
|
||||||
jits.emplace(current_page_table, std::move(new_jit));
|
jits.emplace(current_page_table, std::move(new_jit));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ARM_Dynarmic::ServeBreak() {
|
void ARM_Dynarmic::ServeBreak([[maybe_unused]] int signal) {
|
||||||
Kernel::Thread* thread = system.Kernel().GetCurrentThreadManager().GetCurrentThread();
|
#ifdef ENABLE_GDBSTUB
|
||||||
SaveContext(thread->context);
|
GDBStub::Break(signal);
|
||||||
GDBStub::Break();
|
#endif
|
||||||
GDBStub::SendTrap(thread, 5);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Dynarmic::A32::Jit> ARM_Dynarmic::MakeJit() {
|
std::unique_ptr<Dynarmic::A32::Jit> ARM_Dynarmic::MakeJit() {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2016 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
|
@ -56,11 +56,15 @@ public:
|
||||||
void ClearExclusiveState() override;
|
void ClearExclusiveState() override;
|
||||||
void SetPageTable(const std::shared_ptr<Memory::PageTable>& page_table) override;
|
void SetPageTable(const std::shared_ptr<Memory::PageTable>& page_table) override;
|
||||||
|
|
||||||
|
bool HasSingleInstructionBreakAccuracy() override {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::shared_ptr<Memory::PageTable> GetPageTable() const override;
|
std::shared_ptr<Memory::PageTable> GetPageTable() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ServeBreak();
|
void ServeBreak(int signal);
|
||||||
|
|
||||||
friend class DynarmicUserCallbacks;
|
friend class DynarmicUserCallbacks;
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2014 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
|
@ -24,10 +24,16 @@ ARM_DynCom::ARM_DynCom(Core::System& system_, Memory::MemorySystem& memory,
|
||||||
ARM_DynCom::~ARM_DynCom() {}
|
ARM_DynCom::~ARM_DynCom() {}
|
||||||
|
|
||||||
void ARM_DynCom::Run() {
|
void ARM_DynCom::Run() {
|
||||||
|
if (break_flag) [[unlikely]] {
|
||||||
|
return;
|
||||||
|
}
|
||||||
ExecuteInstructions(std::max<s64>(timer->GetDowncount(), 0));
|
ExecuteInstructions(std::max<s64>(timer->GetDowncount(), 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ARM_DynCom::Step() {
|
void ARM_DynCom::Step() {
|
||||||
|
if (break_flag) [[unlikely]] {
|
||||||
|
return;
|
||||||
|
}
|
||||||
ExecuteInstructions(1);
|
ExecuteInstructions(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2014 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
|
@ -51,6 +51,10 @@ public:
|
||||||
void SetPageTable(const std::shared_ptr<Memory::PageTable>& page_table) override;
|
void SetPageTable(const std::shared_ptr<Memory::PageTable>& page_table) override;
|
||||||
void PrepareReschedule() override;
|
void PrepareReschedule() override;
|
||||||
|
|
||||||
|
bool HasSingleInstructionBreakAccuracy() override {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::shared_ptr<Memory::PageTable> GetPageTable() const override;
|
std::shared_ptr<Memory::PageTable> GetPageTable() const override;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
// Copyright 2012 Michael Kang, 2014 Citra Emulator Project
|
// Copyright 2012 Michael Kang, 2014 Citra Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
@ -19,7 +23,9 @@
|
||||||
#include "core/arm/skyeye_common/vfp/vfp.h"
|
#include "core/arm/skyeye_common/vfp/vfp.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
#include "core/gdbstub/gdbstub.h"
|
#include "core/gdbstub/gdbstub.h"
|
||||||
|
#endif
|
||||||
#include "core/hle/kernel/svc.h"
|
#include "core/hle/kernel/svc.h"
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
|
|
||||||
|
|
@ -918,9 +924,11 @@ MICROPROFILE_DEFINE(DynCom_Execute, "DynCom", "Execute", MP_RGB(255, 0, 0));
|
||||||
unsigned InterpreterMainLoop(ARMul_State* cpu) {
|
unsigned InterpreterMainLoop(ARMul_State* cpu) {
|
||||||
MICROPROFILE_SCOPE(DynCom_Execute);
|
MICROPROFILE_SCOPE(DynCom_Execute);
|
||||||
|
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
/// Nearest upcoming GDB code execution breakpoint, relative to the last dispatch's address.
|
/// Nearest upcoming GDB code execution breakpoint, relative to the last dispatch's address.
|
||||||
GDBStub::BreakpointAddress breakpoint_data;
|
GDBStub::BreakpointAddress breakpoint_data;
|
||||||
breakpoint_data.type = GDBStub::BreakpointType::None;
|
breakpoint_data.type = GDBStub::BreakpointType::None;
|
||||||
|
#endif
|
||||||
|
|
||||||
#undef RM
|
#undef RM
|
||||||
#undef RS
|
#undef RS
|
||||||
|
|
@ -948,16 +956,14 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) {
|
||||||
#define INC_PC(l) ptr += sizeof(arm_inst) + l
|
#define INC_PC(l) ptr += sizeof(arm_inst) + l
|
||||||
#define INC_PC_STUB ptr += sizeof(arm_inst)
|
#define INC_PC_STUB ptr += sizeof(arm_inst)
|
||||||
|
|
||||||
#ifdef ANDROID
|
#ifndef ENABLE_GDBSTUB
|
||||||
#define GDB_BP_CHECK
|
#define GDB_BP_CHECK
|
||||||
#else
|
#else
|
||||||
#define GDB_BP_CHECK \
|
#define GDB_BP_CHECK \
|
||||||
cpu->Cpsr &= ~(1 << 5); \
|
cpu->Cpsr &= ~(1 << 5); \
|
||||||
cpu->Cpsr |= cpu->TFlag << 5; \
|
cpu->Cpsr |= cpu->TFlag << 5; \
|
||||||
if (GDBStub::IsServerEnabled()) { \
|
if (GDBStub::IsServerEnabled()) [[unlikely]] { \
|
||||||
if (GDBStub::IsMemoryBreak()) { \
|
if (breakpoint_data.type != GDBStub::BreakpointType::None && \
|
||||||
goto END; \
|
|
||||||
} else if (breakpoint_data.type != GDBStub::BreakpointType::None && \
|
|
||||||
PC == breakpoint_data.address) { \
|
PC == breakpoint_data.address) { \
|
||||||
cpu->RecordBreak(breakpoint_data); \
|
cpu->RecordBreak(breakpoint_data); \
|
||||||
goto END; \
|
goto END; \
|
||||||
|
|
@ -1651,7 +1657,7 @@ DISPATCH: {
|
||||||
goto END;
|
goto END;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef ANDROID
|
#ifdef ENABLE_GDBSTUB
|
||||||
// Find breakpoint if one exists within the block
|
// Find breakpoint if one exists within the block
|
||||||
if (GDBStub::IsConnected()) {
|
if (GDBStub::IsConnected()) {
|
||||||
breakpoint_data =
|
breakpoint_data =
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,23 @@
|
||||||
// Copyright 2015 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <csignal>
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/swap.h"
|
#include "common/swap.h"
|
||||||
#include "core/arm/skyeye_common/armstate.h"
|
#include "core/arm/skyeye_common/armstate.h"
|
||||||
#include "core/arm/skyeye_common/vfp/vfp.h"
|
#include "core/arm/skyeye_common/vfp/vfp.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
|
#include "core/gdbstub/gdbstub.h"
|
||||||
|
#endif
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
|
|
||||||
|
#ifndef SIGTRAP
|
||||||
|
constexpr u32 SIGTRAP = 5;
|
||||||
|
#endif
|
||||||
|
|
||||||
ARMul_State::ARMul_State(Core::System& system_, Memory::MemorySystem& memory_,
|
ARMul_State::ARMul_State(Core::System& system_, Memory::MemorySystem& memory_,
|
||||||
PrivilegeMode initial_mode)
|
PrivilegeMode initial_mode)
|
||||||
: system{system_}, memory{memory_} {
|
: system{system_}, memory{memory_} {
|
||||||
|
|
@ -182,26 +190,12 @@ void ARMul_State::ResetMPCoreCP15Registers() {
|
||||||
CP15[CP15_MAIN_TLB_LOCKDOWN_ATTRIBUTE] = 0x00000000;
|
CP15[CP15_MAIN_TLB_LOCKDOWN_ATTRIBUTE] = 0x00000000;
|
||||||
CP15[CP15_TLB_DEBUG_CONTROL] = 0x00000000;
|
CP15[CP15_TLB_DEBUG_CONTROL] = 0x00000000;
|
||||||
}
|
}
|
||||||
#ifdef ANDROID
|
|
||||||
static void CheckMemoryBreakpoint(u32 address, GDBStub::BreakpointType type) {}
|
|
||||||
#else
|
|
||||||
static void CheckMemoryBreakpoint(u32 address, GDBStub::BreakpointType type) {
|
|
||||||
if (GDBStub::IsServerEnabled() && GDBStub::CheckBreakpoint(address, type)) {
|
|
||||||
LOG_DEBUG(Debug, "Found memory breakpoint @ {:08x}", address);
|
|
||||||
GDBStub::Break(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
u8 ARMul_State::ReadMemory8(u32 address) const {
|
u8 ARMul_State::ReadMemory8(u32 address) const {
|
||||||
CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Read);
|
|
||||||
|
|
||||||
return memory.Read8(address);
|
return memory.Read8(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
u16 ARMul_State::ReadMemory16(u32 address) const {
|
u16 ARMul_State::ReadMemory16(u32 address) const {
|
||||||
CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Read);
|
|
||||||
|
|
||||||
u16 data = memory.Read16(address);
|
u16 data = memory.Read16(address);
|
||||||
|
|
||||||
if (InBigEndianMode())
|
if (InBigEndianMode())
|
||||||
|
|
@ -211,8 +205,6 @@ u16 ARMul_State::ReadMemory16(u32 address) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 ARMul_State::ReadMemory32(u32 address) const {
|
u32 ARMul_State::ReadMemory32(u32 address) const {
|
||||||
CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Read);
|
|
||||||
|
|
||||||
u32 data = memory.Read32(address);
|
u32 data = memory.Read32(address);
|
||||||
|
|
||||||
if (InBigEndianMode())
|
if (InBigEndianMode())
|
||||||
|
|
@ -222,8 +214,6 @@ u32 ARMul_State::ReadMemory32(u32 address) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 ARMul_State::ReadMemory64(u32 address) const {
|
u64 ARMul_State::ReadMemory64(u32 address) const {
|
||||||
CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Read);
|
|
||||||
|
|
||||||
u64 data = memory.Read64(address);
|
u64 data = memory.Read64(address);
|
||||||
|
|
||||||
if (InBigEndianMode())
|
if (InBigEndianMode())
|
||||||
|
|
@ -233,14 +223,10 @@ u64 ARMul_State::ReadMemory64(u32 address) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ARMul_State::WriteMemory8(u32 address, u8 data) {
|
void ARMul_State::WriteMemory8(u32 address, u8 data) {
|
||||||
CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Write);
|
|
||||||
|
|
||||||
memory.Write8(address, data);
|
memory.Write8(address, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ARMul_State::WriteMemory16(u32 address, u16 data) {
|
void ARMul_State::WriteMemory16(u32 address, u16 data) {
|
||||||
CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Write);
|
|
||||||
|
|
||||||
if (InBigEndianMode())
|
if (InBigEndianMode())
|
||||||
data = Common::swap16(data);
|
data = Common::swap16(data);
|
||||||
|
|
||||||
|
|
@ -248,8 +234,6 @@ void ARMul_State::WriteMemory16(u32 address, u16 data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ARMul_State::WriteMemory32(u32 address, u32 data) {
|
void ARMul_State::WriteMemory32(u32 address, u32 data) {
|
||||||
CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Write);
|
|
||||||
|
|
||||||
if (InBigEndianMode())
|
if (InBigEndianMode())
|
||||||
data = Common::swap32(data);
|
data = Common::swap32(data);
|
||||||
|
|
||||||
|
|
@ -257,8 +241,6 @@ void ARMul_State::WriteMemory32(u32 address, u32 data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ARMul_State::WriteMemory64(u32 address, u64 data) {
|
void ARMul_State::WriteMemory64(u32 address, u64 data) {
|
||||||
CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Write);
|
|
||||||
|
|
||||||
if (InBigEndianMode())
|
if (InBigEndianMode())
|
||||||
data = Common::swap64(data);
|
data = Common::swap64(data);
|
||||||
|
|
||||||
|
|
@ -601,6 +583,7 @@ void ARMul_State::WriteCP15Register(u32 value, u32 crn, u32 opcode_1, u32 crm, u
|
||||||
}
|
}
|
||||||
|
|
||||||
void ARMul_State::ServeBreak() {
|
void ARMul_State::ServeBreak() {
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
if (!GDBStub::IsServerEnabled()) {
|
if (!GDBStub::IsServerEnabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -609,12 +592,9 @@ void ARMul_State::ServeBreak() {
|
||||||
DEBUG_ASSERT(Reg[15] == last_bkpt.address);
|
DEBUG_ASSERT(Reg[15] == last_bkpt.address);
|
||||||
}
|
}
|
||||||
|
|
||||||
Kernel::Thread* thread = system.Kernel().GetCurrentThreadManager().GetCurrentThread();
|
if (last_bkpt_hit) {
|
||||||
system.GetRunningCore().SaveContext(thread->context);
|
|
||||||
|
|
||||||
if (last_bkpt_hit || GDBStub::IsMemoryBreak() || GDBStub::GetCpuStepFlag()) {
|
|
||||||
last_bkpt_hit = false;
|
last_bkpt_hit = false;
|
||||||
GDBStub::Break();
|
GDBStub::Break(SIGTRAP);
|
||||||
GDBStub::SendTrap(thread, 5);
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
/* armdefs.h -- ARMulator common definitions: ARM6 Instruction Emulator.
|
/* armdefs.h -- ARMulator common definitions: ARM6 Instruction Emulator.
|
||||||
Copyright (C) 1994 Advanced RISC Machines Ltd.
|
Copyright (C) 1994 Advanced RISC Machines Ltd.
|
||||||
|
|
||||||
|
|
@ -21,7 +25,9 @@
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "core/arm/skyeye_common/arm_regformat.h"
|
#include "core/arm/skyeye_common/arm_regformat.h"
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
#include "core/gdbstub/gdbstub.h"
|
#include "core/gdbstub/gdbstub.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
class System;
|
class System;
|
||||||
|
|
@ -199,10 +205,12 @@ public:
|
||||||
return TFlag ? 2 : 4;
|
return TFlag ? 2 : 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
void RecordBreak(GDBStub::BreakpointAddress bkpt) {
|
void RecordBreak(GDBStub::BreakpointAddress bkpt) {
|
||||||
last_bkpt = bkpt;
|
last_bkpt = bkpt;
|
||||||
last_bkpt_hit = true;
|
last_bkpt_hit = true;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void ServeBreak();
|
void ServeBreak();
|
||||||
|
|
||||||
|
|
@ -267,6 +275,8 @@ private:
|
||||||
u32 exclusive_tag; // The address for which the local monitor is in exclusive access mode
|
u32 exclusive_tag; // The address for which the local monitor is in exclusive access mode
|
||||||
bool exclusive_state;
|
bool exclusive_state;
|
||||||
|
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
GDBStub::BreakpointAddress last_bkpt{};
|
GDBStub::BreakpointAddress last_bkpt{};
|
||||||
bool last_bkpt_hit = false;
|
bool last_bkpt_hit = false;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,9 @@
|
||||||
#include "core/dumping/backend.h"
|
#include "core/dumping/backend.h"
|
||||||
#include "core/file_sys/ncch_container.h"
|
#include "core/file_sys/ncch_container.h"
|
||||||
#include "core/frontend/image_interface.h"
|
#include "core/frontend/image_interface.h"
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
#include "core/gdbstub/gdbstub.h"
|
#include "core/gdbstub/gdbstub.h"
|
||||||
|
#endif
|
||||||
#include "core/global.h"
|
#include "core/global.h"
|
||||||
#include "core/hle/kernel/ipc_debugger/recorder.h"
|
#include "core/hle/kernel/ipc_debugger/recorder.h"
|
||||||
#include "core/hle/kernel/kernel.h"
|
#include "core/hle/kernel/kernel.h"
|
||||||
|
|
@ -83,23 +85,17 @@ System::ResultStatus System::RunLoop(bool tight_loop) {
|
||||||
return ResultStatus::ErrorNotInitialized;
|
return ResultStatus::ErrorNotInitialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
if (GDBStub::IsServerEnabled()) {
|
if (GDBStub::IsServerEnabled()) {
|
||||||
Kernel::Thread* thread = kernel->GetCurrentThreadManager().GetCurrentThread();
|
// The break flag is only set if GDB is connected,
|
||||||
if (thread && running_core) {
|
// we can do clearing here safely. If it is ever
|
||||||
running_core->SaveContext(thread->context);
|
// used outside, move the clearing outside the if.
|
||||||
|
for (auto& cpu_core : cpu_cores) {
|
||||||
|
cpu_core->ClearBreakFlag();
|
||||||
}
|
}
|
||||||
GDBStub::HandlePacket(*this);
|
GDBStub::HandlePacket(*this);
|
||||||
|
|
||||||
// If the loop is halted and we want to step, use a tiny (1) number of instructions to
|
|
||||||
// execute. Otherwise, get out of the loop function.
|
|
||||||
if (GDBStub::GetCpuHaltFlag()) {
|
|
||||||
if (GDBStub::GetCpuStepFlag()) {
|
|
||||||
tight_loop = false;
|
|
||||||
} else {
|
|
||||||
return ResultStatus::Success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
Signal signal{Signal::None};
|
Signal signal{Signal::None};
|
||||||
u32 param{};
|
u32 param{};
|
||||||
|
|
@ -259,6 +255,8 @@ System::ResultStatus System::RunLoop(bool tight_loop) {
|
||||||
cpu_core->GetTimer().Idle();
|
cpu_core->GetTimer().Idle();
|
||||||
PrepareReschedule();
|
PrepareReschedule();
|
||||||
} else {
|
} else {
|
||||||
|
// In the rare case the break flag is set (due to exception thrown)
|
||||||
|
// there is probably no need to adjust the timer accordingly.
|
||||||
if (tight_loop) {
|
if (tight_loop) {
|
||||||
cpu_core->Run();
|
cpu_core->Run();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -269,10 +267,6 @@ System::ResultStatus System::RunLoop(bool tight_loop) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GDBStub::IsServerEnabled()) {
|
|
||||||
GDBStub::SetCpuStepFlag(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Reschedule();
|
Reschedule();
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
|
|
@ -571,7 +565,9 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window,
|
||||||
app_loader->ReadProgramId(loading_title_id);
|
app_loader->ReadProgramId(loading_title_id);
|
||||||
HW::AES::InitKeys();
|
HW::AES::InitKeys();
|
||||||
Service::Init(*this, loading_title_id, lle_modules, !app_loader->DoingInitialSetup());
|
Service::Init(*this, loading_title_id, lle_modules, !app_loader->DoingInitialSetup());
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
GDBStub::DeferStart();
|
GDBStub::DeferStart();
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!registered_image_interface) {
|
if (!registered_image_interface) {
|
||||||
registered_image_interface = std::make_shared<Frontend::ImageInterface>();
|
registered_image_interface = std::make_shared<Frontend::ImageInterface>();
|
||||||
|
|
@ -696,7 +692,9 @@ void System::Shutdown(bool is_deserializing) {
|
||||||
gpu.reset();
|
gpu.reset();
|
||||||
if (!is_deserializing) {
|
if (!is_deserializing) {
|
||||||
lle_modules.clear();
|
lle_modules.clear();
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
GDBStub::Shutdown();
|
GDBStub::Shutdown();
|
||||||
|
#endif
|
||||||
perf_stats.reset();
|
perf_stats.reset();
|
||||||
app_loader.reset();
|
app_loader.reset();
|
||||||
}
|
}
|
||||||
|
|
@ -759,8 +757,10 @@ void System::Reset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::ApplySettings() {
|
void System::ApplySettings() {
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
GDBStub::SetServerPort(Settings::values.gdbstub_port.GetValue());
|
GDBStub::SetServerPort(Settings::values.gdbstub_port.GetValue());
|
||||||
GDBStub::ToggleServer(Settings::values.use_gdbstub.GetValue());
|
GDBStub::ToggleServer(Settings::values.use_gdbstub.GetValue());
|
||||||
|
#endif
|
||||||
|
|
||||||
if (gpu) {
|
if (gpu) {
|
||||||
#ifndef ANDROID
|
#ifndef ANDROID
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,8 @@ public:
|
||||||
ErrorSavestate, ///< Error saving or loading
|
ErrorSavestate, ///< Error saving or loading
|
||||||
ErrorArticDisconnected, ///< Error when artic base disconnects
|
ErrorArticDisconnected, ///< Error when artic base disconnects
|
||||||
ErrorN3DSApplication, ///< Error launching New 3DS application in Old 3DS mode
|
ErrorN3DSApplication, ///< Error launching New 3DS application in Old 3DS mode
|
||||||
|
ErrorCoreExceptionRaised, ///< The CPU emulation raised an exception
|
||||||
|
ErrorMemoryExceptionRaised, ///< Unmmaped memory was accessed
|
||||||
ShutdownRequested, ///< Emulated program requested a system shutdown
|
ShutdownRequested, ///< Emulated program requested a system shutdown
|
||||||
ErrorUnknown ///< Any other error
|
ErrorUnknown ///< Any other error
|
||||||
};
|
};
|
||||||
|
|
@ -403,6 +405,18 @@ public:
|
||||||
info_led_color_changed = func;
|
info_led_color_changed = func;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetDebugNextProcessFlag() {
|
||||||
|
debug_next_process = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetDebugNextProcessFlag() {
|
||||||
|
return debug_next_process;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClearDebugNextProcessFlag() {
|
||||||
|
debug_next_process = false;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Initialize the emulated system.
|
* Initialize the emulated system.
|
||||||
|
|
@ -512,6 +526,8 @@ private:
|
||||||
Common::Vec3<u8> info_led_color;
|
Common::Vec3<u8> info_led_color;
|
||||||
std::function<void()> info_led_color_changed;
|
std::function<void()> info_led_color_changed;
|
||||||
|
|
||||||
|
bool debug_next_process;
|
||||||
|
|
||||||
friend class boost::serialization::access;
|
friend class boost::serialization::access;
|
||||||
template <typename Archive>
|
template <typename Archive>
|
||||||
void serialize(Archive& ar, const unsigned int file_version);
|
void serialize(Archive& ar, const unsigned int file_version);
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,3 +1,7 @@
|
||||||
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
// Copyright 2013 Dolphin Emulator Project
|
// Copyright 2013 Dolphin Emulator Project
|
||||||
// Licensed under GPLv2+
|
// Licensed under GPLv2+
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
@ -10,6 +14,10 @@
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "core/hle/kernel/thread.h"
|
#include "core/hle/kernel/thread.h"
|
||||||
|
|
||||||
|
#ifndef ENABLE_GDBSTUB
|
||||||
|
#error "File was included with GDB stub support disabled"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
class System;
|
class System;
|
||||||
}
|
}
|
||||||
|
|
@ -60,18 +68,28 @@ void Shutdown();
|
||||||
/// Checks if the gdbstub server is enabled.
|
/// Checks if the gdbstub server is enabled.
|
||||||
bool IsServerEnabled();
|
bool IsServerEnabled();
|
||||||
|
|
||||||
|
/// Returns true if the GDB server is initialized
|
||||||
|
bool IsInitialized();
|
||||||
|
|
||||||
/// Returns true if there is an active socket connection.
|
/// Returns true if there is an active socket connection.
|
||||||
bool IsConnected();
|
bool IsConnected();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signal to the gdbstub server that it should halt CPU execution.
|
* Signal to the gdbstub server that it should halt CPU execution.
|
||||||
*
|
*
|
||||||
* @param is_memory_break If true, the break resulted from a memory breakpoint.
|
* @param signal Signal that produced the break (SIGTRAP by default)
|
||||||
*/
|
*/
|
||||||
void Break(bool is_memory_break = false);
|
void Break(int signal);
|
||||||
|
|
||||||
/// Determine if there was a memory breakpoint.
|
/**
|
||||||
bool IsMemoryBreak();
|
* Signal to the GDB stub that the specified process ID is exiting
|
||||||
|
*/
|
||||||
|
void OnProcessExit(u32 process_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal to the GDB stub that the specified thread ID is exiting
|
||||||
|
*/
|
||||||
|
void OnThreadExit(u32 thread_id);
|
||||||
|
|
||||||
/// Read and handle packet from gdb client.
|
/// Read and handle packet from gdb client.
|
||||||
void HandlePacket(Core::System& system);
|
void HandlePacket(Core::System& system);
|
||||||
|
|
@ -88,37 +106,10 @@ BreakpointAddress GetNextBreakpointFromAddress(VAddr addr, GDBStub::BreakpointTy
|
||||||
* Check if a breakpoint of the specified type exists at the given address.
|
* Check if a breakpoint of the specified type exists at the given address.
|
||||||
*
|
*
|
||||||
* @param addr Address of breakpoint.
|
* @param addr Address of breakpoint.
|
||||||
|
* @param access_len Access size in bytes.
|
||||||
* @param type Type of breakpoint.
|
* @param type Type of breakpoint.
|
||||||
*/
|
*/
|
||||||
bool CheckBreakpoint(VAddr addr, GDBStub::BreakpointType type);
|
bool CheckBreakpoint(VAddr addr, u32 access_len, BreakpointType type);
|
||||||
|
|
||||||
// If set to true, the CPU will halt at the beginning of the next CPU loop.
|
|
||||||
bool GetCpuHaltFlag();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If set to true, the CPU will halt at the beginning of the next CPU loop.
|
|
||||||
*
|
|
||||||
* @param halt whether to halt on the next loop
|
|
||||||
*/
|
|
||||||
void SetCpuHaltFlag(bool halt);
|
|
||||||
|
|
||||||
// If set to true and the CPU is halted, the CPU will step one instruction.
|
|
||||||
bool GetCpuStepFlag();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When set to true, the CPU will step one instruction when the CPU is halted next.
|
|
||||||
*
|
|
||||||
* @param is_step
|
|
||||||
*/
|
|
||||||
void SetCpuStepFlag(bool is_step);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send trap signal from thread back to the gdbstub server.
|
|
||||||
*
|
|
||||||
* @param thread Sending thread.
|
|
||||||
* @param trap Trap no.
|
|
||||||
*/
|
|
||||||
void SendTrap(Kernel::Thread* thread, int trap);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send reply to gdb client.
|
* Send reply to gdb client.
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,22 @@
|
||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <csignal>
|
||||||
#include <fmt/ranges.h>
|
#include <fmt/ranges.h>
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/gdbstub/gdbstub.h"
|
#include "core/gdbstub/gdbstub.h"
|
||||||
#include "core/gdbstub/hio.h"
|
#include "core/gdbstub/hio.h"
|
||||||
|
|
||||||
|
#ifndef ENABLE_GDBSTUB
|
||||||
|
#error "File was compiled with GDB stub support disabled"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SIGTRAP
|
||||||
|
constexpr u32 SIGTRAP = 5;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace GDBStub {
|
namespace GDBStub {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
@ -23,9 +32,6 @@ enum class Status {
|
||||||
|
|
||||||
static std::atomic<Status> request_status{Status::NoRequest};
|
static std::atomic<Status> request_status{Status::NoRequest};
|
||||||
|
|
||||||
static std::atomic<bool> was_halted = false;
|
|
||||||
static std::atomic<bool> was_stepping = false;
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -54,7 +60,7 @@ static void SendErrorReply(int error_code, int retval = -1) {
|
||||||
SendReply(packet.data());
|
SendReply(packet.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetHioRequest(Core::System& system, const VAddr addr) {
|
void SetHioRequest(Core::System& system, Kernel::Process* process, const VAddr addr) {
|
||||||
if (!IsServerEnabled()) {
|
if (!IsServerEnabled()) {
|
||||||
LOG_WARNING(Debug_GDBStub, "HIO requested but GDB stub is not running");
|
LOG_WARNING(Debug_GDBStub, "HIO requested but GDB stub is not running");
|
||||||
return;
|
return;
|
||||||
|
|
@ -70,14 +76,13 @@ void SetHioRequest(Core::System& system, const VAddr addr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& memory = system.Memory();
|
auto& memory = system.Memory();
|
||||||
const auto process = system.Kernel().GetCurrentProcess();
|
|
||||||
|
|
||||||
if (!memory.IsValidVirtualAddress(*process, addr)) {
|
if (!memory.IsValidVirtualAddress(*process, addr)) {
|
||||||
LOG_WARNING(Debug_GDBStub, "Invalid address for HIO request");
|
LOG_WARNING(Debug_GDBStub, "Invalid address for HIO request");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
memory.ReadBlock(addr, ¤t_hio_request, sizeof(PackedGdbHioRequest));
|
memory.ReadBlock(*process, addr, ¤t_hio_request, sizeof(PackedGdbHioRequest));
|
||||||
|
|
||||||
if (current_hio_request.magic != std::array{'G', 'D', 'B', '\0'}) {
|
if (current_hio_request.magic != std::array{'G', 'D', 'B', '\0'}) {
|
||||||
std::string_view bad_magic{
|
std::string_view bad_magic{
|
||||||
|
|
@ -97,18 +102,13 @@ void SetHioRequest(Core::System& system, const VAddr addr) {
|
||||||
current_hio_request_addr = addr;
|
current_hio_request_addr = addr;
|
||||||
request_status = Status::NotSent;
|
request_status = Status::NotSent;
|
||||||
|
|
||||||
was_halted = GetCpuHaltFlag();
|
|
||||||
was_stepping = GetCpuStepFlag();
|
|
||||||
|
|
||||||
// Now halt, so that no further instructions are executed until the request
|
// Now halt, so that no further instructions are executed until the request
|
||||||
// is processed by the client. We will continue after the reply comes back
|
// is processed by the client. We will continue after the reply comes back
|
||||||
Break();
|
Break(SIGTRAP);
|
||||||
SetCpuHaltFlag(true);
|
|
||||||
SetCpuStepFlag(false);
|
|
||||||
system.GetRunningCore().ClearInstructionCache();
|
system.GetRunningCore().ClearInstructionCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HandleHioReply(Core::System& system, const u8* const command_buffer,
|
void HandleHioReply(Core::System& system, Kernel::Process* process, const u8* const command_buffer,
|
||||||
const u32 command_length) {
|
const u32 command_length) {
|
||||||
if (!IsWaitingForHioReply()) {
|
if (!IsWaitingForHioReply()) {
|
||||||
LOG_WARNING(Debug_GDBStub, "Got HIO reply but never sent a request");
|
LOG_WARNING(Debug_GDBStub, "Got HIO reply but never sent a request");
|
||||||
|
|
@ -177,7 +177,6 @@ void HandleHioReply(Core::System& system, const u8* const command_buffer,
|
||||||
current_hio_request.retval, current_hio_request.gdb_errno,
|
current_hio_request.retval, current_hio_request.gdb_errno,
|
||||||
current_hio_request.ctrl_c);
|
current_hio_request.ctrl_c);
|
||||||
|
|
||||||
const auto process = system.Kernel().GetCurrentProcess();
|
|
||||||
auto& memory = system.Memory();
|
auto& memory = system.Memory();
|
||||||
|
|
||||||
// should have been checked when we first initialized the request,
|
// should have been checked when we first initialized the request,
|
||||||
|
|
@ -188,15 +187,14 @@ void HandleHioReply(Core::System& system, const u8* const command_buffer,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
memory.WriteBlock(current_hio_request_addr, ¤t_hio_request, sizeof(PackedGdbHioRequest));
|
memory.WriteBlock(*process, current_hio_request_addr, ¤t_hio_request,
|
||||||
|
sizeof(PackedGdbHioRequest));
|
||||||
|
|
||||||
current_hio_request = {};
|
current_hio_request = {};
|
||||||
current_hio_request_addr = 0;
|
current_hio_request_addr = 0;
|
||||||
request_status = Status::NoRequest;
|
request_status = Status::NoRequest;
|
||||||
|
|
||||||
// Restore state from before the request came in
|
// Restore state from before the request came in
|
||||||
SetCpuStepFlag(was_stepping);
|
|
||||||
SetCpuHaltFlag(was_halted);
|
|
||||||
system.GetRunningCore().ClearInstructionCache();
|
system.GetRunningCore().ClearInstructionCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2023 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
|
@ -6,10 +6,18 @@
|
||||||
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
#ifndef ENABLE_GDBSTUB
|
||||||
|
#error "File was included with GDB stub support disabled"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
class System;
|
class System;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
class Process;
|
||||||
|
}
|
||||||
|
|
||||||
namespace GDBStub {
|
namespace GDBStub {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -51,7 +59,7 @@ static_assert(sizeof(PackedGdbHioRequest) == 152,
|
||||||
*
|
*
|
||||||
* @param address The memory address of the \ref PackedGdbHioRequest.
|
* @param address The memory address of the \ref PackedGdbHioRequest.
|
||||||
*/
|
*/
|
||||||
void SetHioRequest(Core::System& system, const VAddr address);
|
void SetHioRequest(Core::System& system, Kernel::Process* process, const VAddr address);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If there is a pending HIO request, send it to the client.
|
* If there is a pending HIO request, send it to the client.
|
||||||
|
|
@ -63,6 +71,7 @@ bool HandlePendingHioRequestPacket();
|
||||||
/**
|
/**
|
||||||
* Process an HIO reply from the client.
|
* Process an HIO reply from the client.
|
||||||
*/
|
*/
|
||||||
void HandleHioReply(Core::System& system, const u8* const command_buffer, const u32 command_length);
|
void HandleHioReply(Core::System& system, Kernel::Process* process, const u8* const command_buffer,
|
||||||
|
const u32 command_length);
|
||||||
|
|
||||||
} // namespace GDBStub
|
} // namespace GDBStub
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,9 @@
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/serialization/boost_vector.hpp"
|
#include "common/serialization/boost_vector.hpp"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
|
#include "core/gdbstub/gdbstub.h"
|
||||||
|
#endif
|
||||||
#include "core/hle/kernel/errors.h"
|
#include "core/hle/kernel/errors.h"
|
||||||
#include "core/hle/kernel/memory.h"
|
#include "core/hle/kernel/memory.h"
|
||||||
#include "core/hle/kernel/process.h"
|
#include "core/hle/kernel/process.h"
|
||||||
|
|
@ -260,9 +263,25 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) {
|
||||||
|
|
||||||
vm_manager.LogLayout(Common::Log::Level::Debug);
|
vm_manager.LogLayout(Common::Log::Level::Debug);
|
||||||
Kernel::SetupMainThread(kernel, codeset->entrypoint, main_thread_priority, SharedFrom(this));
|
Kernel::SetupMainThread(kernel, codeset->entrypoint, main_thread_priority, SharedFrom(this));
|
||||||
|
|
||||||
|
// Pause process at start if flag enabled and we are not a sysmodule
|
||||||
|
if (Core::System::GetInstance().GetDebugNextProcessFlag() &&
|
||||||
|
resource_limit->GetCategory() != Kernel::ResourceLimitCategory::Other) {
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
|
if (GDBStub::IsServerEnabled()) {
|
||||||
|
LOG_INFO(Loader, "Pausing process {} at start", process_id);
|
||||||
|
SetDebugBreak(true);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
Core::System::GetInstance().ClearDebugNextProcessFlag();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Process::Exit() {
|
void Process::Exit() {
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
|
GDBStub::OnProcessExit(process_id);
|
||||||
|
#endif
|
||||||
|
|
||||||
auto plgldr = Service::PLGLDR::GetService(Core::System::GetInstance());
|
auto plgldr = Service::PLGLDR::GetService(Core::System::GetInstance());
|
||||||
if (plgldr) {
|
if (plgldr) {
|
||||||
plgldr->OnProcessExit(*this, kernel);
|
plgldr->OnProcessExit(*this, kernel);
|
||||||
|
|
@ -592,6 +611,42 @@ Result Process::Unmap(VAddr target, VAddr source, u32 size, VMAPermission perms,
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<Kernel::Thread>> Kernel::Process::GetThreadList() {
|
||||||
|
std::vector<std::shared_ptr<Kernel::Thread>> ret;
|
||||||
|
for (u32 core = 0; core < Core::GetNumCores(); core++) {
|
||||||
|
auto thread_list = kernel.GetThreadManager(core).GetThreadList();
|
||||||
|
for (auto& thread : thread_list) {
|
||||||
|
if (thread->owner_process.lock().get() == this) {
|
||||||
|
ret.push_back(thread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kernel::Process::SetDebugBreak(bool debug_break, std::vector<u32> thread_ids) {
|
||||||
|
auto thread_list = GetThreadList();
|
||||||
|
bool needs_reschedule = false;
|
||||||
|
for (auto& t : thread_list) {
|
||||||
|
|
||||||
|
if (!thread_ids.empty()) {
|
||||||
|
u32 thread_id = t->thread_id;
|
||||||
|
if (std::find(thread_ids.begin(), thread_ids.end(), thread_id) == thread_ids.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
needs_reschedule |= t->SetDebugBreak(debug_break);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needs_reschedule) {
|
||||||
|
for (u32 i = 0; i < Core::GetNumCores(); i++) {
|
||||||
|
Core::GetCore(i).PrepareReschedule();
|
||||||
|
kernel.GetThreadManager(i).Reschedule();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Process::FreeAllMemory() {
|
void Process::FreeAllMemory() {
|
||||||
if (memory_region == nullptr || resource_limit == nullptr) {
|
if (memory_region == nullptr || resource_limit == nullptr) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2015 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
|
@ -226,6 +226,10 @@ public:
|
||||||
Result Unmap(VAddr target, VAddr source, u32 size, VMAPermission perms,
|
Result Unmap(VAddr target, VAddr source, u32 size, VMAPermission perms,
|
||||||
bool privileged = false);
|
bool privileged = false);
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<Kernel::Thread>> GetThreadList();
|
||||||
|
|
||||||
|
void SetDebugBreak(bool debug_break, std::vector<u32> thread_ids = {});
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void FreeAllMemory();
|
void FreeAllMemory();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,9 @@
|
||||||
#include "core/arm/arm_interface.h"
|
#include "core/arm/arm_interface.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
#include "core/gdbstub/hio.h"
|
#include "core/gdbstub/hio.h"
|
||||||
|
#endif
|
||||||
#include "core/hle/kernel/address_arbiter.h"
|
#include "core/hle/kernel/address_arbiter.h"
|
||||||
#include "core/hle/kernel/client_port.h"
|
#include "core/hle/kernel/client_port.h"
|
||||||
#include "core/hle/kernel/client_session.h"
|
#include "core/hle/kernel/client_session.h"
|
||||||
|
|
@ -1171,7 +1173,9 @@ void SVC::OutputDebugString(VAddr address, s32 len) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (len == 0) {
|
if (len == 0) {
|
||||||
GDBStub::SetHioRequest(system, address);
|
#ifdef ENABLE_GDBSTUB
|
||||||
|
GDBStub::SetHioRequest(system, kernel.GetCurrentProcess().get(), address);
|
||||||
|
#endif
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@
|
||||||
#include "core/arm/arm_interface.h"
|
#include "core/arm/arm_interface.h"
|
||||||
#include "core/arm/skyeye_common/armstate.h"
|
#include "core/arm/skyeye_common/armstate.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
|
#include "core/gdbstub/gdbstub.h"
|
||||||
|
#endif
|
||||||
#include "core/hle/kernel/errors.h"
|
#include "core/hle/kernel/errors.h"
|
||||||
#include "core/hle/kernel/kernel.h"
|
#include "core/hle/kernel/kernel.h"
|
||||||
#include "core/hle/kernel/mutex.h"
|
#include "core/hle/kernel/mutex.h"
|
||||||
|
|
@ -77,6 +80,7 @@ void Thread::serialize(Archive& ar, const unsigned int file_version) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ar & wakeup_callback;
|
ar & wakeup_callback;
|
||||||
|
ar & debug_break;
|
||||||
}
|
}
|
||||||
SERIALIZE_IMPL(Thread)
|
SERIALIZE_IMPL(Thread)
|
||||||
|
|
||||||
|
|
@ -133,6 +137,10 @@ void Thread::Stop() {
|
||||||
process->tls_slots[tls_page].reset(tls_slot);
|
process->tls_slots[tls_page].reset(tls_slot);
|
||||||
process->resource_limit->Release(ResourceLimitType::Thread, 1);
|
process->resource_limit->Release(ResourceLimitType::Thread, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
|
GDBStub::OnThreadExit(thread_id);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreadManager::SwitchContext(Thread* new_thread) {
|
void ThreadManager::SwitchContext(Thread* new_thread) {
|
||||||
|
|
@ -191,7 +199,7 @@ Thread* ThreadManager::PopNextReadyThread() {
|
||||||
u32 next_priority{};
|
u32 next_priority{};
|
||||||
next = nullptr;
|
next = nullptr;
|
||||||
|
|
||||||
if (thread && thread->status == ThreadStatus::Running) {
|
if (thread && thread->status == ThreadStatus::Running && thread->CanSchedule()) {
|
||||||
do {
|
do {
|
||||||
// We have to do better than the current thread.
|
// We have to do better than the current thread.
|
||||||
// This call returns null when that's not possible.
|
// This call returns null when that's not possible.
|
||||||
|
|
@ -201,18 +209,18 @@ Thread* ThreadManager::PopNextReadyThread() {
|
||||||
// Otherwise just keep going with the current thread
|
// Otherwise just keep going with the current thread
|
||||||
next = thread;
|
next = thread;
|
||||||
break;
|
break;
|
||||||
} else if (!next->can_schedule) {
|
} else if (!next->CanSchedule()) {
|
||||||
skipped.push_back({next_priority, next});
|
skipped.push_back({next_priority, next});
|
||||||
}
|
}
|
||||||
|
|
||||||
} while (!next->can_schedule);
|
} while (!next->CanSchedule());
|
||||||
} else {
|
} else {
|
||||||
do {
|
do {
|
||||||
std::tie(next_priority, next) = ready_queue.pop_first();
|
std::tie(next_priority, next) = ready_queue.pop_first();
|
||||||
if (next && !next->can_schedule) {
|
if (next && !next->CanSchedule()) {
|
||||||
skipped.push_back({next_priority, next});
|
skipped.push_back({next_priority, next});
|
||||||
}
|
}
|
||||||
} while (next && !next->can_schedule);
|
} while (next && !next->CanSchedule());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto it = skipped.rbegin(); it != skipped.rend(); it++) {
|
for (auto it = skipped.rbegin(); it != skipped.rend(); it++) {
|
||||||
|
|
@ -537,6 +545,14 @@ VAddr Thread::GetCommandBufferAddress() const {
|
||||||
return GetTLSAddress() + command_header_offset;
|
return GetTLSAddress() + command_header_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Thread::SetDebugBreak(bool _debug_break) {
|
||||||
|
if (debug_break == _debug_break) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
debug_break = _debug_break;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
CpuLimiter::~CpuLimiter() {}
|
CpuLimiter::~CpuLimiter() {}
|
||||||
|
|
||||||
CpuLimiterMulti::CpuLimiterMulti(Kernel::KernelSystem& _kernel) : kernel(_kernel) {}
|
CpuLimiterMulti::CpuLimiterMulti(Kernel::KernelSystem& _kernel) : kernel(_kernel) {}
|
||||||
|
|
|
||||||
|
|
@ -365,6 +365,15 @@ public:
|
||||||
return status == ThreadStatus::WaitSynchAll;
|
return status == ThreadStatus::WaitSynchAll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CanSchedule() {
|
||||||
|
// TODO(PabloMK7): This may not be the proper way
|
||||||
|
// threads are marked as non-schedulable when they
|
||||||
|
// are in debug break. Figure out and fix.
|
||||||
|
return can_schedule && !debug_break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SetDebugBreak(bool debug_break);
|
||||||
|
|
||||||
Core::ARM_Interface::ThreadContext context{};
|
Core::ARM_Interface::ThreadContext context{};
|
||||||
|
|
||||||
u32 thread_id;
|
u32 thread_id;
|
||||||
|
|
@ -410,6 +419,7 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ThreadManager& thread_manager;
|
ThreadManager& thread_manager;
|
||||||
|
bool debug_break{};
|
||||||
|
|
||||||
friend class boost::serialization::access;
|
friend class boost::serialization::access;
|
||||||
template <class Archive>
|
template <class Archive>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <csignal>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <boost/serialization/array.hpp>
|
#include <boost/serialization/array.hpp>
|
||||||
#include <boost/serialization/binary_object.hpp>
|
#include <boost/serialization/binary_object.hpp>
|
||||||
|
|
@ -12,10 +13,14 @@
|
||||||
#include "common/atomic_ops.h"
|
#include "common/atomic_ops.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "common/optional_helper.h"
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "common/swap.h"
|
#include "common/swap.h"
|
||||||
#include "core/arm/arm_interface.h"
|
#include "core/arm/arm_interface.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
|
#include "core/gdbstub/gdbstub.h"
|
||||||
|
#endif
|
||||||
#include "core/global.h"
|
#include "core/global.h"
|
||||||
#include "core/hle/kernel/process.h"
|
#include "core/hle/kernel/process.h"
|
||||||
#include "core/hle/service/plgldr/plgldr.h"
|
#include "core/hle/service/plgldr/plgldr.h"
|
||||||
|
|
@ -28,6 +33,14 @@ SERIALIZE_EXPORT_IMPL(Memory::MemorySystem::BackingMemImpl<Memory::Region::VRAM>
|
||||||
SERIALIZE_EXPORT_IMPL(Memory::MemorySystem::BackingMemImpl<Memory::Region::DSP>)
|
SERIALIZE_EXPORT_IMPL(Memory::MemorySystem::BackingMemImpl<Memory::Region::DSP>)
|
||||||
SERIALIZE_EXPORT_IMPL(Memory::MemorySystem::BackingMemImpl<Memory::Region::N3DS>)
|
SERIALIZE_EXPORT_IMPL(Memory::MemorySystem::BackingMemImpl<Memory::Region::N3DS>)
|
||||||
|
|
||||||
|
#ifndef SIGTRAP
|
||||||
|
constexpr u32 SIGTRAP = 5;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SIGSEGV
|
||||||
|
constexpr u32 SIGSEGV = 11;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Memory {
|
namespace Memory {
|
||||||
|
|
||||||
void PageTable::Clear() {
|
void PageTable::Clear() {
|
||||||
|
|
@ -187,7 +200,17 @@ public:
|
||||||
std::memcpy(dest_buffer, src_ptr, copy_amount);
|
std::memcpy(dest_buffer, src_ptr, copy_amount);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PageType::RasterizerCachedMemory: {
|
case PageType::MemoryWatchpoint: {
|
||||||
|
auto it = page_table.watchpoint_pages_map.find(page_index);
|
||||||
|
ASSERT_MSG(it != page_table.watchpoint_pages_map.end(),
|
||||||
|
"Missing memory for watchpoint page");
|
||||||
|
|
||||||
|
const u8* src_ptr = it->second.memory.GetPtr() + page_offset;
|
||||||
|
std::memcpy(dest_buffer, src_ptr, copy_amount);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PageType::RasterizerCachedMemory:
|
||||||
|
case PageType::RasterizerCachedMemoryWatchpoint: {
|
||||||
if constexpr (!UNSAFE) {
|
if constexpr (!UNSAFE) {
|
||||||
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
||||||
FlushMode::Flush);
|
FlushMode::Flush);
|
||||||
|
|
@ -235,7 +258,17 @@ public:
|
||||||
std::memcpy(dest_ptr, src_buffer, copy_amount);
|
std::memcpy(dest_ptr, src_buffer, copy_amount);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PageType::RasterizerCachedMemory: {
|
case PageType::MemoryWatchpoint: {
|
||||||
|
auto it = page_table.watchpoint_pages_map.find(page_index);
|
||||||
|
ASSERT_MSG(it != page_table.watchpoint_pages_map.end(),
|
||||||
|
"Missing memory for watchpoint page");
|
||||||
|
|
||||||
|
u8* dest_ptr = it->second.memory.GetPtr() + page_offset;
|
||||||
|
std::memcpy(dest_ptr, src_buffer, copy_amount);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PageType::RasterizerCachedMemory:
|
||||||
|
case PageType::RasterizerCachedMemoryWatchpoint: {
|
||||||
if constexpr (!UNSAFE) {
|
if constexpr (!UNSAFE) {
|
||||||
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
||||||
FlushMode::Invalidate);
|
FlushMode::Invalidate);
|
||||||
|
|
@ -392,6 +425,88 @@ PAddr& Memory::MemorySystem::Plugin3GXFramebufferAddress() {
|
||||||
return impl->plugin_fb_address;
|
return impl->plugin_fb_address;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MemorySystem::RegisterWatchpoint(const Kernel::Process& process, VAddr addr, u32 size) {
|
||||||
|
auto& page_table = *process.vm_manager.page_table;
|
||||||
|
|
||||||
|
VAddr current = addr;
|
||||||
|
VAddr end = addr + size;
|
||||||
|
|
||||||
|
while (current < end) {
|
||||||
|
const VAddr page_base = (current & ~CITRA_PAGE_MASK);
|
||||||
|
const VAddr page_index = page_base >> CITRA_PAGE_BITS;
|
||||||
|
|
||||||
|
auto it = page_table.watchpoint_pages_map.find(page_index);
|
||||||
|
if (it != page_table.watchpoint_pages_map.end()) {
|
||||||
|
// Nothing to do, only increment count.
|
||||||
|
it->second.watchpoint_count++;
|
||||||
|
} else {
|
||||||
|
MemoryRef mem;
|
||||||
|
PageType& type = page_table.attributes[page_index];
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case PageType::Memory:
|
||||||
|
mem = page_table.pointers.Ref(page_index);
|
||||||
|
type = PageType::MemoryWatchpoint;
|
||||||
|
page_table.pointers[page_index] = nullptr;
|
||||||
|
break;
|
||||||
|
case PageType::RasterizerCachedMemory:
|
||||||
|
mem = GetPointerForRasterizerCache(page_base);
|
||||||
|
type = PageType::RasterizerCachedMemoryWatchpoint;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_ERROR(HW_Memory, "Cannot get pointer to register watchpoint for page 0x{:08X}",
|
||||||
|
page_base);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
page_table.watchpoint_pages_map.insert(
|
||||||
|
{page_index,
|
||||||
|
PageTable::WatchpointPageInfo{.watchpoint_count = 1, .memory = std::move(mem)}});
|
||||||
|
}
|
||||||
|
|
||||||
|
current = page_base + CITRA_PAGE_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemorySystem::UnregisterWatchpoint(const Kernel::Process& process, VAddr addr, u32 size) {
|
||||||
|
auto& page_table = *process.vm_manager.page_table;
|
||||||
|
|
||||||
|
VAddr current = addr;
|
||||||
|
VAddr end = addr + size;
|
||||||
|
|
||||||
|
while (current < end) {
|
||||||
|
const VAddr page_base = (current & ~CITRA_PAGE_MASK);
|
||||||
|
const VAddr page_index = page_base >> CITRA_PAGE_BITS;
|
||||||
|
|
||||||
|
auto it = page_table.watchpoint_pages_map.find(page_index);
|
||||||
|
if (it != page_table.watchpoint_pages_map.end()) {
|
||||||
|
if (--it->second.watchpoint_count == 0) {
|
||||||
|
|
||||||
|
PageType& type = page_table.attributes[page_index];
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case PageType::MemoryWatchpoint:
|
||||||
|
type = PageType::Memory;
|
||||||
|
page_table.pointers[page_index] = it->second.memory;
|
||||||
|
break;
|
||||||
|
case PageType::RasterizerCachedMemoryWatchpoint:
|
||||||
|
type = PageType::RasterizerCachedMemory;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_ERROR(HW_Memory, "Invalid watchpoint page type for page 0x{:08X}: {}",
|
||||||
|
page_base, static_cast<u8>(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
page_table.watchpoint_pages_map.erase(page_index);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(HW_Memory, "No watchpoint found on page 0x{:08X}", page_base);
|
||||||
|
}
|
||||||
|
|
||||||
|
current = page_base + CITRA_PAGE_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MemorySystem::MapPages(PageTable& page_table, u32 base, u32 size, MemoryRef memory,
|
void MemorySystem::MapPages(PageTable& page_table, u32 base, u32 size, MemoryRef memory,
|
||||||
PageType type) {
|
PageType type) {
|
||||||
LOG_DEBUG(HW_Memory, "Mapping {} onto {:08X}-{:08X}", (void*)memory.GetPtr(),
|
LOG_DEBUG(HW_Memory, "Mapping {} onto {:08X}-{:08X}", (void*)memory.GetPtr(),
|
||||||
|
|
@ -449,13 +564,37 @@ void MemorySystem::UnregisterPageTable(std::shared_ptr<PageTable> page_table) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void MemorySystem::UnmappedAccess(const VAddr vaddr, const T value, bool read) {
|
||||||
|
const std::string mode = (read ? "Read" : "Write");
|
||||||
|
const std::string value_str = read ? std::string("") : fmt::format(" 0x{:08X}", value);
|
||||||
|
const std::string message = fmt::format("unmapped {}{}{} @ 0x{:08X} at PC 0x{:08X}", mode,
|
||||||
|
sizeof(T) * 8, value_str, vaddr, impl->GetPC());
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
|
if (GDBStub::IsConnected()) {
|
||||||
|
GDBStub::Break(SIGSEGV);
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
if (Settings::values.break_on_unmapped_memory_access) {
|
||||||
|
impl->system.SetStatus(Core::System::ResultStatus::ErrorMemoryExceptionRaised,
|
||||||
|
message.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERROR(HW_Memory, "{}", message);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T MemorySystem::Read(const std::shared_ptr<PageTable>& page_table, const VAddr vaddr) {
|
T MemorySystem::Read(const std::shared_ptr<PageTable>& page_table, const VAddr vaddr) {
|
||||||
|
constexpr bool is_optional = is_optional_type<T>;
|
||||||
|
using ReadType = optional_inner_or_type<T>;
|
||||||
|
|
||||||
|
constexpr size_t read_size = sizeof(ReadType);
|
||||||
|
|
||||||
const u8* page_pointer = page_table->pointers[vaddr >> CITRA_PAGE_BITS];
|
const u8* page_pointer = page_table->pointers[vaddr >> CITRA_PAGE_BITS];
|
||||||
if (page_pointer) {
|
if (page_pointer) {
|
||||||
// NOTE: Avoid adding any extra logic to this fast-path block
|
// NOTE: Avoid adding any extra logic to this fast-path block
|
||||||
T value;
|
ReadType value;
|
||||||
std::memcpy(&value, &page_pointer[vaddr & CITRA_PAGE_MASK], sizeof(T));
|
std::memcpy(&value, &page_pointer[vaddr & CITRA_PAGE_MASK], read_size);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -464,37 +603,77 @@ T MemorySystem::Read(const std::shared_ptr<PageTable>& page_table, const VAddr v
|
||||||
if (vaddr & (1 << 31)) {
|
if (vaddr & (1 << 31)) {
|
||||||
PAddr paddr = (vaddr & ~(1 << 31));
|
PAddr paddr = (vaddr & ~(1 << 31));
|
||||||
if ((paddr & 0xF0000000) == Memory::FCRAM_PADDR) { // Check FCRAM region
|
if ((paddr & 0xF0000000) == Memory::FCRAM_PADDR) { // Check FCRAM region
|
||||||
T value;
|
ReadType value;
|
||||||
std::memcpy(&value, GetFCRAMPointer(paddr - Memory::FCRAM_PADDR), sizeof(T));
|
std::memcpy(&value, GetFCRAMPointer(paddr - Memory::FCRAM_PADDR), read_size);
|
||||||
return value;
|
return value;
|
||||||
} else if ((paddr & 0xF0000000) == 0x10000000 &&
|
} else if ((paddr & 0xF0000000) == 0x10000000 &&
|
||||||
paddr >= Memory::IO_AREA_PADDR) { // Check MMIO region
|
paddr >= Memory::IO_AREA_PADDR) { // Check MMIO region
|
||||||
return impl->system.GPU().ReadReg(static_cast<VAddr>(paddr) - Memory::IO_AREA_PADDR +
|
return static_cast<ReadType>(impl->system.GPU().ReadReg(
|
||||||
0x1EC00000);
|
static_cast<VAddr>(paddr) - Memory::IO_AREA_PADDR + 0x1EC00000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PageType type = page_table->attributes[vaddr >> CITRA_PAGE_BITS];
|
PageType type = page_table->attributes[vaddr >> CITRA_PAGE_BITS];
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case PageType::Unmapped:
|
case PageType::Unmapped: {
|
||||||
LOG_ERROR(HW_Memory, "unmapped Read{} @ 0x{:08X} at PC 0x{:08X}", sizeof(T) * 8, vaddr,
|
|
||||||
impl->GetPC());
|
UnmappedAccess<ReadType>(vaddr, 0, true);
|
||||||
return 0;
|
|
||||||
|
if constexpr (is_optional) {
|
||||||
|
return std::nullopt;
|
||||||
|
} else {
|
||||||
|
return T{};
|
||||||
|
}
|
||||||
|
}
|
||||||
case PageType::Memory:
|
case PageType::Memory:
|
||||||
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:08X}", vaddr);
|
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:08X}", vaddr);
|
||||||
break;
|
break;
|
||||||
case PageType::RasterizerCachedMemory: {
|
case PageType::MemoryWatchpoint: {
|
||||||
RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Flush);
|
auto it = page_table->watchpoint_pages_map.find(vaddr >> CITRA_PAGE_BITS);
|
||||||
|
ASSERT_MSG(it != page_table->watchpoint_pages_map.end(),
|
||||||
|
"Missing memory for watchpoint page");
|
||||||
|
|
||||||
|
ReadType value;
|
||||||
|
std::memcpy(&value, it->second.memory.GetPtr() + (vaddr & CITRA_PAGE_MASK), read_size);
|
||||||
|
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
|
if (GDBStub::CheckBreakpoint(vaddr, read_size, GDBStub::BreakpointType::Read)) {
|
||||||
|
GDBStub::Break(SIGTRAP);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
[[likely]] case PageType::RasterizerCachedMemory: {
|
||||||
|
RasterizerFlushVirtualRegion(vaddr, read_size, FlushMode::Flush);
|
||||||
|
|
||||||
|
ReadType value;
|
||||||
|
std::memcpy(&value, GetPointerForRasterizerCache(vaddr), read_size);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
case PageType::RasterizerCachedMemoryWatchpoint: {
|
||||||
|
RasterizerFlushVirtualRegion(vaddr, read_size, FlushMode::Flush);
|
||||||
|
|
||||||
|
ReadType value;
|
||||||
|
std::memcpy(&value, GetPointerForRasterizerCache(vaddr), read_size);
|
||||||
|
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
|
if (GDBStub::CheckBreakpoint(vaddr, read_size, GDBStub::BreakpointType::Read)) {
|
||||||
|
GDBStub::Break(SIGTRAP);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
T value;
|
|
||||||
std::memcpy(&value, GetPointerForRasterizerCache(vaddr), sizeof(T));
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if constexpr (is_optional) {
|
||||||
|
return std::nullopt;
|
||||||
|
} else {
|
||||||
return T{};
|
return T{};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
|
@ -527,17 +706,43 @@ void MemorySystem::Write(const std::shared_ptr<PageTable>& page_table, const VAd
|
||||||
PageType type = page_table->attributes[vaddr >> CITRA_PAGE_BITS];
|
PageType type = page_table->attributes[vaddr >> CITRA_PAGE_BITS];
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case PageType::Unmapped:
|
case PageType::Unmapped:
|
||||||
LOG_ERROR(HW_Memory, "unmapped Write{} 0x{:08X} @ 0x{:08X} at PC 0x{:08X}",
|
(void)UnmappedAccess<T>(vaddr, data, false);
|
||||||
sizeof(data) * 8, (u32)data, vaddr, impl->GetPC());
|
|
||||||
return;
|
return;
|
||||||
case PageType::Memory:
|
case PageType::Memory:
|
||||||
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:08X}", vaddr);
|
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:08X}", vaddr);
|
||||||
break;
|
break;
|
||||||
case PageType::RasterizerCachedMemory: {
|
case PageType::MemoryWatchpoint: {
|
||||||
|
auto it = page_table->watchpoint_pages_map.find(vaddr >> CITRA_PAGE_BITS);
|
||||||
|
ASSERT_MSG(it != page_table->watchpoint_pages_map.end(),
|
||||||
|
"Missing memory for watchpoint page");
|
||||||
|
|
||||||
|
std::memcpy(it->second.memory.GetPtr() + (vaddr & CITRA_PAGE_MASK), &data, sizeof(T));
|
||||||
|
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
|
if (GDBStub::CheckBreakpoint(vaddr, sizeof(T), GDBStub::BreakpointType::Write)) {
|
||||||
|
GDBStub::Break(SIGTRAP);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
[[likely]] case PageType::RasterizerCachedMemory: {
|
||||||
RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Invalidate);
|
RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Invalidate);
|
||||||
std::memcpy(GetPointerForRasterizerCache(vaddr), &data, sizeof(T));
|
std::memcpy(GetPointerForRasterizerCache(vaddr), &data, sizeof(T));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case PageType::RasterizerCachedMemoryWatchpoint: {
|
||||||
|
RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Invalidate);
|
||||||
|
std::memcpy(GetPointerForRasterizerCache(vaddr), &data, sizeof(T));
|
||||||
|
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
|
if (GDBStub::CheckBreakpoint(vaddr, sizeof(T), GDBStub::BreakpointType::Write)) {
|
||||||
|
GDBStub::Break(SIGTRAP);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
|
@ -556,18 +761,48 @@ bool MemorySystem::WriteExclusive(const VAddr vaddr, const T data, const T expec
|
||||||
PageType type = impl->current_page_table->attributes[vaddr >> CITRA_PAGE_BITS];
|
PageType type = impl->current_page_table->attributes[vaddr >> CITRA_PAGE_BITS];
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case PageType::Unmapped:
|
case PageType::Unmapped:
|
||||||
LOG_ERROR(HW_Memory, "unmapped Write{} 0x{:08X} @ 0x{:08X} at PC 0x{:08X}",
|
(void)UnmappedAccess<T>(vaddr, data, false);
|
||||||
sizeof(data) * 8, static_cast<u32>(data), vaddr, impl->GetPC());
|
|
||||||
return true;
|
return true;
|
||||||
case PageType::Memory:
|
case PageType::Memory:
|
||||||
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:08X}", vaddr);
|
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:08X}", vaddr);
|
||||||
return true;
|
return true;
|
||||||
case PageType::RasterizerCachedMemory: {
|
case PageType::MemoryWatchpoint: {
|
||||||
|
auto it = impl->current_page_table->watchpoint_pages_map.find(vaddr >> CITRA_PAGE_BITS);
|
||||||
|
ASSERT_MSG(it != impl->current_page_table->watchpoint_pages_map.end(),
|
||||||
|
"Missing memory for watchpoint page");
|
||||||
|
|
||||||
|
const auto volatile_pointer =
|
||||||
|
reinterpret_cast<volatile T*>(it->second.memory.GetPtr() + (vaddr & CITRA_PAGE_MASK));
|
||||||
|
|
||||||
|
bool ret = Common::AtomicCompareAndSwap(volatile_pointer, data, expected);
|
||||||
|
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
|
if (GDBStub::CheckBreakpoint(vaddr, sizeof(T), GDBStub::BreakpointType::Write)) {
|
||||||
|
GDBStub::Break(SIGTRAP);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
[[likely]] case PageType::RasterizerCachedMemory: {
|
||||||
RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Invalidate);
|
RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Invalidate);
|
||||||
const auto volatile_pointer =
|
const auto volatile_pointer =
|
||||||
reinterpret_cast<volatile T*>(GetPointerForRasterizerCache(vaddr).GetPtr());
|
reinterpret_cast<volatile T*>(GetPointerForRasterizerCache(vaddr).GetPtr());
|
||||||
return Common::AtomicCompareAndSwap(volatile_pointer, data, expected);
|
return Common::AtomicCompareAndSwap(volatile_pointer, data, expected);
|
||||||
}
|
}
|
||||||
|
case PageType::RasterizerCachedMemoryWatchpoint: {
|
||||||
|
RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Invalidate);
|
||||||
|
const auto volatile_pointer =
|
||||||
|
reinterpret_cast<volatile T*>(GetPointerForRasterizerCache(vaddr).GetPtr());
|
||||||
|
|
||||||
|
#ifdef ENABLE_GDBSTUB
|
||||||
|
if (GDBStub::CheckBreakpoint(vaddr, sizeof(T), GDBStub::BreakpointType::Write)) {
|
||||||
|
GDBStub::Break(SIGTRAP);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return Common::AtomicCompareAndSwap(volatile_pointer, data, expected);
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
|
@ -582,7 +817,7 @@ bool MemorySystem::IsValidVirtualAddress(const Kernel::Process& process, const V
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (page_table.attributes[vaddr >> CITRA_PAGE_BITS] == PageType::RasterizerCachedMemory) {
|
if (page_table.attributes[vaddr >> CITRA_PAGE_BITS] != PageType::Unmapped) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -600,7 +835,9 @@ u8* MemorySystem::GetPointer(const VAddr vaddr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (impl->current_page_table->attributes[vaddr >> CITRA_PAGE_BITS] ==
|
if (impl->current_page_table->attributes[vaddr >> CITRA_PAGE_BITS] ==
|
||||||
PageType::RasterizerCachedMemory) {
|
PageType::RasterizerCachedMemory ||
|
||||||
|
impl->current_page_table->attributes[vaddr >> CITRA_PAGE_BITS] ==
|
||||||
|
PageType::RasterizerCachedMemoryWatchpoint) {
|
||||||
return GetPointerForRasterizerCache(vaddr);
|
return GetPointerForRasterizerCache(vaddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -615,7 +852,9 @@ const u8* MemorySystem::GetPointer(const VAddr vaddr) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (impl->current_page_table->attributes[vaddr >> CITRA_PAGE_BITS] ==
|
if (impl->current_page_table->attributes[vaddr >> CITRA_PAGE_BITS] ==
|
||||||
PageType::RasterizerCachedMemory) {
|
PageType::RasterizerCachedMemory ||
|
||||||
|
impl->current_page_table->attributes[vaddr >> CITRA_PAGE_BITS] ==
|
||||||
|
PageType::RasterizerCachedMemoryWatchpoint) {
|
||||||
return GetPointerForRasterizerCache(vaddr);
|
return GetPointerForRasterizerCache(vaddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -755,7 +994,10 @@ void MemorySystem::RasterizerMarkRegionCached(PAddr start, u32 size, bool cached
|
||||||
// address space, for example, a system module need not have a VRAM mapping.
|
// address space, for example, a system module need not have a VRAM mapping.
|
||||||
break;
|
break;
|
||||||
case PageType::Memory:
|
case PageType::Memory:
|
||||||
page_type = PageType::RasterizerCachedMemory;
|
case PageType::MemoryWatchpoint:
|
||||||
|
page_type = (page_type == PageType::Memory)
|
||||||
|
? PageType::RasterizerCachedMemory
|
||||||
|
: PageType::RasterizerCachedMemoryWatchpoint;
|
||||||
page_table->pointers[vaddr >> CITRA_PAGE_BITS] = nullptr;
|
page_table->pointers[vaddr >> CITRA_PAGE_BITS] = nullptr;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
@ -768,10 +1010,16 @@ void MemorySystem::RasterizerMarkRegionCached(PAddr start, u32 size, bool cached
|
||||||
// It is not necessary for a process to have this region mapped into its
|
// It is not necessary for a process to have this region mapped into its
|
||||||
// address space, for example, a system module need not have a VRAM mapping.
|
// address space, for example, a system module need not have a VRAM mapping.
|
||||||
break;
|
break;
|
||||||
case PageType::RasterizerCachedMemory: {
|
case PageType::RasterizerCachedMemory:
|
||||||
page_type = PageType::Memory;
|
case PageType::RasterizerCachedMemoryWatchpoint: {
|
||||||
|
page_type = (page_type == PageType::RasterizerCachedMemory)
|
||||||
|
? PageType::Memory
|
||||||
|
: PageType::MemoryWatchpoint;
|
||||||
|
|
||||||
|
if (page_type == PageType::Memory) {
|
||||||
page_table->pointers[vaddr >> CITRA_PAGE_BITS] =
|
page_table->pointers[vaddr >> CITRA_PAGE_BITS] =
|
||||||
GetPointerForRasterizerCache(vaddr & ~CITRA_PAGE_MASK);
|
GetPointerForRasterizerCache(vaddr & ~CITRA_PAGE_MASK);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|
@ -815,6 +1063,14 @@ u64 MemorySystem::Read64(const Kernel::Process& process, VAddr addr) {
|
||||||
return Read<u64_le>(process.vm_manager.page_table, addr);
|
return Read<u64_le>(process.vm_manager.page_table, addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<u32> MemorySystem::Read32OrNullopt(VAddr addr) {
|
||||||
|
return Read<std::optional<u32_le>>(impl->current_page_table, addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<u32> MemorySystem::Read32OrNullopt(const Kernel::Process& process, VAddr addr) {
|
||||||
|
return Read<std::optional<u32_le>>(process.vm_manager.page_table, addr);
|
||||||
|
}
|
||||||
|
|
||||||
void MemorySystem::ReadBlock(const Kernel::Process& process, const VAddr src_addr,
|
void MemorySystem::ReadBlock(const Kernel::Process& process, const VAddr src_addr,
|
||||||
void* dest_buffer, const std::size_t size) {
|
void* dest_buffer, const std::size_t size) {
|
||||||
return impl->ReadBlockImpl<false>(process, src_addr, dest_buffer, size);
|
return impl->ReadBlockImpl<false>(process, src_addr, dest_buffer, size);
|
||||||
|
|
@ -911,7 +1167,17 @@ void MemorySystem::ZeroBlock(const Kernel::Process& process, const VAddr dest_ad
|
||||||
std::memset(dest_ptr, 0, copy_amount);
|
std::memset(dest_ptr, 0, copy_amount);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PageType::RasterizerCachedMemory: {
|
case PageType::MemoryWatchpoint: {
|
||||||
|
auto it = page_table.watchpoint_pages_map.find(page_index);
|
||||||
|
ASSERT_MSG(it != page_table.watchpoint_pages_map.end(),
|
||||||
|
"Missing memory for watchpoint page");
|
||||||
|
|
||||||
|
u8* dest_ptr = it->second.memory.GetPtr() + page_offset;
|
||||||
|
std::memset(dest_ptr, 0, copy_amount);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PageType::RasterizerCachedMemory:
|
||||||
|
case PageType::RasterizerCachedMemoryWatchpoint: {
|
||||||
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
||||||
FlushMode::Invalidate);
|
FlushMode::Invalidate);
|
||||||
std::memset(GetPointerForRasterizerCache(current_vaddr), 0, copy_amount);
|
std::memset(GetPointerForRasterizerCache(current_vaddr), 0, copy_amount);
|
||||||
|
|
@ -960,7 +1226,17 @@ void MemorySystem::CopyBlock(const Kernel::Process& dest_process,
|
||||||
WriteBlock(dest_process, dest_addr, src_ptr, copy_amount);
|
WriteBlock(dest_process, dest_addr, src_ptr, copy_amount);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PageType::RasterizerCachedMemory: {
|
case PageType::MemoryWatchpoint: {
|
||||||
|
auto it = page_table.watchpoint_pages_map.find(page_index);
|
||||||
|
ASSERT_MSG(it != page_table.watchpoint_pages_map.end(),
|
||||||
|
"Missing memory for watchpoint page");
|
||||||
|
|
||||||
|
const u8* src_ptr = it->second.memory.GetPtr() + page_offset;
|
||||||
|
WriteBlock(dest_process, dest_addr, src_ptr, copy_amount);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PageType::RasterizerCachedMemory:
|
||||||
|
case PageType::RasterizerCachedMemoryWatchpoint: {
|
||||||
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
|
||||||
FlushMode::Flush);
|
FlushMode::Flush);
|
||||||
WriteBlock(dest_process, dest_addr, GetPointerForRasterizerCache(current_vaddr),
|
WriteBlock(dest_process, dest_addr, GetPointerForRasterizerCache(current_vaddr),
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,13 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <boost/serialization/array.hpp>
|
#include <boost/serialization/array.hpp>
|
||||||
#include <boost/serialization/vector.hpp>
|
#include <boost/serialization/vector.hpp>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/memory_ref.h"
|
#include "common/memory_ref.h"
|
||||||
|
#include "common/swap.h"
|
||||||
|
|
||||||
namespace Kernel {
|
namespace Kernel {
|
||||||
class Process;
|
class Process;
|
||||||
|
|
@ -34,7 +36,7 @@ constexpr u32 CITRA_PAGE_MASK = CITRA_PAGE_SIZE - 1;
|
||||||
constexpr int CITRA_PAGE_BITS = 12;
|
constexpr int CITRA_PAGE_BITS = 12;
|
||||||
constexpr std::size_t PAGE_TABLE_NUM_ENTRIES = 1 << (32 - CITRA_PAGE_BITS);
|
constexpr std::size_t PAGE_TABLE_NUM_ENTRIES = 1 << (32 - CITRA_PAGE_BITS);
|
||||||
|
|
||||||
enum class PageType {
|
enum class PageType : u8 {
|
||||||
/// Page is unmapped and should cause an access error.
|
/// Page is unmapped and should cause an access error.
|
||||||
Unmapped,
|
Unmapped,
|
||||||
/// Page is mapped to regular memory. This is the only type you can get pointers to.
|
/// Page is mapped to regular memory. This is the only type you can get pointers to.
|
||||||
|
|
@ -42,6 +44,12 @@ enum class PageType {
|
||||||
/// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and
|
/// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and
|
||||||
/// invalidation
|
/// invalidation
|
||||||
RasterizerCachedMemory,
|
RasterizerCachedMemory,
|
||||||
|
/// Page is mapped to regular memory. Furthermore a debug watchpoint is set to an address within
|
||||||
|
/// the page.
|
||||||
|
MemoryWatchpoint,
|
||||||
|
/// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and
|
||||||
|
/// invalidation. Furthermore a debug watchpoint is set to an address within the page.
|
||||||
|
RasterizerCachedMemoryWatchpoint,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -82,6 +90,10 @@ struct PageTable {
|
||||||
return Entry(*this, static_cast<VAddr>(idx));
|
return Entry(*this, static_cast<VAddr>(idx));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MemoryRef& Ref(std::size_t idx) {
|
||||||
|
return refs[idx];
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::array<u8*, PAGE_TABLE_NUM_ENTRIES> raw;
|
std::array<u8*, PAGE_TABLE_NUM_ENTRIES> raw;
|
||||||
std::array<MemoryRef, PAGE_TABLE_NUM_ENTRIES> refs;
|
std::array<MemoryRef, PAGE_TABLE_NUM_ENTRIES> refs;
|
||||||
|
|
@ -100,6 +112,22 @@ struct PageTable {
|
||||||
return pointers.raw;
|
return pointers.raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct WatchpointPageInfo {
|
||||||
|
u32 watchpoint_count{};
|
||||||
|
MemoryRef memory;
|
||||||
|
|
||||||
|
template <class Archive>
|
||||||
|
void serialize(Archive& ar, const unsigned int) {
|
||||||
|
ar & watchpoint_count;
|
||||||
|
ar & memory;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map holding pages that are marked to contain watchpoints. We don't need
|
||||||
|
// any fancy performance tricks here, as watchpoints are only used rarely
|
||||||
|
// while debugging and performance is not a priority in such cases.
|
||||||
|
std::unordered_map<VAddr, WatchpointPageInfo> watchpoint_pages_map{};
|
||||||
|
|
||||||
void Clear();
|
void Clear();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -107,6 +135,7 @@ private:
|
||||||
void serialize(Archive& ar, const unsigned int) {
|
void serialize(Archive& ar, const unsigned int) {
|
||||||
ar & pointers.refs;
|
ar & pointers.refs;
|
||||||
ar & attributes;
|
ar & attributes;
|
||||||
|
ar & watchpoint_pages_map;
|
||||||
for (std::size_t i = 0; i < PAGE_TABLE_NUM_ENTRIES; i++) {
|
for (std::size_t i = 0; i < PAGE_TABLE_NUM_ENTRIES; i++) {
|
||||||
pointers.raw[i] = pointers.refs[i].GetPtr();
|
pointers.raw[i] = pointers.refs[i].GetPtr();
|
||||||
}
|
}
|
||||||
|
|
@ -360,6 +389,29 @@ public:
|
||||||
*/
|
*/
|
||||||
u64 Read64(const Kernel::Process& process, VAddr addr);
|
u64 Read64(const Kernel::Process& process, VAddr addr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a 32-bit unsigned value from the current process' address space
|
||||||
|
* at the given virtual address. If the address is invalid std::nullopt
|
||||||
|
* is returned instead.
|
||||||
|
*
|
||||||
|
* @param addr The virtual address to read the 32-bit value from.
|
||||||
|
*
|
||||||
|
* @returns the read 32-bit unsigned value or std::nullopt.
|
||||||
|
*/
|
||||||
|
std::optional<u32> Read32OrNullopt(VAddr addr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a 32-bit unsigned value from the process' address space
|
||||||
|
* at the given virtual address. If the address is invalid std::nullopt
|
||||||
|
* is returned instead.
|
||||||
|
*
|
||||||
|
* @param process The process to read from.
|
||||||
|
* @param addr The virtual address to read the 32-bit value from.
|
||||||
|
*
|
||||||
|
* @returns the read 32-bit unsigned value or std::nullopt.
|
||||||
|
*/
|
||||||
|
std::optional<u32> Read32OrNullopt(const Kernel::Process& process, VAddr addr);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes an 8-bit unsigned integer to the given virtual address in
|
* Writes an 8-bit unsigned integer to the given virtual address in
|
||||||
* the current process' address space.
|
* the current process' address space.
|
||||||
|
|
@ -649,7 +701,14 @@ public:
|
||||||
/// Returns a reference to the framebuffer address of the currently loaded 3GX plugin.
|
/// Returns a reference to the framebuffer address of the currently loaded 3GX plugin.
|
||||||
PAddr& Plugin3GXFramebufferAddress();
|
PAddr& Plugin3GXFramebufferAddress();
|
||||||
|
|
||||||
|
void RegisterWatchpoint(const Kernel::Process& process, VAddr addr, u32 size);
|
||||||
|
|
||||||
|
void UnregisterWatchpoint(const Kernel::Process& process, VAddr addr, u32 size);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
template <typename T>
|
||||||
|
void UnmappedAccess(const VAddr vaddr, const T value, bool read);
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T Read(const std::shared_ptr<PageTable>& page_table, const VAddr vaddr);
|
T Read(const std::shared_ptr<PageTable>& page_table, const VAddr vaddr);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue