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

This commit is contained in:
PabloMK7 2026-05-07 13:48:35 +02:00 committed by GitHub
parent 5ddbaeae23
commit b540725090
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 1671 additions and 466 deletions

View file

@ -99,7 +99,7 @@ endif()
# Track which options were explicitly set by the user (for libretro conflict detection)
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)
set(_USER_SET_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_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)
option(ENABLE_OPENAL "Enables the OpenAL audio backend" ON)

View file

@ -116,6 +116,7 @@ foreach(KEY IN ITEMS
"log_filter"
"log_regex_filter"
"toggle_unique_data_console_type"
"break_on_unmapped_memory_access"
"use_integer_scaling"
"layouts_to_cycle"
"camera_inner_flip"

View file

@ -9,6 +9,7 @@ COPY . /var/azahar-src
RUN mkdir builddir && cd builddir && \
cmake /var/azahar-src -G Ninja \
-DENABLE_QT=OFF \
-DENABLE_GDBSTUB=OFF \
-DENABLE_TESTS=OFF \
-DENABLE_ROOM=ON \
-DENABLE_ROOM_STANDALONE=ON \

View file

@ -79,7 +79,8 @@ android {
"-DENABLE_QT=0", // Don't use QT
"-DENABLE_SDL2=0", // Don't use SDL
"-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
)
}
}

View file

@ -19,6 +19,7 @@ import android.view.Surface
import android.view.View
import android.widget.TextView
import androidx.annotation.Keep
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.fragment.app.DialogFragment
@ -317,6 +318,12 @@ object NativeLibrary {
canContinue = false
}
CoreError.ErrorCoreExceptionRaised -> {
title = emulationActivity.getString(R.string.fatal_error)
message = emulationActivity.getString(R.string.fatal_error_message)
canContinue = false
}
CoreError.ErrorUnknown -> {
title = emulationActivity.getString(R.string.fatal_error)
message = emulationActivity.getString(R.string.fatal_error_message)
@ -439,7 +446,7 @@ object NativeLibrary {
return
}
if (resultCode == EmulationErrorDialogFragment.ShutdownRequested) {
if (resultCode == CoreError.ShutdownRequested.value) {
emulationActivity.finish()
return
}
@ -458,23 +465,26 @@ object NativeLibrary {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
emulationActivity = requireActivity() as EmulationActivity
var captionId = R.string.loader_error_invalid_format
val result = requireArguments().getInt(RESULT_CODE)
if (result == ErrorLoader_ErrorEncrypted) {
captionId = R.string.loader_error_encrypted
var captionString = getString(R.string.loader_error_invalid_format)
if (result == CoreError.ErrorLoader_ErrorEncrypted.value) {
captionString = getString(R.string.loader_error_encrypted)
}
if (result == ErrorArticDisconnected) {
captionId = R.string.artic_base
if (result == CoreError.ErrorArticDisconnected.value) {
captionString = getString(R.string.artic_base)
}
val alert = MaterialAlertDialogBuilder(requireContext())
.setTitle(captionId)
.setTitle(captionString)
.setMessage(
Html.fromHtml(
if (result == ErrorArticDisconnected)
CitraApplication.appContext.resources.getString(R.string.artic_server_comm_error)
if (result == CoreError.ErrorArticDisconnected.value)
getString(R.string.artic_server_comm_error)
else if (result == CoreError.ErrorLoader_ErrorEncrypted.value)
getString(R.string.loader_error_encrypted_desc)
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
)
)
@ -496,21 +506,6 @@ object NativeLibrary {
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 {
val args = Bundle()
args.putInt(RESULT_CODE, resultCode)
@ -857,12 +852,29 @@ object NativeLibrary {
FileUtil.deleteDocument(path)
}
enum class CoreError {
ErrorSystemFiles,
ErrorSavestate,
ErrorArticDisconnected,
ErrorN3DSApplication,
ErrorUnknown
enum class CoreError(val value: Int, @StringRes val stringRes: Int) {
Success(0, R.string.core_error_success),
ErrorNotInitialized(1, R.string.core_error_not_initialized),
ErrorGetLoader(2, R.string.core_error_get_loader),
ErrorSystemMode(3, R.string.core_error_system_mode),
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 {

View file

@ -317,6 +317,7 @@ void Config::ReadValues() {
ReadSetting("Debugging", Settings::values.instant_debug_log);
ReadSetting("Debugging", Settings::values.enable_rpc_server);
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) {
bool use_lle =

View file

@ -36,6 +36,9 @@ constexpr std::array android_config_omitted_keys = {
Keys::audio_encoder_options,
Keys::audio_bitrate,
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
@ -531,10 +534,6 @@ static const char* android_config_default_file_content = (BOOST_HANA_STRING(R"(
# 0 (default): Off, 1: On
)") 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
# 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"(

View file

@ -393,7 +393,6 @@
<string name="learn_more">Learn More</string>
<string name="close">Close</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="none">None</string>
<string name="auto">Auto</string>
@ -437,9 +436,27 @@
<!-- ROM loading errors -->
<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_file_not_found">ROM file does not exist</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 -->
<string name="emulation_menu_help">Press Back to access the menu.</string>

View file

@ -855,10 +855,11 @@ void GMainWindow::InitializeHotkeys() {
// QAction Hotkeys
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");
action->setShortcut(hotkey_registry.GetKeySequence(main_window, action_name));
action->setAutoRepeat(false);
action->setAutoRepeat(auto_repeat);
this->addAction(action);
if (!primary_only)
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_Fullscreen, fullscreen, true);
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_Upright_Screens,
QStringLiteral("Rotate Screens Upright"));
@ -1189,6 +1193,23 @@ void GMainWindow::ConnectMenuEvents() {
connect_menu(ui->action_Capture_Screenshot, &GMainWindow::OnCaptureScreenshot);
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
connect_menu(ui->action_Compress_ROM_File, &GMainWindow::OnCompressFile);
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());
error_severity_icon = QMessageBox::Icon::Critical;
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 {
title = tr("Fatal Error");
message = tr("A fatal error occurred. "

View file

@ -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
// UISetting::values.shortcuts, which is alphabetically ordered.
// 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("Audio Mute/Unmute"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}},
{QStringLiteral("Audio Volume Down"), 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("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("Decrease 3D Factor"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+-"), 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.enable_rpc_server);
ReadBasicSetting(Settings::values.toggle_unique_data_console_type);
ReadBasicSetting(Settings::values.break_on_unmapped_memory_access);
qt_config->beginGroup(QStringLiteral("LLE"));
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.enable_rpc_server);
WriteBasicSetting(Settings::values.toggle_unique_data_console_type);
WriteBasicSetting(Settings::values.break_on_unmapped_memory_access);
qt_config->beginGroup(QStringLiteral("LLE"));
for (const auto& service_module : Settings::values.lle_modules) {

View file

@ -26,7 +26,7 @@ public:
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<UISettings::Shortcut, 38> default_hotkeys;
static const std::array<UISettings::Shortcut, 41> default_hotkeys;
private:
void Initialize(const std::string& config_name);

View file

@ -12,6 +12,7 @@
#include "common/file_util.h"
#include "common/logging/backend.h"
#include "common/settings.h"
#include "core/core.h"
#include "ui_configure_debug.h"
#ifdef ENABLE_VULKAN
#include "video_core/renderer_vulkan/vk_instance.h"
@ -33,6 +34,17 @@ ConfigureDebug::ConfigureDebug(bool is_powered_on_, QWidget* parent)
ui->setupUi(this);
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, []() {
QString path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LogDir));
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_combo->setVisible(!Settings::IsConfiguringGlobal());
#ifndef ENABLE_GDBSTUB
ui->gdb_groupbox->setVisible(false);
#endif
SetupPerGameUI();
}
@ -94,6 +110,9 @@ ConfigureDebug::~ConfigureDebug() = default;
void ConfigureDebug::SetConfiguration() {
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->setValue(Settings::values.gdbstub_port.GetValue());
ui->toggle_console->setEnabled(!is_powered_on);
@ -112,6 +131,8 @@ void ConfigureDebug::SetConfiguration() {
#endif // !ENABLE_SCRIPTING
ui->toggle_unique_data_console_type->setChecked(
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_dump_command_buffers->setChecked(Settings::values.dump_command_buffers.GetValue());
@ -133,6 +154,10 @@ void ConfigureDebug::SetConfiguration() {
ui->clock_display_label->setText(
QStringLiteral("%1%").arg(Settings::values.cpu_clock_percentage.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() {
@ -153,6 +178,8 @@ void ConfigureDebug::ApplyConfiguration() {
Settings::values.enable_rpc_server = ui->enable_rpc_server->isChecked();
Settings::values.toggle_unique_data_console_type =
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.dump_command_buffers = ui->toggle_dump_command_buffers->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);
});
ui->groupBox->setVisible(false);
ui->gdb_groupbox->setVisible(false);
ui->groupBox_2->setVisible(false);
ui->enable_rpc_server->setVisible(false);
ui->toggle_unique_data_console_type->setVisible(false);
ui->break_on_unmapped_memory_access->setVisible(false);
ui->toggle_cpu_jit->setVisible(false);
}

View file

@ -17,7 +17,7 @@
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox">
<widget class="QGroupBox" name="gdb_groupbox">
<property name="title">
<string>GDB</string>
</property>
@ -60,6 +60,13 @@
</item>
</layout>
</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>
</widget>
</item>
@ -299,6 +306,16 @@
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="break_on_unmapped_memory_access">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pauses emulation and shows an error message if an unmapped memory access is detected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Break on unmapped memory access</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View file

@ -206,6 +206,16 @@
<addaction name="action_Advance_Frame"/>
<addaction name="menu_Movie"/>
<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_Dump_Video"/>
<addaction name="separator"/>
@ -453,6 +463,30 @@
<string>Capture Screenshot</string>
</property>
</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">
<property name="checkable">
<bool>true</bool>

View file

@ -59,6 +59,7 @@ add_library(citra_common STATIC
microprofile.cpp
microprofile.h
microprofileui.h
optional_helper.h
param_package.cpp
param_package.h
play_time_manager.cpp

View file

@ -1,4 +1,4 @@
// Copyright 2020 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -153,7 +153,9 @@ private:
void serialize(Archive& ar, const unsigned int) {
ar & backing_mem;
ar & offset;
if (Archive::is_loading::value) {
Init();
}
}
friend class boost::serialization::access;
};

View 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;

View file

@ -166,6 +166,8 @@ void LogSettings() {
log_setting("Debugging_InstantDebugLog", values.instant_debug_log.GetValue());
log_setting("Debugging_ToggleUniqueDataConsoleType",
values.toggle_unique_data_console_type.GetValue());
log_setting("Debugging_BreakOnUnmappedMemoryAccess",
values.break_on_unmapped_memory_access.GetValue());
}
bool IsConfiguringGlobal() {

View file

@ -643,6 +643,7 @@ struct Values {
Setting<bool> instant_debug_log{false, Keys::instant_debug_log};
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> break_on_unmapped_memory_access{false, Keys::break_on_unmapped_memory_access};
// Miscellaneous
Setting<std::string> log_filter{"*:Info", Keys::log_filter};

View file

@ -122,8 +122,7 @@ void BuildCompleteFilename(std::string& _CompleteFilename, const std::string& _P
_CompleteFilename += _Filename;
}
std::vector<std::string> SplitString(const std::string& str, const char delim) {
std::istringstream iss(str);
static std::vector<std::string> SplitString(std::istringstream& iss, const char delim) {
std::vector<std::string> output(1);
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;
}
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::size_t i = 0;

View file

@ -36,6 +36,7 @@ namespace Common {
[[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);
// "C:/Windows/winhelp.exe" to "C:/Windows/", "winhelp", ".exe"

View file

@ -126,10 +126,6 @@ add_library(citra_core STATIC
frontend/image_interface.cpp
frontend/image_interface.h
frontend/input.h
gdbstub/gdbstub.cpp
gdbstub/gdbstub.h
gdbstub/hio.cpp
gdbstub/hio.h
hle/applets/applet.cpp
hle/applets/applet.h
hle/applets/erreula.cpp
@ -529,6 +525,16 @@ if (ENABLE_SCRIPTING)
)
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)
target_sources(citra_core PRIVATE
arm/dynarmic/arm_dynarmic.cpp

View file

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -186,6 +186,13 @@ public:
/// Prepare core for thread reschedule (if needed to correctly handle state)
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() {
return *timer;
}
@ -198,12 +205,28 @@ public:
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:
// This us used for serialization. Returning nullptr is valid if page tables are not used.
virtual std::shared_ptr<Memory::PageTable> GetPageTable() const = 0;
std::shared_ptr<Core::Timing::Timer> timer;
bool break_flag{};
private:
u32 id;
@ -213,6 +236,7 @@ private:
void save(Archive& ar, const unsigned int file_version) const {
ar << timer;
ar << id;
ar << break_flag;
const auto page_table = GetPageTable();
ar << page_table;
for (int i = 0; i < 15; i++) {
@ -258,6 +282,7 @@ private:
ClearInstructionCache();
ar >> timer;
ar >> id;
ar >> break_flag;
std::shared_ptr<Memory::PageTable> page_table{};
ar >> page_table;
SetPageTable(page_table);

View file

@ -1,7 +1,8 @@
// Copyright 2016 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <csignal>
#include <cstring>
#include <dynarmic/interface/A32/a32.h>
#include <dynarmic/interface/optimization_flags.h>
@ -13,10 +14,20 @@
#include "core/arm/dynarmic/arm_tick_counts.h"
#include "core/core.h"
#include "core/core_timing.h"
#ifdef ENABLE_GDBSTUB
#include "core/gdbstub/gdbstub.h"
#endif
#include "core/hle/kernel/svc.h"
#include "core/memory.h"
#ifndef SIGILL
constexpr u32 SIGILL = 4;
#endif
#ifndef SIGTRAP
constexpr u32 SIGTRAP = 5;
#endif
namespace Core {
class DynarmicUserCallbacks final : public Dynarmic::A32::UserCallbacks {
@ -25,6 +36,10 @@ public:
: parent(parent), svc_context(parent.system), memory(parent.memory) {}
~DynarmicUserCallbacks() = default;
std::optional<std::uint32_t> MemoryReadCode(VAddr vaddr) override {
return memory.Read32OrNullopt(vaddr);
}
std::uint8_t MemoryRead8(VAddr vaddr) override {
return memory.Read8(vaddr);
}
@ -82,12 +97,13 @@ public:
case Dynarmic::A32::Exception::NoExecuteFault:
break;
case Dynarmic::A32::Exception::Breakpoint:
#ifdef ENABLE_GDBSTUB
if (GDBStub::IsConnected()) {
parent.jit->HaltExecution();
parent.SetPC(pc);
parent.ServeBreak();
parent.ServeBreak(SIGTRAP);
return;
}
#endif
break;
case Dynarmic::A32::Exception::SendEvent:
case Dynarmic::A32::Exception::SendEventLocal:
@ -99,11 +115,40 @@ public:
case Dynarmic::A32::Exception::PreloadInstruction:
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 {
@ -138,16 +183,19 @@ MICROPROFILE_DEFINE(ARM_Jit, "ARM JIT", "ARM JIT", MP_RGB(255, 64, 64));
void ARM_Dynarmic::Run() {
ASSERT(memory.GetCurrentPageTable() == current_page_table);
MICROPROFILE_SCOPE(ARM_Jit);
if (break_flag) [[unlikely]] {
return;
}
jit->Run();
}
void ARM_Dynarmic::Step() {
jit->Step();
if (GDBStub::IsConnected()) {
ServeBreak();
if (break_flag) [[unlikely]] {
return;
}
jit->Step();
}
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));
}
void ARM_Dynarmic::ServeBreak() {
Kernel::Thread* thread = system.Kernel().GetCurrentThreadManager().GetCurrentThread();
SaveContext(thread->context);
GDBStub::Break();
GDBStub::SendTrap(thread, 5);
void ARM_Dynarmic::ServeBreak([[maybe_unused]] int signal) {
#ifdef ENABLE_GDBSTUB
GDBStub::Break(signal);
#endif
}
std::unique_ptr<Dynarmic::A32::Jit> ARM_Dynarmic::MakeJit() {

View file

@ -1,4 +1,4 @@
// Copyright 2016 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -56,11 +56,15 @@ public:
void ClearExclusiveState() override;
void SetPageTable(const std::shared_ptr<Memory::PageTable>& page_table) override;
bool HasSingleInstructionBreakAccuracy() override {
return false;
}
protected:
std::shared_ptr<Memory::PageTable> GetPageTable() const override;
private:
void ServeBreak();
void ServeBreak(int signal);
friend class DynarmicUserCallbacks;
Core::System& system;

View file

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// 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() {}
void ARM_DynCom::Run() {
if (break_flag) [[unlikely]] {
return;
}
ExecuteInstructions(std::max<s64>(timer->GetDowncount(), 0));
}
void ARM_DynCom::Step() {
if (break_flag) [[unlikely]] {
return;
}
ExecuteInstructions(1);
}

View file

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -51,6 +51,10 @@ public:
void SetPageTable(const std::shared_ptr<Memory::PageTable>& page_table) override;
void PrepareReschedule() override;
bool HasSingleInstructionBreakAccuracy() override {
return true;
}
protected:
std::shared_ptr<Memory::PageTable> GetPageTable() const override;

View file

@ -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
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -19,7 +23,9 @@
#include "core/arm/skyeye_common/vfp/vfp.h"
#include "core/core.h"
#include "core/core_timing.h"
#ifdef ENABLE_GDBSTUB
#include "core/gdbstub/gdbstub.h"
#endif
#include "core/hle/kernel/svc.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) {
MICROPROFILE_SCOPE(DynCom_Execute);
#ifdef ENABLE_GDBSTUB
/// Nearest upcoming GDB code execution breakpoint, relative to the last dispatch's address.
GDBStub::BreakpointAddress breakpoint_data;
breakpoint_data.type = GDBStub::BreakpointType::None;
#endif
#undef RM
#undef RS
@ -948,16 +956,14 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) {
#define INC_PC(l) ptr += sizeof(arm_inst) + l
#define INC_PC_STUB ptr += sizeof(arm_inst)
#ifdef ANDROID
#ifndef ENABLE_GDBSTUB
#define GDB_BP_CHECK
#else
#define GDB_BP_CHECK \
cpu->Cpsr &= ~(1 << 5); \
cpu->Cpsr |= cpu->TFlag << 5; \
if (GDBStub::IsServerEnabled()) { \
if (GDBStub::IsMemoryBreak()) { \
goto END; \
} else if (breakpoint_data.type != GDBStub::BreakpointType::None && \
if (GDBStub::IsServerEnabled()) [[unlikely]] { \
if (breakpoint_data.type != GDBStub::BreakpointType::None && \
PC == breakpoint_data.address) { \
cpu->RecordBreak(breakpoint_data); \
goto END; \
@ -1651,7 +1657,7 @@ DISPATCH: {
goto END;
}
#ifndef ANDROID
#ifdef ENABLE_GDBSTUB
// Find breakpoint if one exists within the block
if (GDBStub::IsConnected()) {
breakpoint_data =

View file

@ -1,15 +1,23 @@
// Copyright 2015 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <csignal>
#include "common/logging/log.h"
#include "common/swap.h"
#include "core/arm/skyeye_common/armstate.h"
#include "core/arm/skyeye_common/vfp/vfp.h"
#include "core/core.h"
#ifdef ENABLE_GDBSTUB
#include "core/gdbstub/gdbstub.h"
#endif
#include "core/memory.h"
#ifndef SIGTRAP
constexpr u32 SIGTRAP = 5;
#endif
ARMul_State::ARMul_State(Core::System& system_, Memory::MemorySystem& memory_,
PrivilegeMode initial_mode)
: system{system_}, memory{memory_} {
@ -182,26 +190,12 @@ void ARMul_State::ResetMPCoreCP15Registers() {
CP15[CP15_MAIN_TLB_LOCKDOWN_ATTRIBUTE] = 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 {
CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Read);
return memory.Read8(address);
}
u16 ARMul_State::ReadMemory16(u32 address) const {
CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Read);
u16 data = memory.Read16(address);
if (InBigEndianMode())
@ -211,8 +205,6 @@ u16 ARMul_State::ReadMemory16(u32 address) const {
}
u32 ARMul_State::ReadMemory32(u32 address) const {
CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Read);
u32 data = memory.Read32(address);
if (InBigEndianMode())
@ -222,8 +214,6 @@ u32 ARMul_State::ReadMemory32(u32 address) const {
}
u64 ARMul_State::ReadMemory64(u32 address) const {
CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Read);
u64 data = memory.Read64(address);
if (InBigEndianMode())
@ -233,14 +223,10 @@ u64 ARMul_State::ReadMemory64(u32 address) const {
}
void ARMul_State::WriteMemory8(u32 address, u8 data) {
CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Write);
memory.Write8(address, data);
}
void ARMul_State::WriteMemory16(u32 address, u16 data) {
CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Write);
if (InBigEndianMode())
data = Common::swap16(data);
@ -248,8 +234,6 @@ void ARMul_State::WriteMemory16(u32 address, u16 data) {
}
void ARMul_State::WriteMemory32(u32 address, u32 data) {
CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Write);
if (InBigEndianMode())
data = Common::swap32(data);
@ -257,8 +241,6 @@ void ARMul_State::WriteMemory32(u32 address, u32 data) {
}
void ARMul_State::WriteMemory64(u32 address, u64 data) {
CheckMemoryBreakpoint(address, GDBStub::BreakpointType::Write);
if (InBigEndianMode())
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() {
#ifdef ENABLE_GDBSTUB
if (!GDBStub::IsServerEnabled()) {
return;
}
@ -609,12 +592,9 @@ void ARMul_State::ServeBreak() {
DEBUG_ASSERT(Reg[15] == last_bkpt.address);
}
Kernel::Thread* thread = system.Kernel().GetCurrentThreadManager().GetCurrentThread();
system.GetRunningCore().SaveContext(thread->context);
if (last_bkpt_hit || GDBStub::IsMemoryBreak() || GDBStub::GetCpuStepFlag()) {
if (last_bkpt_hit) {
last_bkpt_hit = false;
GDBStub::Break();
GDBStub::SendTrap(thread, 5);
GDBStub::Break(SIGTRAP);
}
#endif
}

View file

@ -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.
Copyright (C) 1994 Advanced RISC Machines Ltd.
@ -21,7 +25,9 @@
#include <unordered_map>
#include "common/common_types.h"
#include "core/arm/skyeye_common/arm_regformat.h"
#ifdef ENABLE_GDBSTUB
#include "core/gdbstub/gdbstub.h"
#endif
namespace Core {
class System;
@ -199,10 +205,12 @@ public:
return TFlag ? 2 : 4;
}
#ifdef ENABLE_GDBSTUB
void RecordBreak(GDBStub::BreakpointAddress bkpt) {
last_bkpt = bkpt;
last_bkpt_hit = true;
}
#endif
void ServeBreak();
@ -267,6 +275,8 @@ private:
u32 exclusive_tag; // The address for which the local monitor is in exclusive access mode
bool exclusive_state;
#ifdef ENABLE_GDBSTUB
GDBStub::BreakpointAddress last_bkpt{};
bool last_bkpt_hit = false;
#endif
};

View file

@ -26,7 +26,9 @@
#include "core/dumping/backend.h"
#include "core/file_sys/ncch_container.h"
#include "core/frontend/image_interface.h"
#ifdef ENABLE_GDBSTUB
#include "core/gdbstub/gdbstub.h"
#endif
#include "core/global.h"
#include "core/hle/kernel/ipc_debugger/recorder.h"
#include "core/hle/kernel/kernel.h"
@ -83,23 +85,17 @@ System::ResultStatus System::RunLoop(bool tight_loop) {
return ResultStatus::ErrorNotInitialized;
}
#ifdef ENABLE_GDBSTUB
if (GDBStub::IsServerEnabled()) {
Kernel::Thread* thread = kernel->GetCurrentThreadManager().GetCurrentThread();
if (thread && running_core) {
running_core->SaveContext(thread->context);
// The break flag is only set if GDB is connected,
// we can do clearing here safely. If it is ever
// used outside, move the clearing outside the if.
for (auto& cpu_core : cpu_cores) {
cpu_core->ClearBreakFlag();
}
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};
u32 param{};
@ -259,6 +255,8 @@ System::ResultStatus System::RunLoop(bool tight_loop) {
cpu_core->GetTimer().Idle();
PrepareReschedule();
} 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) {
cpu_core->Run();
} else {
@ -269,10 +267,6 @@ System::ResultStatus System::RunLoop(bool tight_loop) {
}
}
if (GDBStub::IsServerEnabled()) {
GDBStub::SetCpuStepFlag(false);
}
Reschedule();
return status;
@ -571,7 +565,9 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window,
app_loader->ReadProgramId(loading_title_id);
HW::AES::InitKeys();
Service::Init(*this, loading_title_id, lle_modules, !app_loader->DoingInitialSetup());
#ifdef ENABLE_GDBSTUB
GDBStub::DeferStart();
#endif
if (!registered_image_interface) {
registered_image_interface = std::make_shared<Frontend::ImageInterface>();
@ -696,7 +692,9 @@ void System::Shutdown(bool is_deserializing) {
gpu.reset();
if (!is_deserializing) {
lle_modules.clear();
#ifdef ENABLE_GDBSTUB
GDBStub::Shutdown();
#endif
perf_stats.reset();
app_loader.reset();
}
@ -759,8 +757,10 @@ void System::Reset() {
}
void System::ApplySettings() {
#ifdef ENABLE_GDBSTUB
GDBStub::SetServerPort(Settings::values.gdbstub_port.GetValue());
GDBStub::ToggleServer(Settings::values.use_gdbstub.GetValue());
#endif
if (gpu) {
#ifndef ANDROID

View file

@ -103,6 +103,8 @@ public:
ErrorSavestate, ///< Error saving or loading
ErrorArticDisconnected, ///< Error when artic base disconnects
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
ErrorUnknown ///< Any other error
};
@ -403,6 +405,18 @@ public:
info_led_color_changed = func;
}
void SetDebugNextProcessFlag() {
debug_next_process = true;
}
bool GetDebugNextProcessFlag() {
return debug_next_process;
}
void ClearDebugNextProcessFlag() {
debug_next_process = false;
}
private:
/**
* Initialize the emulated system.
@ -512,6 +526,8 @@ private:
Common::Vec3<u8> info_led_color;
std::function<void()> info_led_color_changed;
bool debug_next_process;
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive& ar, const unsigned int file_version);

File diff suppressed because it is too large Load diff

View file

@ -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
// Licensed under GPLv2+
// Refer to the license.txt file included.
@ -10,6 +14,10 @@
#include "common/common_types.h"
#include "core/hle/kernel/thread.h"
#ifndef ENABLE_GDBSTUB
#error "File was included with GDB stub support disabled"
#endif
namespace Core {
class System;
}
@ -60,18 +68,28 @@ void Shutdown();
/// Checks if the gdbstub server is enabled.
bool IsServerEnabled();
/// Returns true if the GDB server is initialized
bool IsInitialized();
/// Returns true if there is an active socket connection.
bool IsConnected();
/**
* 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.
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.
*
* @param addr Address of breakpoint.
* @param access_len Access size in bytes.
* @param type Type of breakpoint.
*/
bool CheckBreakpoint(VAddr addr, GDBStub::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);
bool CheckBreakpoint(VAddr addr, u32 access_len, BreakpointType type);
/**
* Send reply to gdb client.

View file

@ -1,13 +1,22 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <csignal>
#include <fmt/ranges.h>
#include "common/string_util.h"
#include "core/core.h"
#include "core/gdbstub/gdbstub.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 {
@ -23,9 +32,6 @@ enum class Status {
static std::atomic<Status> request_status{Status::NoRequest};
static std::atomic<bool> was_halted = false;
static std::atomic<bool> was_stepping = false;
} // namespace
/**
@ -54,7 +60,7 @@ static void SendErrorReply(int error_code, int retval = -1) {
SendReply(packet.data());
}
void SetHioRequest(Core::System& system, const VAddr addr) {
void SetHioRequest(Core::System& system, Kernel::Process* process, const VAddr addr) {
if (!IsServerEnabled()) {
LOG_WARNING(Debug_GDBStub, "HIO requested but GDB stub is not running");
return;
@ -70,14 +76,13 @@ void SetHioRequest(Core::System& system, const VAddr addr) {
}
auto& memory = system.Memory();
const auto process = system.Kernel().GetCurrentProcess();
if (!memory.IsValidVirtualAddress(*process, addr)) {
LOG_WARNING(Debug_GDBStub, "Invalid address for HIO request");
return;
}
memory.ReadBlock(addr, &current_hio_request, sizeof(PackedGdbHioRequest));
memory.ReadBlock(*process, addr, &current_hio_request, sizeof(PackedGdbHioRequest));
if (current_hio_request.magic != std::array{'G', 'D', 'B', '\0'}) {
std::string_view bad_magic{
@ -97,18 +102,13 @@ void SetHioRequest(Core::System& system, const VAddr addr) {
current_hio_request_addr = addr;
request_status = Status::NotSent;
was_halted = GetCpuHaltFlag();
was_stepping = GetCpuStepFlag();
// 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
Break();
SetCpuHaltFlag(true);
SetCpuStepFlag(false);
Break(SIGTRAP);
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) {
if (!IsWaitingForHioReply()) {
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.ctrl_c);
const auto process = system.Kernel().GetCurrentProcess();
auto& memory = system.Memory();
// 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;
}
memory.WriteBlock(current_hio_request_addr, &current_hio_request, sizeof(PackedGdbHioRequest));
memory.WriteBlock(*process, current_hio_request_addr, &current_hio_request,
sizeof(PackedGdbHioRequest));
current_hio_request = {};
current_hio_request_addr = 0;
request_status = Status::NoRequest;
// Restore state from before the request came in
SetCpuStepFlag(was_stepping);
SetCpuHaltFlag(was_halted);
system.GetRunningCore().ClearInstructionCache();
}

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -6,10 +6,18 @@
#include "common/common_types.h"
#ifndef ENABLE_GDBSTUB
#error "File was included with GDB stub support disabled"
#endif
namespace Core {
class System;
}
namespace Kernel {
class Process;
}
namespace GDBStub {
/**
@ -51,7 +59,7 @@ static_assert(sizeof(PackedGdbHioRequest) == 152,
*
* @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.
@ -63,6 +71,7 @@ bool HandlePendingHioRequestPacket();
/**
* 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

View file

@ -16,6 +16,9 @@
#include "common/logging/log.h"
#include "common/serialization/boost_vector.hpp"
#include "core/core.h"
#ifdef ENABLE_GDBSTUB
#include "core/gdbstub/gdbstub.h"
#endif
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/memory.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);
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() {
#ifdef ENABLE_GDBSTUB
GDBStub::OnProcessExit(process_id);
#endif
auto plgldr = Service::PLGLDR::GetService(Core::System::GetInstance());
if (plgldr) {
plgldr->OnProcessExit(*this, kernel);
@ -592,6 +611,42 @@ Result Process::Unmap(VAddr target, VAddr source, u32 size, VMAPermission perms,
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() {
if (memory_region == nullptr || resource_limit == nullptr) {
return;

View file

@ -1,4 +1,4 @@
// Copyright 2015 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -226,6 +226,10 @@ public:
Result Unmap(VAddr target, VAddr source, u32 size, VMAPermission perms,
bool privileged = false);
std::vector<std::shared_ptr<Kernel::Thread>> GetThreadList();
void SetDebugBreak(bool debug_break, std::vector<u32> thread_ids = {});
private:
void FreeAllMemory();

View file

@ -14,7 +14,9 @@
#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/core_timing.h"
#ifdef ENABLE_GDBSTUB
#include "core/gdbstub/hio.h"
#endif
#include "core/hle/kernel/address_arbiter.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
@ -1171,7 +1173,9 @@ void SVC::OutputDebugString(VAddr address, s32 len) {
}
if (len == 0) {
GDBStub::SetHioRequest(system, address);
#ifdef ENABLE_GDBSTUB
GDBStub::SetHioRequest(system, kernel.GetCurrentProcess().get(), address);
#endif
return;
}

View file

@ -17,6 +17,9 @@
#include "core/arm/arm_interface.h"
#include "core/arm/skyeye_common/armstate.h"
#include "core/core.h"
#ifdef ENABLE_GDBSTUB
#include "core/gdbstub/gdbstub.h"
#endif
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/kernel.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 & debug_break;
}
SERIALIZE_IMPL(Thread)
@ -133,6 +137,10 @@ void Thread::Stop() {
process->tls_slots[tls_page].reset(tls_slot);
process->resource_limit->Release(ResourceLimitType::Thread, 1);
}
#ifdef ENABLE_GDBSTUB
GDBStub::OnThreadExit(thread_id);
#endif
}
void ThreadManager::SwitchContext(Thread* new_thread) {
@ -191,7 +199,7 @@ Thread* ThreadManager::PopNextReadyThread() {
u32 next_priority{};
next = nullptr;
if (thread && thread->status == ThreadStatus::Running) {
if (thread && thread->status == ThreadStatus::Running && thread->CanSchedule()) {
do {
// We have to do better than the current thread.
// This call returns null when that's not possible.
@ -201,18 +209,18 @@ Thread* ThreadManager::PopNextReadyThread() {
// Otherwise just keep going with the current thread
next = thread;
break;
} else if (!next->can_schedule) {
} else if (!next->CanSchedule()) {
skipped.push_back({next_priority, next});
}
} while (!next->can_schedule);
} while (!next->CanSchedule());
} else {
do {
std::tie(next_priority, next) = ready_queue.pop_first();
if (next && !next->can_schedule) {
if (next && !next->CanSchedule()) {
skipped.push_back({next_priority, next});
}
} while (next && !next->can_schedule);
} while (next && !next->CanSchedule());
}
for (auto it = skipped.rbegin(); it != skipped.rend(); it++) {
@ -537,6 +545,14 @@ VAddr Thread::GetCommandBufferAddress() const {
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() {}
CpuLimiterMulti::CpuLimiterMulti(Kernel::KernelSystem& _kernel) : kernel(_kernel) {}

View file

@ -365,6 +365,15 @@ public:
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{};
u32 thread_id;
@ -410,6 +419,7 @@ public:
private:
ThreadManager& thread_manager;
bool debug_break{};
friend class boost::serialization::access;
template <class Archive>

View file

@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <array>
#include <csignal>
#include <cstring>
#include <boost/serialization/array.hpp>
#include <boost/serialization/binary_object.hpp>
@ -12,10 +13,14 @@
#include "common/atomic_ops.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/optional_helper.h"
#include "common/settings.h"
#include "common/swap.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
#ifdef ENABLE_GDBSTUB
#include "core/gdbstub/gdbstub.h"
#endif
#include "core/global.h"
#include "core/hle/kernel/process.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::N3DS>)
#ifndef SIGTRAP
constexpr u32 SIGTRAP = 5;
#endif
#ifndef SIGSEGV
constexpr u32 SIGSEGV = 11;
#endif
namespace Memory {
void PageTable::Clear() {
@ -187,7 +200,17 @@ public:
std::memcpy(dest_buffer, src_ptr, copy_amount);
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) {
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
FlushMode::Flush);
@ -235,7 +258,17 @@ public:
std::memcpy(dest_ptr, src_buffer, copy_amount);
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) {
RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
FlushMode::Invalidate);
@ -392,6 +425,88 @@ PAddr& Memory::MemorySystem::Plugin3GXFramebufferAddress() {
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,
PageType type) {
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>
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];
if (page_pointer) {
// NOTE: Avoid adding any extra logic to this fast-path block
T value;
std::memcpy(&value, &page_pointer[vaddr & CITRA_PAGE_MASK], sizeof(T));
ReadType value;
std::memcpy(&value, &page_pointer[vaddr & CITRA_PAGE_MASK], read_size);
return value;
}
@ -464,38 +603,78 @@ T MemorySystem::Read(const std::shared_ptr<PageTable>& page_table, const VAddr v
if (vaddr & (1 << 31)) {
PAddr paddr = (vaddr & ~(1 << 31));
if ((paddr & 0xF0000000) == Memory::FCRAM_PADDR) { // Check FCRAM region
T value;
std::memcpy(&value, GetFCRAMPointer(paddr - Memory::FCRAM_PADDR), sizeof(T));
ReadType value;
std::memcpy(&value, GetFCRAMPointer(paddr - Memory::FCRAM_PADDR), read_size);
return value;
} else if ((paddr & 0xF0000000) == 0x10000000 &&
paddr >= Memory::IO_AREA_PADDR) { // Check MMIO region
return impl->system.GPU().ReadReg(static_cast<VAddr>(paddr) - Memory::IO_AREA_PADDR +
0x1EC00000);
return static_cast<ReadType>(impl->system.GPU().ReadReg(
static_cast<VAddr>(paddr) - Memory::IO_AREA_PADDR + 0x1EC00000));
}
}
PageType type = page_table->attributes[vaddr >> CITRA_PAGE_BITS];
switch (type) {
case PageType::Unmapped:
LOG_ERROR(HW_Memory, "unmapped Read{} @ 0x{:08X} at PC 0x{:08X}", sizeof(T) * 8, vaddr,
impl->GetPC());
return 0;
case PageType::Unmapped: {
UnmappedAccess<ReadType>(vaddr, 0, true);
if constexpr (is_optional) {
return std::nullopt;
} else {
return T{};
}
}
case PageType::Memory:
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:08X}", vaddr);
break;
case PageType::RasterizerCachedMemory: {
RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Flush);
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");
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;
}
default:
UNREACHABLE();
}
if constexpr (is_optional) {
return std::nullopt;
} else {
return T{};
}
}
template <typename T>
void MemorySystem::Write(const std::shared_ptr<PageTable>& page_table, const VAddr vaddr,
@ -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];
switch (type) {
case PageType::Unmapped:
LOG_ERROR(HW_Memory, "unmapped Write{} 0x{:08X} @ 0x{:08X} at PC 0x{:08X}",
sizeof(data) * 8, (u32)data, vaddr, impl->GetPC());
(void)UnmappedAccess<T>(vaddr, data, false);
return;
case PageType::Memory:
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:08X}", vaddr);
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);
std::memcpy(GetPointerForRasterizerCache(vaddr), &data, sizeof(T));
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:
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];
switch (type) {
case PageType::Unmapped:
LOG_ERROR(HW_Memory, "unmapped Write{} 0x{:08X} @ 0x{:08X} at PC 0x{:08X}",
sizeof(data) * 8, static_cast<u32>(data), vaddr, impl->GetPC());
(void)UnmappedAccess<T>(vaddr, data, false);
return true;
case PageType::Memory:
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:08X}", vaddr);
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);
const auto volatile_pointer =
reinterpret_cast<volatile T*>(GetPointerForRasterizerCache(vaddr).GetPtr());
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:
UNREACHABLE();
}
@ -582,7 +817,7 @@ bool MemorySystem::IsValidVirtualAddress(const Kernel::Process& process, const V
return true;
}
if (page_table.attributes[vaddr >> CITRA_PAGE_BITS] == PageType::RasterizerCachedMemory) {
if (page_table.attributes[vaddr >> CITRA_PAGE_BITS] != PageType::Unmapped) {
return true;
}
@ -600,7 +835,9 @@ u8* MemorySystem::GetPointer(const VAddr vaddr) {
}
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);
}
@ -615,7 +852,9 @@ const u8* MemorySystem::GetPointer(const VAddr vaddr) const {
}
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);
}
@ -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.
break;
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;
break;
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
// address space, for example, a system module need not have a VRAM mapping.
break;
case PageType::RasterizerCachedMemory: {
page_type = PageType::Memory;
case PageType::RasterizerCachedMemory:
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] =
GetPointerForRasterizerCache(vaddr & ~CITRA_PAGE_MASK);
}
break;
}
default:
@ -815,6 +1063,14 @@ u64 MemorySystem::Read64(const Kernel::Process& process, VAddr 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* dest_buffer, const std::size_t 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);
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),
FlushMode::Invalidate);
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);
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),
FlushMode::Flush);
WriteBlock(dest_process, dest_addr, GetPointerForRasterizerCache(current_vaddr),

View file

@ -5,11 +5,13 @@
#pragma once
#include <array>
#include <cstddef>
#include <optional>
#include <string>
#include <boost/serialization/array.hpp>
#include <boost/serialization/vector.hpp>
#include "common/common_types.h"
#include "common/memory_ref.h"
#include "common/swap.h"
namespace Kernel {
class Process;
@ -34,7 +36,7 @@ constexpr u32 CITRA_PAGE_MASK = CITRA_PAGE_SIZE - 1;
constexpr int CITRA_PAGE_BITS = 12;
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.
Unmapped,
/// 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
/// invalidation
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));
}
const MemoryRef& Ref(std::size_t idx) {
return refs[idx];
}
private:
std::array<u8*, PAGE_TABLE_NUM_ENTRIES> raw;
std::array<MemoryRef, PAGE_TABLE_NUM_ENTRIES> refs;
@ -100,6 +112,22 @@ struct PageTable {
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();
private:
@ -107,6 +135,7 @@ private:
void serialize(Archive& ar, const unsigned int) {
ar & pointers.refs;
ar & attributes;
ar & watchpoint_pages_map;
for (std::size_t i = 0; i < PAGE_TABLE_NUM_ENTRIES; i++) {
pointers.raw[i] = pointers.refs[i].GetPtr();
}
@ -360,6 +389,29 @@ public:
*/
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
* the current process' address space.
@ -649,7 +701,14 @@ public:
/// Returns a reference to the framebuffer address of the currently loaded 3GX plugin.
PAddr& Plugin3GXFramebufferAddress();
void RegisterWatchpoint(const Kernel::Process& process, VAddr addr, u32 size);
void UnregisterWatchpoint(const Kernel::Process& process, VAddr addr, u32 size);
private:
template <typename T>
void UnmappedAccess(const VAddr vaddr, const T value, bool read);
template <typename T>
T Read(const std::shared_ptr<PageTable>& page_table, const VAddr vaddr);