mirror of
https://github.com/azahar-emu/azahar.git
synced 2026-06-08 11:43:40 -04:00
gdb: Revamp the entire GDB implementation and add debug features
This commit is contained in:
parent
9ce755cd07
commit
19fbe53856
29 changed files with 1039 additions and 256 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -3880,6 +3880,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. "
|
||||
|
|
|
|||
|
|
@ -510,6 +510,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 +1097,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) {
|
||||
|
|
|
|||
|
|
@ -112,6 +112,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());
|
||||
|
|
@ -153,6 +155,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();
|
||||
|
|
@ -178,6 +182,7 @@ void ConfigureDebug::SetupPerGameUI() {
|
|||
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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -299,6 +299,16 @@
|
|||
</property>
|
||||
</widget>
|
||||
</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>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
Init();
|
||||
if (Archive::is_loading::value) {
|
||||
Init();
|
||||
}
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -198,12 +198,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 +229,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 +275,7 @@ private:
|
|||
ClearInstructionCache();
|
||||
ar >> timer;
|
||||
ar >> id;
|
||||
ar >> break_flag;
|
||||
std::shared_ptr<Memory::PageTable> page_table{};
|
||||
ar >> 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
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <csignal>
|
||||
#include <cstring>
|
||||
#include <dynarmic/interface/A32/a32.h>
|
||||
#include <dynarmic/interface/optimization_flags.h>
|
||||
|
|
@ -17,6 +18,14 @@
|
|||
#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 +34,21 @@ public:
|
|||
: parent(parent), svc_context(parent.system), memory(parent.memory) {}
|
||||
~DynarmicUserCallbacks() = default;
|
||||
|
||||
std::optional<std::uint32_t> MemoryReadCode(VAddr vaddr) override {
|
||||
constexpr VAddr low_page_limit = 0x1000;
|
||||
|
||||
// This check prevents a common cascading error that results from the
|
||||
// memory system allowing unmapped memory accesses (in some situations,
|
||||
// a vtable is read from an invalid address and execution jumps to the
|
||||
// zero page). On real hw, it's not normally possible to map the zero page.
|
||||
if (vaddr < low_page_limit) [[unlikely]] {
|
||||
LOG_CRITICAL(Debug, "Tried to read code from low address 0x{:08x}", vaddr);
|
||||
return {};
|
||||
}
|
||||
|
||||
return MemoryRead32(vaddr);
|
||||
}
|
||||
|
||||
std::uint8_t MemoryRead8(VAddr vaddr) override {
|
||||
return memory.Read8(vaddr);
|
||||
}
|
||||
|
|
@ -83,9 +107,8 @@ public:
|
|||
break;
|
||||
case Dynarmic::A32::Exception::Breakpoint:
|
||||
if (GDBStub::IsConnected()) {
|
||||
parent.jit->HaltExecution();
|
||||
parent.SetPC(pc);
|
||||
parent.ServeBreak();
|
||||
parent.ServeBreak(SIGTRAP);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
|
@ -99,11 +122,19 @@ public:
|
|||
case Dynarmic::A32::Exception::PreloadInstruction:
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < 16; i++) {
|
||||
LOG_CRITICAL(Debug, "r{:02d} = {:08X}", i, parent.GetReg(i));
|
||||
|
||||
parent.SetPC(pc);
|
||||
if (GDBStub::IsConnected()) {
|
||||
parent.ServeBreak(SIGILL);
|
||||
} else {
|
||||
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})", 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 +169,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 +328,8 @@ 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(int signal) {
|
||||
GDBStub::Break(signal);
|
||||
}
|
||||
|
||||
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
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ protected:
|
|||
std::shared_ptr<Memory::PageTable> GetPageTable() const override;
|
||||
|
||||
private:
|
||||
void ServeBreak();
|
||||
void ServeBreak(int signal);
|
||||
|
||||
friend class DynarmicUserCallbacks;
|
||||
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
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -954,11 +958,9 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) {
|
|||
#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 && \
|
||||
PC == breakpoint_data.address) { \
|
||||
if (GDBStub::IsServerEnabled()) [[unlikely]] { \
|
||||
if (breakpoint_data.type != GDBStub::BreakpointType::None && \
|
||||
PC == breakpoint_data.address) { \
|
||||
cpu->RecordBreak(breakpoint_data); \
|
||||
goto END; \
|
||||
} \
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
// 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"
|
||||
|
|
@ -10,6 +11,10 @@
|
|||
#include "core/core.h"
|
||||
#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 +187,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 +202,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 +211,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 +220,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 +231,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 +238,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);
|
||||
|
||||
|
|
@ -609,12 +588,8 @@ 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()) {
|
||||
if (last_bkpt_hit) {
|
||||
last_bkpt_hit = false;
|
||||
GDBStub::Break();
|
||||
GDBStub::SendTrap(thread, 5);
|
||||
GDBStub::Break(SIGTRAP);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,6 +88,12 @@ System::ResultStatus System::RunLoop(bool tight_loop) {
|
|||
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);
|
||||
}
|
||||
|
||||
|
|
@ -249,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 {
|
||||
|
|
|
|||
|
|
@ -103,8 +103,10 @@ public:
|
|||
ErrorSavestate, ///< Error saving or loading
|
||||
ErrorArticDisconnected, ///< Error when artic base disconnects
|
||||
ErrorN3DSApplication, ///< Error launching New 3DS application in Old 3DS mode
|
||||
ShutdownRequested, ///< Emulated program requested a system shutdown
|
||||
ErrorUnknown ///< Any other error
|
||||
ErrorCoreExceptionRaised, ///< The CPU emulation raised an exception
|
||||
ErrorMemoryExceptionRaised, ///< Unmmaped memory was accessed
|
||||
ShutdownRequested, ///< Emulated program requested a system shutdown
|
||||
ErrorUnknown ///< Any other error
|
||||
};
|
||||
|
||||
explicit System();
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -36,10 +40,13 @@
|
|||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/gdbstub/hio.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
#pragma optimize("", off)
|
||||
|
||||
// Uncomment to log all GDB traffic
|
||||
// #define PRINT_GDB_TRAFFIC
|
||||
#define PRINT_GDB_TRAFFIC
|
||||
|
||||
namespace GDBStub {
|
||||
namespace {
|
||||
|
|
@ -62,6 +69,10 @@ constexpr u32 SIGTERM = 15;
|
|||
constexpr u32 MSG_WAITALL = 8;
|
||||
#endif
|
||||
|
||||
#ifndef SIGSEGV
|
||||
constexpr u32 SIGSEGV = 11;
|
||||
#endif
|
||||
|
||||
constexpr u32 SP_REGISTER = 13;
|
||||
constexpr u32 LR_REGISTER = 14;
|
||||
constexpr u32 PC_REGISTER = 15;
|
||||
|
|
@ -76,6 +87,8 @@ constexpr char target_xml[] =
|
|||
R"(l<?xml version="1.0"?>
|
||||
<!DOCTYPE target SYSTEM "gdb-target.dtd">
|
||||
<target version="1.0">
|
||||
<architecture>arm</architecture>
|
||||
<osabi>3DS</osabi>
|
||||
<feature name="org.gnu.gdb.arm.core">
|
||||
<reg name="r0" bitsize="32"/>
|
||||
<reg name="r1" bitsize="32"/>
|
||||
|
|
@ -129,19 +142,25 @@ u8 command_buffer[GDB_BUFFER_SIZE];
|
|||
u32 command_length;
|
||||
|
||||
u32 latest_signal = 0;
|
||||
bool memory_break = false;
|
||||
|
||||
static Kernel::Thread* current_thread = nullptr;
|
||||
static Kernel::Process* current_process = nullptr;
|
||||
|
||||
// Binding to a port within the reserved ports range (0-1023) requires root permissions,
|
||||
// so default to a port outside of that range.
|
||||
u16 gdbstub_port = 24689;
|
||||
|
||||
bool send_trap = false;
|
||||
constexpr bool supports_extended_mode = true;
|
||||
bool is_extended_mode = false;
|
||||
|
||||
// If set to false, the server will never be started and no
|
||||
// gdbstub-related functions will be executed.
|
||||
std::atomic<bool> server_enabled(false);
|
||||
int accept_socket = -1;
|
||||
int continue_thread = -1;
|
||||
|
||||
static Kernel::Thread* break_thread = nullptr;
|
||||
static int break_signal = 0;
|
||||
|
||||
#ifdef _WIN32
|
||||
WSADATA InitData;
|
||||
|
|
@ -161,16 +180,17 @@ BreakpointMap breakpoints_write;
|
|||
} // Anonymous namespace
|
||||
|
||||
static Kernel::Thread* FindThreadById(int id) {
|
||||
u32 num_cores = Core::GetNumCores();
|
||||
for (u32 i = 0; i < num_cores; ++i) {
|
||||
const auto& threads =
|
||||
Core::System::GetInstance().Kernel().GetThreadManager(i).GetThreadList();
|
||||
for (auto& thread : threads) {
|
||||
if (thread->GetThreadId() == static_cast<u32>(id)) {
|
||||
return thread.get();
|
||||
}
|
||||
if (!current_process) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto thread_list = current_process->GetThreadList();
|
||||
for (auto& thread : thread_list) {
|
||||
if (thread->GetThreadId() == static_cast<u32>(id)) {
|
||||
return thread.get();
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
|
@ -364,12 +384,40 @@ static u64 GdbHexToLong(const u8* src) {
|
|||
return output;
|
||||
}
|
||||
|
||||
static int GetErrno() {
|
||||
#ifdef _WIN32
|
||||
return WSAGetLastError();
|
||||
#else
|
||||
return errno;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool SetNonBlock(int socket, bool nonblock) {
|
||||
#ifdef _WIN32
|
||||
unsigned long nonblocking = nonblock ? 1 : 0;
|
||||
int ret = ioctlsocket(socket, FIONBIO, &nonblocking);
|
||||
if (ret < 0) {
|
||||
LOG_ERROR(Debug_GDBStub, "Failed to set non-blocking gdb socket");
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
int flags = nonblock ? O_NONBLOCK : 0;
|
||||
|
||||
const int ret = ::fcntl(socket, F_SETFL, flags);
|
||||
if (ret < 0) {
|
||||
LOG_ERROR(Debug_GDBStub, "Failed to set non-blocking gdb socket");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Read a byte from the gdb client.
|
||||
static u8 ReadByte() {
|
||||
u8 c;
|
||||
std::size_t received_size = recv(gdbserver_socket, reinterpret_cast<char*>(&c), 1, MSG_WAITALL);
|
||||
if (received_size != 1) {
|
||||
LOG_ERROR(Debug_GDBStub, "recv failed : {}", received_size);
|
||||
LOG_ERROR(Debug_GDBStub, "recv failed : {}", GetErrno());
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
|
|
@ -417,13 +465,15 @@ static void RemoveBreakpoint(BreakpointType type, VAddr addr) {
|
|||
bp->second.len, bp->second.addr, type);
|
||||
|
||||
if (type == BreakpointType::Execute) {
|
||||
Core::System::GetInstance().Memory().WriteBlock(
|
||||
*Core::System::GetInstance().Kernel().GetCurrentProcess(), bp->second.addr,
|
||||
bp->second.inst.data(), bp->second.inst.size());
|
||||
Core::System::GetInstance().Memory().WriteBlock(*current_process, bp->second.addr,
|
||||
bp->second.inst.data(), bp->second.len);
|
||||
u32 num_cores = Core::GetNumCores();
|
||||
for (u32 i = 0; i < num_cores; ++i) {
|
||||
Core::GetCore(i).ClearInstructionCache();
|
||||
}
|
||||
} else {
|
||||
Core::System::GetInstance().Memory().UnregisterWatchpoint(*current_process, bp->second.addr,
|
||||
bp->second.len);
|
||||
}
|
||||
p.erase(addr);
|
||||
}
|
||||
|
|
@ -444,38 +494,58 @@ BreakpointAddress GetNextBreakpointFromAddress(VAddr addr, BreakpointType type)
|
|||
return breakpoint;
|
||||
}
|
||||
|
||||
bool CheckBreakpoint(VAddr addr, BreakpointType type) {
|
||||
bool CheckBreakpoint(VAddr addr, u32 access_len, BreakpointType type) {
|
||||
if (!IsConnected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const BreakpointMap& p = GetBreakpointMap(type);
|
||||
const auto bp = p.find(addr);
|
||||
|
||||
if (bp == p.end()) {
|
||||
return false;
|
||||
}
|
||||
// Access range: [addr, access_end)
|
||||
const VAddr access_end = addr + access_len;
|
||||
|
||||
u32 len = bp->second.len;
|
||||
for (const auto& [base_addr, bp] : p) {
|
||||
if (!bp.active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// IDA Pro defaults to 4-byte breakpoints for all non-hardware breakpoints
|
||||
// no matter if it's a 4-byte or 2-byte instruction. When you execute a
|
||||
// Thumb instruction with a 4-byte breakpoint set, it will set a breakpoint on
|
||||
// two instructions instead of the single instruction you placed the breakpoint
|
||||
// on. So, as a way to make sure that execution breakpoints are only breaking
|
||||
// on the instruction that was specified, set the length of an execution
|
||||
// breakpoint to 1. This should be fine since the CPU should never begin executing
|
||||
// an instruction anywhere except the beginning of the instruction.
|
||||
if (type == BreakpointType::Execute) {
|
||||
len = 1;
|
||||
}
|
||||
u32 bp_len = bp.len;
|
||||
|
||||
if (bp->second.active && (addr >= bp->second.addr && addr < bp->second.addr + len)) {
|
||||
LOG_DEBUG(Debug_GDBStub,
|
||||
"Found breakpoint type {} @ {:08x}, range: {:08x}"
|
||||
" - {:08x} ({:x} bytes)",
|
||||
type, addr, bp->second.addr, bp->second.addr + len, len);
|
||||
return true;
|
||||
// IDA Pro defaults to 4-byte breakpoints for all non-hardware breakpoints
|
||||
// no matter if it's a 4-byte or 2-byte instruction. When you execute a
|
||||
// Thumb instruction with a 4-byte breakpoint set, it will set a breakpoint on
|
||||
// two instructions instead of the single instruction you placed the breakpoint
|
||||
// on. So, as a way to make sure that execution breakpoints are only breaking
|
||||
// on the instruction that was specified, set the length of an execution
|
||||
// breakpoint to 1. This should be fine since the CPU should never begin executing
|
||||
// an instruction anywhere except the beginning of the instruction.
|
||||
if (type == BreakpointType::Execute) {
|
||||
bp_len = 1;
|
||||
}
|
||||
|
||||
// Breakpoint/watchpoint range: [bp.addr, bp_end)
|
||||
const VAddr bp_end = bp.addr + bp_len;
|
||||
|
||||
bool hit = false;
|
||||
|
||||
if (type == BreakpointType::Execute) {
|
||||
// Execute breakpoints should only trigger on exact PC match.
|
||||
hit = (addr == bp.addr);
|
||||
} else {
|
||||
// Range overlap test:
|
||||
// [addr, access_end) overlaps [bp.addr, bp_end)
|
||||
hit = (addr < bp_end) && (bp.addr < access_end);
|
||||
}
|
||||
|
||||
if (hit) {
|
||||
LOG_DEBUG(Debug_GDBStub,
|
||||
"Found breakpoint type {}, "
|
||||
"access range: {:08x} - {:08x}, "
|
||||
"breakpoint range: {:08x} - {:08x}",
|
||||
type, addr, addr, access_end, bp.addr, bp_end);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
@ -503,11 +573,7 @@ void SendReply(const char* reply) {
|
|||
command_length = static_cast<u32>(strlen(reply));
|
||||
|
||||
#ifdef PRINT_GDB_TRAFFIC
|
||||
if (command_length > 0) {
|
||||
LOG_INFO(Debug_GDBStub, "Req: {} {}", *reply, reply + 1);
|
||||
} else {
|
||||
LOG_INFO(Debug_GDBStub, "Req:");
|
||||
}
|
||||
LOG_INFO(Debug_GDBStub, "Res: {}", reply);
|
||||
#endif
|
||||
|
||||
if (command_length + 4 > sizeof(command_buffer)) {
|
||||
|
|
@ -545,60 +611,103 @@ static void HandleQuery() {
|
|||
|
||||
if (strcmp(query, "TStatus") == 0) {
|
||||
SendReply("T0");
|
||||
} else if (strncmp(query, "Attached", strlen("Attached")) == 0) {
|
||||
SendReply("1");
|
||||
} else if (strncmp(query, "Supported", strlen("Supported")) == 0) {
|
||||
// PacketSize needs to be large enough for target xml
|
||||
SendReply("PacketSize=2000;qXfer:features:read+;qXfer:threads:read+");
|
||||
SendReply("PacketSize=2000;qXfer:features:read+;qXfer:osdata:read+;qXfer:threads:read+;"
|
||||
"vContSupported+");
|
||||
} else if (strncmp(query, "Xfer:features:read:target.xml:",
|
||||
strlen("Xfer:features:read:target.xml:")) == 0) {
|
||||
SendReply(target_xml);
|
||||
} else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) {
|
||||
std::string val = "m";
|
||||
u32 num_cores = Core::GetNumCores();
|
||||
for (u32 i = 0; i < num_cores; ++i) {
|
||||
const auto& threads =
|
||||
Core::System::GetInstance().Kernel().GetThreadManager(i).GetThreadList();
|
||||
for (const auto& thread : threads) {
|
||||
val += fmt::format("{:x},", thread->GetThreadId());
|
||||
}
|
||||
if (!current_process) {
|
||||
SendReply("E01");
|
||||
return;
|
||||
}
|
||||
|
||||
auto thread_list = current_process->GetThreadList();
|
||||
std::string val = "m";
|
||||
for (const auto& thread : thread_list) {
|
||||
val += fmt::format("{:x},", thread->GetThreadId());
|
||||
}
|
||||
|
||||
val.pop_back();
|
||||
SendReply(val.c_str());
|
||||
} else if (strncmp(query, "sThreadInfo", strlen("sThreadInfo")) == 0) {
|
||||
SendReply("l");
|
||||
} else if (strncmp(query, "Xfer:osdata:read:processes", strlen("Xfer:osdata:read:processes")) ==
|
||||
0) {
|
||||
std::string buffer;
|
||||
buffer += "l<osdata type=\"processes\">";
|
||||
auto process_list = Core::System::GetInstance().Kernel().GetProcessList();
|
||||
for (const auto& p : process_list) {
|
||||
buffer += fmt::format("<item>"
|
||||
"<column name=\"pid\">{}</column>"
|
||||
"<column name=\"command\">{}</column>"
|
||||
"</item>",
|
||||
p->process_id, p->codeset->name);
|
||||
}
|
||||
buffer += "</osdata>";
|
||||
SendReply(buffer.c_str());
|
||||
} else if (strncmp(query, "Xfer:threads:read", strlen("Xfer:threads:read")) == 0) {
|
||||
if (!current_process) {
|
||||
SendReply("E01");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string buffer;
|
||||
buffer += "l<?xml version=\"1.0\"?>";
|
||||
buffer += "<threads>";
|
||||
u32 num_cores = Core::GetNumCores();
|
||||
for (u32 i = 0; i < num_cores; ++i) {
|
||||
const auto& threads =
|
||||
Core::System::GetInstance().Kernel().GetThreadManager(i).GetThreadList();
|
||||
for (const auto& thread : threads) {
|
||||
buffer += fmt::format(R"*(<thread id="{:x}" name="Thread {:x}"></thread>)*",
|
||||
thread->GetThreadId(), thread->GetThreadId());
|
||||
}
|
||||
auto thread_list = current_process->GetThreadList();
|
||||
for (const auto& thread : thread_list) {
|
||||
buffer += fmt::format(R"*(<thread id="{:x}" name="Thread {:x}"></thread>)*",
|
||||
thread->GetThreadId(), thread->GetThreadId());
|
||||
}
|
||||
buffer += "</threads>";
|
||||
|
||||
SendReply(buffer.c_str());
|
||||
} else {
|
||||
SendReply("");
|
||||
}
|
||||
}
|
||||
static bool SetThread(int thread_id) {
|
||||
if (!current_process) {
|
||||
// The process has not been selected yet
|
||||
return false;
|
||||
}
|
||||
|
||||
if (thread_id >= 1) {
|
||||
current_thread = FindThreadById(thread_id);
|
||||
}
|
||||
if (!current_thread) {
|
||||
auto thread_list = current_process->GetThreadList();
|
||||
if (thread_list.size() > 0) {
|
||||
// Select the lowest thread ID, which is the main thread
|
||||
std::sort(thread_list.begin(), thread_list.end(),
|
||||
[](const std::shared_ptr<Kernel::Thread>& a,
|
||||
const std::shared_ptr<Kernel::Thread>& b) {
|
||||
return a->thread_id < b->thread_id;
|
||||
});
|
||||
current_thread = thread_list[0].get();
|
||||
}
|
||||
}
|
||||
return current_thread != nullptr;
|
||||
}
|
||||
/// Handle set thread command from gdb client.
|
||||
static void HandleSetThread() {
|
||||
int thread_id = -1;
|
||||
if (command_buffer[2] != '-') {
|
||||
thread_id = static_cast<int>(HexToInt(command_buffer + 2, command_length - 2));
|
||||
}
|
||||
if (thread_id >= 1) {
|
||||
current_thread = FindThreadById(thread_id);
|
||||
|
||||
if (command_buffer[1] == 'c') {
|
||||
continue_thread = thread_id;
|
||||
SendReply("OK");
|
||||
return;
|
||||
}
|
||||
if (!current_thread) {
|
||||
thread_id = 1;
|
||||
current_thread = FindThreadById(thread_id);
|
||||
}
|
||||
if (current_thread) {
|
||||
|
||||
if (SetThread(thread_id)) {
|
||||
SendReply("OK");
|
||||
return;
|
||||
}
|
||||
|
|
@ -607,6 +716,11 @@ static void HandleSetThread() {
|
|||
|
||||
/// Handle thread alive command from gdb client.
|
||||
static void HandleThreadAlive() {
|
||||
if (!current_process) {
|
||||
SendReply("E01");
|
||||
return;
|
||||
}
|
||||
|
||||
int thread_id = static_cast<int>(HexToInt(command_buffer + 1, command_length - 1));
|
||||
if (thread_id == 0) {
|
||||
thread_id = 1;
|
||||
|
|
@ -618,6 +732,15 @@ static void HandleThreadAlive() {
|
|||
SendReply("E01");
|
||||
}
|
||||
|
||||
static void HandleExtendedMode() {
|
||||
if (supports_extended_mode) {
|
||||
is_extended_mode = true;
|
||||
SendReply("OK");
|
||||
} else {
|
||||
SendReply("");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send signal packet to client.
|
||||
*
|
||||
|
|
@ -636,11 +759,16 @@ static void SendSignal(Kernel::Thread* thread, u32 signal, bool full = true) {
|
|||
|
||||
std::string buffer;
|
||||
if (full) {
|
||||
|
||||
Core::ARM_Interface::ThreadContext ctx{};
|
||||
if (thread) {
|
||||
ctx = thread->context;
|
||||
} else {
|
||||
Core::GetRunningCore().SaveContext(ctx);
|
||||
}
|
||||
buffer = fmt::format("T{:02x}{:02x}:{:08x};{:02x}:{:08x};{:02x}:{:08x}", latest_signal,
|
||||
PC_REGISTER, htonl(Core::GetRunningCore().GetPC()), SP_REGISTER,
|
||||
htonl(Core::GetRunningCore().GetReg(SP_REGISTER)), LR_REGISTER,
|
||||
htonl(Core::GetRunningCore().GetReg(LR_REGISTER)));
|
||||
PC_REGISTER, htonl(ctx.cpu_registers[PC_REGISTER]), SP_REGISTER,
|
||||
htonl(ctx.cpu_registers[SP_REGISTER]), LR_REGISTER,
|
||||
htonl(ctx.cpu_registers[LR_REGISTER]));
|
||||
} else {
|
||||
buffer = fmt::format("T{:02x}", latest_signal);
|
||||
}
|
||||
|
|
@ -653,6 +781,53 @@ static void SendSignal(Kernel::Thread* thread, u32 signal, bool full = true) {
|
|||
SendReply(buffer.c_str());
|
||||
}
|
||||
|
||||
static void HandleGetStopReason() {
|
||||
if (is_extended_mode) {
|
||||
// In extended mode, tell the debugger that there is no selected process yet.
|
||||
// That way the debugger can ask for the process list and attack to the right PID.
|
||||
if (!current_process) {
|
||||
// The process has not been selected yet.
|
||||
SendReply("W00");
|
||||
} else {
|
||||
SendSignal(current_thread, latest_signal);
|
||||
}
|
||||
} else {
|
||||
// In non extended mode, select the process corresponding to the "main"
|
||||
// launched application.
|
||||
u64 program_id = 0;
|
||||
Core::System::GetInstance().GetAppLoader().ReadProgramId(program_id);
|
||||
auto process_list = Core::System::GetInstance().Kernel().GetProcessList();
|
||||
for (const auto& process : process_list) {
|
||||
if (process->codeset->program_id == program_id) {
|
||||
current_process = process.get();
|
||||
current_process->SetDebugBreak(true);
|
||||
if (SetThread(0)) {
|
||||
SendSignal(current_thread, 0);
|
||||
} else {
|
||||
// Should never happen
|
||||
SendReply("W00");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// No process, should never happen
|
||||
SendReply("W00");
|
||||
}
|
||||
}
|
||||
|
||||
static void BreakImpl(int signal) {
|
||||
if (signal == SIGSEGV) {
|
||||
LOG_WARNING(Debug_GDBStub, "Segmentation fault signals may not be fully accurate");
|
||||
}
|
||||
|
||||
current_process->SetDebugBreak(true);
|
||||
|
||||
latest_signal = signal;
|
||||
|
||||
SendSignal(current_thread, signal);
|
||||
}
|
||||
|
||||
/// Read command from gdb client.
|
||||
static void ReadCommand() {
|
||||
command_length = 0;
|
||||
|
|
@ -664,7 +839,7 @@ static void ReadCommand() {
|
|||
return;
|
||||
} else if (c == 0x03) {
|
||||
LOG_INFO(Debug_GDBStub, "gdb: found break command\n");
|
||||
SendSignal(current_thread, SIGTRAP);
|
||||
BreakImpl(SIGTRAP);
|
||||
return;
|
||||
} else if (c != GDB_STUB_START) {
|
||||
LOG_DEBUG(Debug_GDBStub, "gdb: read invalid byte {:02x}\n", c);
|
||||
|
|
@ -726,6 +901,11 @@ static bool IsDataAvailable() {
|
|||
|
||||
/// Send requested register to gdb client.
|
||||
static void ReadRegister() {
|
||||
if (!current_process || !current_thread) {
|
||||
SendReply("E01");
|
||||
return;
|
||||
}
|
||||
|
||||
static u8 reply[64];
|
||||
std::memset(reply, 0, sizeof(reply));
|
||||
|
||||
|
|
@ -752,6 +932,11 @@ static void ReadRegister() {
|
|||
|
||||
/// Send all registers to the gdb client.
|
||||
static void ReadRegisters() {
|
||||
if (!current_process || !current_thread) {
|
||||
SendReply("E01");
|
||||
return;
|
||||
}
|
||||
|
||||
static u8 buffer[GDB_BUFFER_SIZE - 4];
|
||||
std::memset(buffer, 0, sizeof(buffer));
|
||||
|
||||
|
|
@ -791,6 +976,11 @@ static void UpdateCPUThreadContext() {
|
|||
|
||||
/// Modify data of register specified by gdb client.
|
||||
static void WriteRegister() {
|
||||
if (!current_process || !current_thread) {
|
||||
SendReply("E01");
|
||||
return;
|
||||
}
|
||||
|
||||
const u8* buffer_ptr = command_buffer + 3;
|
||||
|
||||
u32 id = HexCharToValue(command_buffer[1]);
|
||||
|
|
@ -819,6 +1009,11 @@ static void WriteRegister() {
|
|||
|
||||
/// Modify all registers with data received from the client.
|
||||
static void WriteRegisters() {
|
||||
if (!current_process || !current_thread) {
|
||||
SendReply("E01");
|
||||
return;
|
||||
}
|
||||
|
||||
const u8* buffer_ptr = command_buffer + 1;
|
||||
|
||||
if (command_buffer[0] != 'G')
|
||||
|
|
@ -849,6 +1044,11 @@ static void WriteRegisters() {
|
|||
|
||||
/// Read location in memory specified by gdb client.
|
||||
static void ReadMemory() {
|
||||
if (!current_process) {
|
||||
SendReply("E01");
|
||||
return;
|
||||
}
|
||||
|
||||
static u8 reply[GDB_BUFFER_SIZE - 4];
|
||||
|
||||
auto start_offset = command_buffer + 1;
|
||||
|
|
@ -866,13 +1066,12 @@ static void ReadMemory() {
|
|||
}
|
||||
|
||||
auto& memory = Core::System::GetInstance().Memory();
|
||||
if (!memory.IsValidVirtualAddress(*Core::System::GetInstance().Kernel().GetCurrentProcess(),
|
||||
addr)) {
|
||||
return SendReply("E00");
|
||||
if (!memory.IsValidVirtualAddress(*current_process, addr)) {
|
||||
return SendReply("E14");
|
||||
}
|
||||
|
||||
std::vector<u8> data(len);
|
||||
memory.ReadBlock(addr, data.data(), len);
|
||||
memory.ReadBlock(*current_process, addr, data.data(), len);
|
||||
|
||||
MemToGdbHex(reply, data.data(), len);
|
||||
reply[len * 2] = '\0';
|
||||
|
|
@ -885,6 +1084,11 @@ static void ReadMemory() {
|
|||
|
||||
/// Modify location in memory with data received from the gdb client.
|
||||
static void WriteMemory() {
|
||||
if (!current_process) {
|
||||
SendReply("E01");
|
||||
return;
|
||||
}
|
||||
|
||||
auto start_offset = command_buffer + 1;
|
||||
auto addr_pos = std::find(start_offset, command_buffer + command_length, ',');
|
||||
VAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset));
|
||||
|
|
@ -894,36 +1098,67 @@ static void WriteMemory() {
|
|||
u32 len = HexToInt(start_offset, static_cast<u32>(len_pos - start_offset));
|
||||
|
||||
auto& memory = Core::System::GetInstance().Memory();
|
||||
if (!memory.IsValidVirtualAddress(*Core::System::GetInstance().Kernel().GetCurrentProcess(),
|
||||
addr)) {
|
||||
if (!memory.IsValidVirtualAddress(*current_process, addr)) {
|
||||
return SendReply("E00");
|
||||
}
|
||||
|
||||
std::vector<u8> data(len);
|
||||
|
||||
GdbHexToMem(data.data(), len_pos + 1, len);
|
||||
memory.WriteBlock(addr, data.data(), len);
|
||||
memory.WriteBlock(*current_process, addr, data.data(), len);
|
||||
ClearAllInstructionCache();
|
||||
SendReply("OK");
|
||||
}
|
||||
|
||||
void Break(bool is_memory_break) {
|
||||
send_trap = true;
|
||||
|
||||
memory_break = is_memory_break;
|
||||
}
|
||||
|
||||
bool IsMemoryBreak() {
|
||||
if (!IsConnected()) {
|
||||
return false;
|
||||
void Break(int signal) {
|
||||
if (!IsConnected() || !current_process ||
|
||||
Core::System::GetInstance().Kernel().GetCurrentProcess().get() != current_process) {
|
||||
LOG_ERROR(Debug_GDBStub, "Got signal for un-attached process, ignoring...");
|
||||
return;
|
||||
}
|
||||
|
||||
return memory_break;
|
||||
if (break_thread) {
|
||||
LOG_ERROR(Debug_GDBStub,
|
||||
"Got multiple break signals in quick succession, latest may be lost");
|
||||
return;
|
||||
}
|
||||
|
||||
break_thread =
|
||||
Core::System::GetInstance().Kernel().GetCurrentThreadManager().GetCurrentThread();
|
||||
if (break_thread) {
|
||||
break_signal = signal;
|
||||
}
|
||||
|
||||
// Try to break CPU asap
|
||||
Core::GetRunningCore().SetBreakFlag();
|
||||
Core::GetRunningCore().ClearInstructionCache();
|
||||
Core::GetRunningCore().PrepareReschedule();
|
||||
}
|
||||
|
||||
/// Tell the CPU to continue executing.
|
||||
static void Continue() {
|
||||
memory_break = false;
|
||||
if (!current_process) {
|
||||
return;
|
||||
}
|
||||
|
||||
u32 thread_id = -1;
|
||||
if (continue_thread == 0) {
|
||||
thread_id = current_process->GetThreadList()[0]->thread_id;
|
||||
} else {
|
||||
thread_id = continue_thread;
|
||||
}
|
||||
|
||||
// There is no documentation anywhere if continue should
|
||||
// reset the continue thread value. Luma3DS implementation
|
||||
// does this, and looks like IDA Pro expects it that way too.
|
||||
continue_thread = -1;
|
||||
|
||||
std::vector<u32> continue_list{};
|
||||
if (thread_id != -1) {
|
||||
continue_list.push_back(thread_id);
|
||||
}
|
||||
|
||||
current_process->SetDebugBreak(false, continue_list);
|
||||
ClearAllInstructionCache();
|
||||
}
|
||||
|
||||
|
|
@ -937,20 +1172,26 @@ static void Continue() {
|
|||
static bool CommitBreakpoint(BreakpointType type, VAddr addr, u32 len) {
|
||||
BreakpointMap& p = GetBreakpointMap(type);
|
||||
|
||||
if (type == BreakpointType::Execute && len != 2 && len != 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Breakpoint breakpoint;
|
||||
breakpoint.active = true;
|
||||
breakpoint.addr = addr;
|
||||
breakpoint.len = len;
|
||||
Core::System::GetInstance().Memory().ReadBlock(
|
||||
*Core::System::GetInstance().Kernel().GetCurrentProcess(), addr, breakpoint.inst.data(),
|
||||
breakpoint.inst.size());
|
||||
Core::System::GetInstance().Memory().ReadBlock(*current_process, addr, breakpoint.inst.data(),
|
||||
len);
|
||||
|
||||
static constexpr std::array<u8, 4> btrap{0x70, 0x00, 0x20, 0xe1};
|
||||
static constexpr std::array<u8, 2> btrap_thumb{0x00, 0xBE};
|
||||
|
||||
if (type == BreakpointType::Execute) {
|
||||
Core::System::GetInstance().Memory().WriteBlock(
|
||||
*Core::System::GetInstance().Kernel().GetCurrentProcess(), addr, btrap.data(),
|
||||
btrap.size());
|
||||
*current_process, addr, (len == 2) ? btrap_thumb.data() : btrap.data(), len);
|
||||
ClearAllInstructionCache();
|
||||
} else {
|
||||
Core::System::GetInstance().Memory().RegisterWatchpoint(*current_process, addr, len);
|
||||
}
|
||||
p.insert({addr, breakpoint});
|
||||
|
||||
|
|
@ -962,6 +1203,11 @@ static bool CommitBreakpoint(BreakpointType type, VAddr addr, u32 len) {
|
|||
|
||||
/// Handle add breakpoint command from gdb client.
|
||||
static void AddBreakpoint() {
|
||||
if (!current_process) {
|
||||
SendReply("E01");
|
||||
return;
|
||||
}
|
||||
|
||||
BreakpointType type;
|
||||
|
||||
u8 type_id = HexCharToValue(command_buffer[1]);
|
||||
|
|
@ -1011,6 +1257,11 @@ static void AddBreakpoint() {
|
|||
|
||||
/// Handle remove breakpoint command from gdb client.
|
||||
static void RemoveBreakpoint() {
|
||||
if (!current_process) {
|
||||
SendReply("E01");
|
||||
return;
|
||||
}
|
||||
|
||||
BreakpointType type;
|
||||
|
||||
u8 type_id = HexCharToValue(command_buffer[1]);
|
||||
|
|
@ -1048,11 +1299,122 @@ static void RemoveBreakpoint() {
|
|||
SendReply("OK");
|
||||
}
|
||||
|
||||
void HandleVCommand() {
|
||||
std::string_view cmd_view((const char*)command_buffer, command_length);
|
||||
|
||||
if (cmd_view.size() <= 1) {
|
||||
SendReply("E01");
|
||||
return;
|
||||
}
|
||||
size_t delimiter_pos = cmd_view.find(';');
|
||||
std::string_view command =
|
||||
cmd_view.substr(1, delimiter_pos == cmd_view.npos ? cmd_view.npos : delimiter_pos);
|
||||
if (command == "Attach;") {
|
||||
if (!is_extended_mode) {
|
||||
SendReply("E01");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string_view arg = cmd_view.substr(delimiter_pos + 1);
|
||||
u32 pid = HexToInt(reinterpret_cast<const u8*>(arg.data()), arg.size());
|
||||
auto process = Core::System::GetInstance().Kernel().GetProcessById(pid);
|
||||
if (!process) {
|
||||
SendReply("E02");
|
||||
} else {
|
||||
current_process = process.get();
|
||||
current_process->SetDebugBreak(true);
|
||||
if (SetThread(0)) {
|
||||
SendSignal(current_thread, 0);
|
||||
} else {
|
||||
// Should never happen
|
||||
SendReply("W00");
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else if (command == "Cont?") {
|
||||
SendReply("vCont;c;C");
|
||||
} else if (command == "Cont;") {
|
||||
if (!current_process) {
|
||||
SendReply("E01");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string_view arg = cmd_view.substr(delimiter_pos + 1);
|
||||
auto actions = Common::SplitString(arg, ';');
|
||||
|
||||
if (actions.empty()) {
|
||||
SendReply("E01");
|
||||
return;
|
||||
}
|
||||
for (auto& action : actions) {
|
||||
auto threads = Common::SplitString(action, ':');
|
||||
if (threads.empty()) {
|
||||
SendReply("E01");
|
||||
return;
|
||||
}
|
||||
char action_type = threads[0][0];
|
||||
if (action_type != 'c') {
|
||||
SendReply("E01");
|
||||
return;
|
||||
}
|
||||
std::vector<u32> thread_ids;
|
||||
for (size_t i = 1; i < threads.size(); i++) {
|
||||
thread_ids.push_back(
|
||||
HexToInt(reinterpret_cast<const u8*>(threads[i].c_str()), threads[i].size()));
|
||||
}
|
||||
|
||||
current_process->SetDebugBreak(false, thread_ids);
|
||||
}
|
||||
} else {
|
||||
SendReply("");
|
||||
}
|
||||
}
|
||||
|
||||
void HandlePacket(Core::System& system) {
|
||||
|
||||
if (!IsConnected()) {
|
||||
if (defer_start) {
|
||||
ToggleServer(true);
|
||||
defer_start = false;
|
||||
}
|
||||
|
||||
// Handle accept new GDB connection
|
||||
if (accept_socket != -1) {
|
||||
sockaddr_in saddr_client;
|
||||
sockaddr* client_addr = reinterpret_cast<sockaddr*>(&saddr_client);
|
||||
socklen_t client_addrlen = sizeof(saddr_client);
|
||||
gdbserver_socket =
|
||||
static_cast<int>(accept(accept_socket, client_addr, &client_addrlen));
|
||||
if (gdbserver_socket < 0) {
|
||||
#ifdef _WIN32
|
||||
if (GetErrno() == WSAEWOULDBLOCK) {
|
||||
// Nothing connected yet
|
||||
return;
|
||||
}
|
||||
#else
|
||||
if (GetErrno() == EAGAIN || GetErrno() == EWOULDBLOCK) {
|
||||
// Nothing connected yet
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
LOG_ERROR(Debug_GDBStub, "Failed to accept gdb client");
|
||||
} else {
|
||||
LOG_INFO(Debug_GDBStub, "Client connected.\n");
|
||||
SetNonBlock(gdbserver_socket, false);
|
||||
}
|
||||
|
||||
shutdown(accept_socket, SHUT_RDWR);
|
||||
accept_socket = -1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (break_thread) {
|
||||
current_thread = break_thread;
|
||||
break_thread = nullptr;
|
||||
int signal = break_signal;
|
||||
break_signal = 0;
|
||||
BreakImpl(signal);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1072,7 +1434,7 @@ void HandlePacket(Core::System& system) {
|
|||
|
||||
#ifdef PRINT_GDB_TRAFFIC
|
||||
std::string cmd_str(command_buffer + 1, command_buffer + command_length);
|
||||
LOG_INFO(Debug_GDBStub, "Res: {:c} {}", command_buffer[0], cmd_str);
|
||||
LOG_INFO(Debug_GDBStub, "Req: {:c} {}", command_buffer[0], cmd_str);
|
||||
#endif
|
||||
|
||||
LOG_DEBUG(Debug_GDBStub, "Packet: {0:d} ('{0:c}')", command_buffer[0]);
|
||||
|
|
@ -1085,16 +1447,28 @@ void HandlePacket(Core::System& system) {
|
|||
HandleSetThread();
|
||||
break;
|
||||
case '?':
|
||||
SendSignal(current_thread, latest_signal);
|
||||
HandleGetStopReason();
|
||||
break;
|
||||
case '!':
|
||||
HandleExtendedMode();
|
||||
break;
|
||||
case 'D':
|
||||
SendReply("OK");
|
||||
ToggleServer(false);
|
||||
// Continue execution
|
||||
continue_thread = -1;
|
||||
Continue();
|
||||
break;
|
||||
case 'k':
|
||||
LOG_INFO(Debug_GDBStub, "killed by gdb");
|
||||
ToggleServer(false);
|
||||
// Continue execution so we don't hang forever after shutting down the server
|
||||
// Continue execution and stop emulation
|
||||
continue_thread = -1;
|
||||
Continue();
|
||||
system.RequestShutdown();
|
||||
return;
|
||||
case 'F':
|
||||
HandleHioReply(system, command_buffer, command_length);
|
||||
HandleHioReply(system, current_process, command_buffer, command_length);
|
||||
break;
|
||||
case 'g':
|
||||
ReadRegisters();
|
||||
|
|
@ -1115,7 +1489,7 @@ void HandlePacket(Core::System& system) {
|
|||
WriteMemory();
|
||||
break;
|
||||
case 's':
|
||||
// Single step, return ENOTSUP
|
||||
// Single step not supported, return ENOTSUP
|
||||
SendReply("E5F");
|
||||
return;
|
||||
case 'C':
|
||||
|
|
@ -1131,6 +1505,9 @@ void HandlePacket(Core::System& system) {
|
|||
case 'T':
|
||||
HandleThreadAlive();
|
||||
break;
|
||||
case 'v':
|
||||
HandleVCommand();
|
||||
break;
|
||||
default:
|
||||
SendReply("");
|
||||
break;
|
||||
|
|
@ -1146,12 +1523,12 @@ void ToggleServer(bool status) {
|
|||
server_enabled = status;
|
||||
|
||||
// Start server
|
||||
if (!IsConnected() && Core::System::GetInstance().IsPoweredOn()) {
|
||||
if (!IsInitialized() && Core::System::GetInstance().IsPoweredOn()) {
|
||||
Init();
|
||||
}
|
||||
} else {
|
||||
// Stop server
|
||||
if (IsConnected()) {
|
||||
if (IsInitialized()) {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
|
|
@ -1184,47 +1561,32 @@ static void Init(u16 port) {
|
|||
WSAStartup(MAKEWORD(2, 2), &InitData);
|
||||
#endif
|
||||
|
||||
int tmpsock = static_cast<int>(socket(PF_INET, SOCK_STREAM, 0));
|
||||
if (tmpsock == -1) {
|
||||
accept_socket = static_cast<int>(socket(PF_INET, SOCK_STREAM, 0));
|
||||
if (accept_socket == -1) {
|
||||
LOG_ERROR(Debug_GDBStub, "Failed to create gdb socket");
|
||||
}
|
||||
|
||||
// Set socket to SO_REUSEADDR so it can always bind on the same port
|
||||
int reuse_enabled = 1;
|
||||
if (setsockopt(tmpsock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse_enabled,
|
||||
if (setsockopt(accept_socket, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse_enabled,
|
||||
sizeof(reuse_enabled)) < 0) {
|
||||
LOG_ERROR(Debug_GDBStub, "Failed to set gdb socket option");
|
||||
}
|
||||
|
||||
const sockaddr* server_addr = reinterpret_cast<const sockaddr*>(&saddr_server);
|
||||
socklen_t server_addrlen = sizeof(saddr_server);
|
||||
if (bind(tmpsock, server_addr, server_addrlen) < 0) {
|
||||
if (bind(accept_socket, server_addr, server_addrlen) < 0) {
|
||||
LOG_ERROR(Debug_GDBStub, "Failed to bind gdb socket");
|
||||
}
|
||||
|
||||
if (listen(tmpsock, 1) < 0) {
|
||||
if (listen(accept_socket, 1) < 0) {
|
||||
LOG_ERROR(Debug_GDBStub, "Failed to listen to gdb socket");
|
||||
}
|
||||
|
||||
SetNonBlock(accept_socket, true);
|
||||
|
||||
// Wait for gdb to connect
|
||||
LOG_INFO(Debug_GDBStub, "Waiting for gdb to connect...\n");
|
||||
sockaddr_in saddr_client;
|
||||
sockaddr* client_addr = reinterpret_cast<sockaddr*>(&saddr_client);
|
||||
socklen_t client_addrlen = sizeof(saddr_client);
|
||||
gdbserver_socket = static_cast<int>(accept(tmpsock, client_addr, &client_addrlen));
|
||||
if (gdbserver_socket < 0) {
|
||||
// In the case that we couldn't start the server for whatever reason, just start CPU
|
||||
// execution like normal.
|
||||
LOG_ERROR(Debug_GDBStub, "Failed to accept gdb client");
|
||||
} else {
|
||||
LOG_INFO(Debug_GDBStub, "Client connected.\n");
|
||||
saddr_client.sin_addr.s_addr = ntohl(saddr_client.sin_addr.s_addr);
|
||||
}
|
||||
|
||||
// Clean up temporary socket if it's still alive at this point.
|
||||
if (tmpsock != -1) {
|
||||
shutdown(tmpsock, SHUT_RDWR);
|
||||
}
|
||||
}
|
||||
|
||||
void Init() {
|
||||
|
|
@ -1236,6 +1598,7 @@ void Shutdown() {
|
|||
return;
|
||||
}
|
||||
defer_start = false;
|
||||
is_extended_mode = false;
|
||||
|
||||
LOG_INFO(Debug_GDBStub, "Stopping GDB ...");
|
||||
if (gdbserver_socket != -1) {
|
||||
|
|
@ -1243,6 +1606,11 @@ void Shutdown() {
|
|||
gdbserver_socket = -1;
|
||||
}
|
||||
|
||||
if (accept_socket != -1) {
|
||||
shutdown(accept_socket, SHUT_RDWR);
|
||||
accept_socket = -1;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
|
|
@ -1254,19 +1622,11 @@ bool IsServerEnabled() {
|
|||
return server_enabled;
|
||||
}
|
||||
|
||||
bool IsInitialized() {
|
||||
return IsServerEnabled() && (accept_socket != -1 || gdbserver_socket != -1);
|
||||
}
|
||||
|
||||
bool IsConnected() {
|
||||
return IsServerEnabled() && gdbserver_socket != -1;
|
||||
}
|
||||
|
||||
void SendTrap(Kernel::Thread* thread, int trap) {
|
||||
if (!send_trap) {
|
||||
return;
|
||||
}
|
||||
|
||||
current_thread = thread;
|
||||
|
||||
SendSignal(thread, trap);
|
||||
|
||||
send_trap = false;
|
||||
}
|
||||
}; // namespace GDBStub
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -60,18 +64,18 @@ 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);
|
||||
|
||||
/// Determine if there was a memory breakpoint.
|
||||
bool IsMemoryBreak();
|
||||
void Break(int signal);
|
||||
|
||||
/// Read and handle packet from gdb client.
|
||||
void HandlePacket(Core::System& system);
|
||||
|
|
@ -88,17 +92,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);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
|
|
|||
|
|
@ -1,13 +1,18 @@
|
|||
// 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 SIGTRAP
|
||||
constexpr u32 SIGTRAP = 5;
|
||||
#endif
|
||||
|
||||
namespace GDBStub {
|
||||
|
||||
namespace {
|
||||
|
|
@ -51,7 +56,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;
|
||||
|
|
@ -67,14 +72,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, ¤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'}) {
|
||||
std::string_view bad_magic{
|
||||
|
|
@ -96,11 +100,11 @@ void SetHioRequest(Core::System& system, const VAddr addr) {
|
|||
|
||||
// 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();
|
||||
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");
|
||||
|
|
@ -169,7 +173,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,
|
||||
|
|
@ -180,7 +183,8 @@ void HandleHioReply(Core::System& system, const u8* const command_buffer,
|
|||
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_addr = 0;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -10,6 +10,10 @@ namespace Core {
|
|||
class System;
|
||||
}
|
||||
|
||||
namespace Kernel {
|
||||
class Process;
|
||||
}
|
||||
|
||||
namespace GDBStub {
|
||||
|
||||
/**
|
||||
|
|
@ -51,7 +55,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 +67,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
|
||||
|
|
|
|||
|
|
@ -592,6 +592,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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -1171,7 +1171,7 @@ void SVC::OutputDebugString(VAddr address, s32 len) {
|
|||
}
|
||||
|
||||
if (len == 0) {
|
||||
GDBStub::SetHioRequest(system, address);
|
||||
GDBStub::SetHioRequest(system, kernel.GetCurrentProcess().get(), address);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ void Thread::serialize(Archive& ar, const unsigned int file_version) {
|
|||
}
|
||||
}
|
||||
ar & wakeup_callback;
|
||||
ar & debug_break;
|
||||
}
|
||||
SERIALIZE_IMPL(Thread)
|
||||
|
||||
|
|
@ -191,7 +192,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 +202,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 +538,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) {}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -16,6 +17,7 @@
|
|||
#include "common/swap.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/core.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/global.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/plgldr/plgldr.h"
|
||||
|
|
@ -28,6 +30,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 +197,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 +255,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 +422,90 @@ PAddr& Memory::MemorySystem::Plugin3GXFramebufferAddress() {
|
|||
return impl->plugin_fb_address;
|
||||
}
|
||||
|
||||
#pragma optimize("", off)
|
||||
|
||||
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,6 +563,23 @@ void MemorySystem::UnregisterPageTable(std::shared_ptr<PageTable> page_table) {
|
|||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T 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());
|
||||
if (GDBStub::IsConnected()) {
|
||||
GDBStub::Break(SIGSEGV);
|
||||
} else if (Settings::values.break_on_unmapped_memory_access) {
|
||||
impl->system.SetStatus(Core::System::ResultStatus::ErrorMemoryExceptionRaised,
|
||||
message.c_str());
|
||||
}
|
||||
|
||||
LOG_ERROR(HW_Memory, "{}", message);
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T MemorySystem::Read(const std::shared_ptr<PageTable>& page_table, const VAddr vaddr) {
|
||||
const u8* page_pointer = page_table->pointers[vaddr >> CITRA_PAGE_BITS];
|
||||
|
|
@ -476,20 +607,45 @@ T MemorySystem::Read(const std::shared_ptr<PageTable>& page_table, const VAddr v
|
|||
|
||||
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: {
|
||||
return UnmappedAccess<T>(vaddr, 0, true);
|
||||
}
|
||||
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");
|
||||
|
||||
T value;
|
||||
std::memcpy(&value, it->second.memory.GetPtr() + (vaddr & CITRA_PAGE_MASK), sizeof(T));
|
||||
|
||||
if (GDBStub::CheckBreakpoint(vaddr, sizeof(T), GDBStub::BreakpointType::Read)) {
|
||||
GDBStub::Break(SIGTRAP);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
[[likely]] case PageType::RasterizerCachedMemory: {
|
||||
RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Flush);
|
||||
|
||||
T value;
|
||||
std::memcpy(&value, GetPointerForRasterizerCache(vaddr), sizeof(T));
|
||||
return value;
|
||||
}
|
||||
case PageType::RasterizerCachedMemoryWatchpoint: {
|
||||
RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Flush);
|
||||
|
||||
T value;
|
||||
std::memcpy(&value, GetPointerForRasterizerCache(vaddr), sizeof(T));
|
||||
|
||||
if (GDBStub::CheckBreakpoint(vaddr, sizeof(T), GDBStub::BreakpointType::Read)) {
|
||||
GDBStub::Break(SIGTRAP);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
|
@ -527,17 +683,39 @@ 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));
|
||||
|
||||
if (GDBStub::CheckBreakpoint(vaddr, sizeof(T), GDBStub::BreakpointType::Write)) {
|
||||
GDBStub::Break(SIGTRAP);
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
if (GDBStub::CheckBreakpoint(vaddr, sizeof(T), GDBStub::BreakpointType::Write)) {
|
||||
GDBStub::Break(SIGTRAP);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
|
@ -556,18 +734,44 @@ 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);
|
||||
|
||||
if (GDBStub::CheckBreakpoint(vaddr, sizeof(T), GDBStub::BreakpointType::Write)) {
|
||||
GDBStub::Break(SIGTRAP);
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
if (GDBStub::CheckBreakpoint(vaddr, sizeof(T), GDBStub::BreakpointType::Write)) {
|
||||
GDBStub::Break(SIGTRAP);
|
||||
}
|
||||
|
||||
return Common::AtomicCompareAndSwap(volatile_pointer, data, expected);
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
|
@ -582,7 +786,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 +804,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 +821,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 +963,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 +979,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;
|
||||
page_table->pointers[vaddr >> CITRA_PAGE_BITS] =
|
||||
GetPointerForRasterizerCache(vaddr & ~CITRA_PAGE_MASK);
|
||||
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:
|
||||
|
|
@ -911,7 +1128,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 +1187,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),
|
||||
|
|
|
|||
|
|
@ -34,7 +34,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 +42,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 +88,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 +110,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 +133,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();
|
||||
}
|
||||
|
|
@ -649,7 +676,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>
|
||||
T UnmappedAccess(const VAddr vaddr, const T value, bool read);
|
||||
|
||||
template <typename T>
|
||||
T Read(const std::shared_ptr<PageTable>& page_table, const VAddr vaddr);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue