Merge pull request #8402 from liamwhite/better-step
core/debugger: Improved stepping mechanism and misc fixes
This commit is contained in:
commit
858f8ac6d9
16 changed files with 252 additions and 122 deletions
|
@ -222,6 +222,11 @@ else()
|
||||||
list(APPEND CONAN_REQUIRED_LIBS "boost/1.79.0")
|
list(APPEND CONAN_REQUIRED_LIBS "boost/1.79.0")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# boost:asio has functions that require AcceptEx et al
|
||||||
|
if (MINGW)
|
||||||
|
find_library(MSWSOCK_LIBRARY mswsock REQUIRED)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Attempt to locate any packages that are required and report the missing ones in CONAN_REQUIRED_LIBS
|
# Attempt to locate any packages that are required and report the missing ones in CONAN_REQUIRED_LIBS
|
||||||
yuzu_find_packages()
|
yuzu_find_packages()
|
||||||
|
|
||||||
|
|
|
@ -768,6 +768,9 @@ create_target_directory_groups(core)
|
||||||
|
|
||||||
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
|
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
|
||||||
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::Opus)
|
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::Opus)
|
||||||
|
if (MINGW)
|
||||||
|
target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY})
|
||||||
|
endif()
|
||||||
|
|
||||||
if (ENABLE_WEB_SERVICE)
|
if (ENABLE_WEB_SERVICE)
|
||||||
target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE)
|
target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE)
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/debugger/debugger.h"
|
#include "core/debugger/debugger.h"
|
||||||
#include "core/hle/kernel/k_process.h"
|
#include "core/hle/kernel/k_process.h"
|
||||||
|
#include "core/hle/kernel/svc.h"
|
||||||
#include "core/loader/loader.h"
|
#include "core/loader/loader.h"
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
|
|
||||||
|
@ -89,8 +90,48 @@ void ARM_Interface::LogBacktrace() const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ARM_Interface::ShouldStep() const {
|
void ARM_Interface::Run() {
|
||||||
return system.DebuggerEnabled() && system.GetDebugger().IsStepping();
|
using Kernel::StepState;
|
||||||
|
using Kernel::SuspendType;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
Kernel::KThread* current_thread{system.Kernel().CurrentScheduler()->GetCurrentThread()};
|
||||||
|
Dynarmic::HaltReason hr{};
|
||||||
|
|
||||||
|
// Notify the debugger and go to sleep if a step was performed
|
||||||
|
// and this thread has been scheduled again.
|
||||||
|
if (current_thread->GetStepState() == StepState::StepPerformed) {
|
||||||
|
system.GetDebugger().NotifyThreadStopped(current_thread);
|
||||||
|
current_thread->RequestSuspend(SuspendType::Debug);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, run the thread.
|
||||||
|
if (current_thread->GetStepState() == StepState::StepPending) {
|
||||||
|
hr = StepJit();
|
||||||
|
|
||||||
|
if (Has(hr, step_thread)) {
|
||||||
|
current_thread->SetStepState(StepState::StepPerformed);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hr = RunJit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify the debugger and go to sleep if a breakpoint was hit.
|
||||||
|
if (Has(hr, breakpoint)) {
|
||||||
|
system.GetDebugger().NotifyThreadStopped(current_thread);
|
||||||
|
current_thread->RequestSuspend(Kernel::SuspendType::Debug);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle syscalls and scheduling (this may change the current thread)
|
||||||
|
if (Has(hr, svc_call)) {
|
||||||
|
Kernel::Svc::Call(system, GetSvcNumber());
|
||||||
|
}
|
||||||
|
if (Has(hr, break_loop) || !uses_wall_clock) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include <dynarmic/interface/halt_reason.h>
|
||||||
|
|
||||||
#include "common/common_funcs.h"
|
#include "common/common_funcs.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "core/hardware_properties.h"
|
#include "core/hardware_properties.h"
|
||||||
|
@ -64,7 +67,7 @@ public:
|
||||||
static_assert(sizeof(ThreadContext64) == 0x320);
|
static_assert(sizeof(ThreadContext64) == 0x320);
|
||||||
|
|
||||||
/// Runs the CPU until an event happens
|
/// Runs the CPU until an event happens
|
||||||
virtual void Run() = 0;
|
void Run();
|
||||||
|
|
||||||
/// Clear all instruction cache
|
/// Clear all instruction cache
|
||||||
virtual void ClearInstructionCache() = 0;
|
virtual void ClearInstructionCache() = 0;
|
||||||
|
@ -191,7 +194,10 @@ public:
|
||||||
|
|
||||||
void LogBacktrace() const;
|
void LogBacktrace() const;
|
||||||
|
|
||||||
bool ShouldStep() const;
|
static constexpr Dynarmic::HaltReason step_thread = Dynarmic::HaltReason::Step;
|
||||||
|
static constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2;
|
||||||
|
static constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3;
|
||||||
|
static constexpr Dynarmic::HaltReason breakpoint = Dynarmic::HaltReason::UserDefined4;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// System context that this ARM interface is running under.
|
/// System context that this ARM interface is running under.
|
||||||
|
@ -200,6 +206,10 @@ protected:
|
||||||
bool uses_wall_clock;
|
bool uses_wall_clock;
|
||||||
|
|
||||||
static void SymbolicateBacktrace(Core::System& system, std::vector<BacktraceEntry>& out);
|
static void SymbolicateBacktrace(Core::System& system, std::vector<BacktraceEntry>& out);
|
||||||
|
|
||||||
|
virtual Dynarmic::HaltReason RunJit() = 0;
|
||||||
|
virtual Dynarmic::HaltReason StepJit() = 0;
|
||||||
|
virtual u32 GetSvcNumber() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
|
@ -26,10 +26,6 @@ namespace Core {
|
||||||
|
|
||||||
using namespace Common::Literals;
|
using namespace Common::Literals;
|
||||||
|
|
||||||
constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2;
|
|
||||||
constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3;
|
|
||||||
constexpr Dynarmic::HaltReason breakpoint = Dynarmic::HaltReason::UserDefined4;
|
|
||||||
|
|
||||||
class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks {
|
class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks {
|
||||||
public:
|
public:
|
||||||
explicit DynarmicCallbacks32(ARM_Dynarmic_32& parent_)
|
explicit DynarmicCallbacks32(ARM_Dynarmic_32& parent_)
|
||||||
|
@ -82,8 +78,8 @@ public:
|
||||||
|
|
||||||
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override {
|
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override {
|
||||||
if (parent.system.DebuggerEnabled()) {
|
if (parent.system.DebuggerEnabled()) {
|
||||||
parent.breakpoint_pc = pc;
|
parent.jit.load()->Regs()[15] = pc;
|
||||||
parent.jit.load()->HaltExecution(breakpoint);
|
parent.jit.load()->HaltExecution(ARM_Interface::breakpoint);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +91,7 @@ public:
|
||||||
|
|
||||||
void CallSVC(u32 swi) override {
|
void CallSVC(u32 swi) override {
|
||||||
parent.svc_swi = swi;
|
parent.svc_swi = swi;
|
||||||
parent.jit.load()->HaltExecution(svc_call);
|
parent.jit.load()->HaltExecution(ARM_Interface::svc_call);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddTicks(u64 ticks) override {
|
void AddTicks(u64 ticks) override {
|
||||||
|
@ -240,35 +236,16 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
|
||||||
return std::make_unique<Dynarmic::A32::Jit>(config);
|
return std::make_unique<Dynarmic::A32::Jit>(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ARM_Dynarmic_32::Run() {
|
Dynarmic::HaltReason ARM_Dynarmic_32::RunJit() {
|
||||||
while (true) {
|
return jit.load()->Run();
|
||||||
const auto hr = ShouldStep() ? jit.load()->Step() : jit.load()->Run();
|
}
|
||||||
if (Has(hr, svc_call)) {
|
|
||||||
Kernel::Svc::Call(system, svc_swi);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check to see if breakpoint is triggered.
|
Dynarmic::HaltReason ARM_Dynarmic_32::StepJit() {
|
||||||
// Recheck step condition in case stop is no longer desired.
|
return jit.load()->Step();
|
||||||
Kernel::KThread* current_thread = system.Kernel().GetCurrentEmuThread();
|
}
|
||||||
if (Has(hr, breakpoint)) {
|
|
||||||
jit.load()->Regs()[15] = breakpoint_pc;
|
|
||||||
|
|
||||||
if (system.GetDebugger().NotifyThreadStopped(current_thread)) {
|
u32 ARM_Dynarmic_32::GetSvcNumber() const {
|
||||||
current_thread->RequestSuspend(Kernel::SuspendType::Debug);
|
return svc_swi;
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (ShouldStep()) {
|
|
||||||
// When stepping, this should be the only thread running.
|
|
||||||
ASSERT(system.GetDebugger().NotifyThreadStopped(current_thread));
|
|
||||||
current_thread->RequestSuspend(Kernel::SuspendType::Debug);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Has(hr, break_loop) || !uses_wall_clock) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ARM_Dynarmic_32::ARM_Dynarmic_32(System& system_, CPUInterrupts& interrupt_handlers_,
|
ARM_Dynarmic_32::ARM_Dynarmic_32(System& system_, CPUInterrupts& interrupt_handlers_,
|
||||||
|
|
|
@ -41,7 +41,6 @@ public:
|
||||||
void SetVectorReg(int index, u128 value) override;
|
void SetVectorReg(int index, u128 value) override;
|
||||||
u32 GetPSTATE() const override;
|
u32 GetPSTATE() const override;
|
||||||
void SetPSTATE(u32 pstate) override;
|
void SetPSTATE(u32 pstate) override;
|
||||||
void Run() override;
|
|
||||||
VAddr GetTlsAddress() const override;
|
VAddr GetTlsAddress() const override;
|
||||||
void SetTlsAddress(VAddr address) override;
|
void SetTlsAddress(VAddr address) override;
|
||||||
void SetTPIDR_EL0(u64 value) override;
|
void SetTPIDR_EL0(u64 value) override;
|
||||||
|
@ -69,6 +68,11 @@ public:
|
||||||
|
|
||||||
std::vector<BacktraceEntry> GetBacktrace() const override;
|
std::vector<BacktraceEntry> GetBacktrace() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Dynarmic::HaltReason RunJit() override;
|
||||||
|
Dynarmic::HaltReason StepJit() override;
|
||||||
|
u32 GetSvcNumber() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<Dynarmic::A32::Jit> MakeJit(Common::PageTable* page_table) const;
|
std::shared_ptr<Dynarmic::A32::Jit> MakeJit(Common::PageTable* page_table) const;
|
||||||
|
|
||||||
|
@ -94,9 +98,6 @@ private:
|
||||||
|
|
||||||
// SVC callback
|
// SVC callback
|
||||||
u32 svc_swi{};
|
u32 svc_swi{};
|
||||||
|
|
||||||
// Debug restart address
|
|
||||||
u32 breakpoint_pc{};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
|
@ -26,10 +26,6 @@ namespace Core {
|
||||||
using Vector = Dynarmic::A64::Vector;
|
using Vector = Dynarmic::A64::Vector;
|
||||||
using namespace Common::Literals;
|
using namespace Common::Literals;
|
||||||
|
|
||||||
constexpr Dynarmic::HaltReason break_loop = Dynarmic::HaltReason::UserDefined2;
|
|
||||||
constexpr Dynarmic::HaltReason svc_call = Dynarmic::HaltReason::UserDefined3;
|
|
||||||
constexpr Dynarmic::HaltReason breakpoint = Dynarmic::HaltReason::UserDefined4;
|
|
||||||
|
|
||||||
class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks {
|
class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks {
|
||||||
public:
|
public:
|
||||||
explicit DynarmicCallbacks64(ARM_Dynarmic_64& parent_)
|
explicit DynarmicCallbacks64(ARM_Dynarmic_64& parent_)
|
||||||
|
@ -123,8 +119,8 @@ public:
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
if (parent.system.DebuggerEnabled()) {
|
if (parent.system.DebuggerEnabled()) {
|
||||||
parent.breakpoint_pc = pc;
|
parent.jit.load()->SetPC(pc);
|
||||||
parent.jit.load()->HaltExecution(breakpoint);
|
parent.jit.load()->HaltExecution(ARM_Interface::breakpoint);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +132,7 @@ public:
|
||||||
|
|
||||||
void CallSVC(u32 swi) override {
|
void CallSVC(u32 swi) override {
|
||||||
parent.svc_swi = swi;
|
parent.svc_swi = swi;
|
||||||
parent.jit.load()->HaltExecution(svc_call);
|
parent.jit.load()->HaltExecution(ARM_Interface::svc_call);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddTicks(u64 ticks) override {
|
void AddTicks(u64 ticks) override {
|
||||||
|
@ -300,35 +296,16 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
|
||||||
return std::make_shared<Dynarmic::A64::Jit>(config);
|
return std::make_shared<Dynarmic::A64::Jit>(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ARM_Dynarmic_64::Run() {
|
Dynarmic::HaltReason ARM_Dynarmic_64::RunJit() {
|
||||||
while (true) {
|
return jit.load()->Run();
|
||||||
const auto hr = jit.load()->Run();
|
}
|
||||||
if (Has(hr, svc_call)) {
|
|
||||||
Kernel::Svc::Call(system, svc_swi);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check to see if breakpoint is triggered.
|
Dynarmic::HaltReason ARM_Dynarmic_64::StepJit() {
|
||||||
// Recheck step condition in case stop is no longer desired.
|
return jit.load()->Step();
|
||||||
Kernel::KThread* current_thread = system.Kernel().GetCurrentEmuThread();
|
}
|
||||||
if (Has(hr, breakpoint)) {
|
|
||||||
jit.load()->SetPC(breakpoint_pc);
|
|
||||||
|
|
||||||
if (system.GetDebugger().NotifyThreadStopped(current_thread)) {
|
u32 ARM_Dynarmic_64::GetSvcNumber() const {
|
||||||
current_thread->RequestSuspend(Kernel::SuspendType::Debug);
|
return svc_swi;
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (ShouldStep()) {
|
|
||||||
// When stepping, this should be the only thread running.
|
|
||||||
ASSERT(system.GetDebugger().NotifyThreadStopped(current_thread));
|
|
||||||
current_thread->RequestSuspend(Kernel::SuspendType::Debug);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Has(hr, break_loop) || !uses_wall_clock) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ARM_Dynarmic_64::ARM_Dynarmic_64(System& system_, CPUInterrupts& interrupt_handlers_,
|
ARM_Dynarmic_64::ARM_Dynarmic_64(System& system_, CPUInterrupts& interrupt_handlers_,
|
||||||
|
|
|
@ -39,7 +39,6 @@ public:
|
||||||
void SetVectorReg(int index, u128 value) override;
|
void SetVectorReg(int index, u128 value) override;
|
||||||
u32 GetPSTATE() const override;
|
u32 GetPSTATE() const override;
|
||||||
void SetPSTATE(u32 pstate) override;
|
void SetPSTATE(u32 pstate) override;
|
||||||
void Run() override;
|
|
||||||
VAddr GetTlsAddress() const override;
|
VAddr GetTlsAddress() const override;
|
||||||
void SetTlsAddress(VAddr address) override;
|
void SetTlsAddress(VAddr address) override;
|
||||||
void SetTPIDR_EL0(u64 value) override;
|
void SetTPIDR_EL0(u64 value) override;
|
||||||
|
@ -63,6 +62,11 @@ public:
|
||||||
|
|
||||||
std::vector<BacktraceEntry> GetBacktrace() const override;
|
std::vector<BacktraceEntry> GetBacktrace() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Dynarmic::HaltReason RunJit() override;
|
||||||
|
Dynarmic::HaltReason StepJit() override;
|
||||||
|
u32 GetSvcNumber() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<Dynarmic::A64::Jit> MakeJit(Common::PageTable* page_table,
|
std::shared_ptr<Dynarmic::A64::Jit> MakeJit(Common::PageTable* page_table,
|
||||||
std::size_t address_space_bits) const;
|
std::size_t address_space_bits) const;
|
||||||
|
@ -87,9 +91,6 @@ private:
|
||||||
|
|
||||||
// SVC callback
|
// SVC callback
|
||||||
u32 svc_swi{};
|
u32 svc_swi{};
|
||||||
|
|
||||||
// Debug restart address
|
|
||||||
u64 breakpoint_pc{};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
|
@ -84,31 +85,31 @@ public:
|
||||||
return active_thread;
|
return active_thread;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsStepping() const {
|
|
||||||
return stepping;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void InitializeServer(u16 port) {
|
void InitializeServer(u16 port) {
|
||||||
using boost::asio::ip::tcp;
|
using boost::asio::ip::tcp;
|
||||||
|
|
||||||
LOG_INFO(Debug_GDBStub, "Starting server on port {}...", port);
|
LOG_INFO(Debug_GDBStub, "Starting server on port {}...", port);
|
||||||
|
|
||||||
// Initialize the listening socket and accept a new client.
|
|
||||||
tcp::endpoint endpoint{boost::asio::ip::address_v4::loopback(), port};
|
|
||||||
tcp::acceptor acceptor{io_context, endpoint};
|
|
||||||
client_socket = acceptor.accept();
|
|
||||||
|
|
||||||
// Run the connection thread.
|
// Run the connection thread.
|
||||||
connection_thread = std::jthread([&](std::stop_token stop_token) {
|
connection_thread = std::jthread([&, port](std::stop_token stop_token) {
|
||||||
try {
|
try {
|
||||||
|
// Initialize the listening socket and accept a new client.
|
||||||
|
tcp::endpoint endpoint{boost::asio::ip::address_v4::loopback(), port};
|
||||||
|
tcp::acceptor acceptor{io_context, endpoint};
|
||||||
|
|
||||||
|
acceptor.async_accept(client_socket, [](const auto&) {});
|
||||||
|
io_context.run_one();
|
||||||
|
io_context.restart();
|
||||||
|
|
||||||
|
if (stop_token.stop_requested()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ThreadLoop(stop_token);
|
ThreadLoop(stop_token);
|
||||||
} catch (const std::exception& ex) {
|
} catch (const std::exception& ex) {
|
||||||
LOG_CRITICAL(Debug_GDBStub, "Stopping server: {}", ex.what());
|
LOG_CRITICAL(Debug_GDBStub, "Stopping server: {}", ex.what());
|
||||||
}
|
}
|
||||||
|
|
||||||
client_socket.shutdown(client_socket.shutdown_both);
|
|
||||||
client_socket.close();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,8 +130,7 @@ private:
|
||||||
AllCoreStop();
|
AllCoreStop();
|
||||||
|
|
||||||
// Set the active thread.
|
// Set the active thread.
|
||||||
active_thread = ThreadList()[0];
|
UpdateActiveThread();
|
||||||
active_thread->Resume(Kernel::SuspendType::Debug);
|
|
||||||
|
|
||||||
// Set up the frontend.
|
// Set up the frontend.
|
||||||
frontend->Connected();
|
frontend->Connected();
|
||||||
|
@ -142,7 +142,7 @@ private:
|
||||||
|
|
||||||
void PipeData(std::span<const u8> data) {
|
void PipeData(std::span<const u8> data) {
|
||||||
AllCoreStop();
|
AllCoreStop();
|
||||||
active_thread->Resume(Kernel::SuspendType::Debug);
|
UpdateActiveThread();
|
||||||
frontend->Stopped(active_thread);
|
frontend->Stopped(active_thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,18 +156,22 @@ private:
|
||||||
stopped = true;
|
stopped = true;
|
||||||
}
|
}
|
||||||
AllCoreStop();
|
AllCoreStop();
|
||||||
active_thread = ThreadList()[0];
|
UpdateActiveThread();
|
||||||
active_thread->Resume(Kernel::SuspendType::Debug);
|
|
||||||
frontend->Stopped(active_thread);
|
frontend->Stopped(active_thread);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DebuggerAction::Continue:
|
case DebuggerAction::Continue:
|
||||||
stepping = false;
|
active_thread->SetStepState(Kernel::StepState::NotStepping);
|
||||||
ResumeInactiveThreads();
|
ResumeInactiveThreads();
|
||||||
AllCoreResume();
|
AllCoreResume();
|
||||||
break;
|
break;
|
||||||
case DebuggerAction::StepThread:
|
case DebuggerAction::StepThreadUnlocked:
|
||||||
stepping = true;
|
active_thread->SetStepState(Kernel::StepState::StepPending);
|
||||||
|
ResumeInactiveThreads();
|
||||||
|
AllCoreResume();
|
||||||
|
break;
|
||||||
|
case DebuggerAction::StepThreadLocked:
|
||||||
|
active_thread->SetStepState(Kernel::StepState::StepPending);
|
||||||
SuspendInactiveThreads();
|
SuspendInactiveThreads();
|
||||||
AllCoreResume();
|
AllCoreResume();
|
||||||
break;
|
break;
|
||||||
|
@ -212,10 +216,20 @@ private:
|
||||||
for (auto* thread : ThreadList()) {
|
for (auto* thread : ThreadList()) {
|
||||||
if (thread != active_thread) {
|
if (thread != active_thread) {
|
||||||
thread->Resume(Kernel::SuspendType::Debug);
|
thread->Resume(Kernel::SuspendType::Debug);
|
||||||
|
thread->SetStepState(Kernel::StepState::NotStepping);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UpdateActiveThread() {
|
||||||
|
const auto& threads{ThreadList()};
|
||||||
|
if (std::find(threads.begin(), threads.end(), active_thread) == threads.end()) {
|
||||||
|
active_thread = threads[0];
|
||||||
|
}
|
||||||
|
active_thread->Resume(Kernel::SuspendType::Debug);
|
||||||
|
active_thread->SetStepState(Kernel::StepState::NotStepping);
|
||||||
|
}
|
||||||
|
|
||||||
const std::vector<Kernel::KThread*>& ThreadList() {
|
const std::vector<Kernel::KThread*>& ThreadList() {
|
||||||
return system.GlobalSchedulerContext().GetThreadList();
|
return system.GlobalSchedulerContext().GetThreadList();
|
||||||
}
|
}
|
||||||
|
@ -233,7 +247,6 @@ private:
|
||||||
|
|
||||||
Kernel::KThread* active_thread;
|
Kernel::KThread* active_thread;
|
||||||
bool stopped;
|
bool stopped;
|
||||||
bool stepping;
|
|
||||||
|
|
||||||
std::array<u8, 4096> client_data;
|
std::array<u8, 4096> client_data;
|
||||||
};
|
};
|
||||||
|
@ -252,8 +265,4 @@ bool Debugger::NotifyThreadStopped(Kernel::KThread* thread) {
|
||||||
return impl && impl->NotifyThreadStopped(thread);
|
return impl && impl->NotifyThreadStopped(thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Debugger::IsStepping() const {
|
|
||||||
return impl && impl->IsStepping();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
|
@ -35,11 +35,6 @@ public:
|
||||||
*/
|
*/
|
||||||
bool NotifyThreadStopped(Kernel::KThread* thread);
|
bool NotifyThreadStopped(Kernel::KThread* thread);
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether a step is in progress.
|
|
||||||
*/
|
|
||||||
bool IsStepping() const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<DebuggerImpl> impl;
|
std::unique_ptr<DebuggerImpl> impl;
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,10 +16,11 @@ class KThread;
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
enum class DebuggerAction {
|
enum class DebuggerAction {
|
||||||
Interrupt, // Stop emulation as soon as possible.
|
Interrupt, ///< Stop emulation as soon as possible.
|
||||||
Continue, // Resume emulation.
|
Continue, ///< Resume emulation.
|
||||||
StepThread, // Step the currently-active thread.
|
StepThreadLocked, ///< Step the currently-active thread without resuming others.
|
||||||
ShutdownEmulation, // Shut down the emulator.
|
StepThreadUnlocked, ///< Step the currently-active thread and resume others.
|
||||||
|
ShutdownEmulation, ///< Shut down the emulator.
|
||||||
};
|
};
|
||||||
|
|
||||||
class DebuggerBackend {
|
class DebuggerBackend {
|
||||||
|
|
|
@ -6,8 +6,7 @@
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include <boost/asio.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
#include <boost/process/async_pipe.hpp>
|
|
||||||
|
|
||||||
#include "common/hex_util.h"
|
#include "common/hex_util.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
@ -114,6 +113,11 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (packet.starts_with("vCont")) {
|
||||||
|
HandleVCont(packet.substr(5), actions);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
std::string_view command{packet.substr(1, packet.size())};
|
std::string_view command{packet.substr(1, packet.size())};
|
||||||
|
|
||||||
switch (packet[0]) {
|
switch (packet[0]) {
|
||||||
|
@ -122,6 +126,8 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction
|
||||||
s64 thread_id{strtoll(command.data() + 1, nullptr, 16)};
|
s64 thread_id{strtoll(command.data() + 1, nullptr, 16)};
|
||||||
if (thread_id >= 1) {
|
if (thread_id >= 1) {
|
||||||
thread = GetThreadByID(thread_id);
|
thread = GetThreadByID(thread_id);
|
||||||
|
} else {
|
||||||
|
thread = backend.GetActiveThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thread) {
|
if (thread) {
|
||||||
|
@ -141,6 +147,7 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'Q':
|
||||||
case 'q':
|
case 'q':
|
||||||
HandleQuery(command);
|
HandleQuery(command);
|
||||||
break;
|
break;
|
||||||
|
@ -204,7 +211,7 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 's':
|
case 's':
|
||||||
actions.push_back(DebuggerAction::StepThread);
|
actions.push_back(DebuggerAction::StepThreadLocked);
|
||||||
break;
|
break;
|
||||||
case 'C':
|
case 'C':
|
||||||
case 'c':
|
case 'c':
|
||||||
|
@ -248,12 +255,47 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::string_view GetThreadWaitReason(const Kernel::KThread* thread) {
|
||||||
|
switch (thread->GetWaitReasonForDebugging()) {
|
||||||
|
case Kernel::ThreadWaitReasonForDebugging::Sleep:
|
||||||
|
return "Sleep";
|
||||||
|
case Kernel::ThreadWaitReasonForDebugging::IPC:
|
||||||
|
return "IPC";
|
||||||
|
case Kernel::ThreadWaitReasonForDebugging::Synchronization:
|
||||||
|
return "Synchronization";
|
||||||
|
case Kernel::ThreadWaitReasonForDebugging::ConditionVar:
|
||||||
|
return "ConditionVar";
|
||||||
|
case Kernel::ThreadWaitReasonForDebugging::Arbitration:
|
||||||
|
return "Arbitration";
|
||||||
|
case Kernel::ThreadWaitReasonForDebugging::Suspended:
|
||||||
|
return "Suspended";
|
||||||
|
default:
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string GetThreadState(const Kernel::KThread* thread) {
|
||||||
|
switch (thread->GetState()) {
|
||||||
|
case Kernel::ThreadState::Initialized:
|
||||||
|
return "Initialized";
|
||||||
|
case Kernel::ThreadState::Waiting:
|
||||||
|
return fmt::format("Waiting ({})", GetThreadWaitReason(thread));
|
||||||
|
case Kernel::ThreadState::Runnable:
|
||||||
|
return "Runnable";
|
||||||
|
case Kernel::ThreadState::Terminated:
|
||||||
|
return "Terminated";
|
||||||
|
default:
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GDBStub::HandleQuery(std::string_view command) {
|
void GDBStub::HandleQuery(std::string_view command) {
|
||||||
if (command.starts_with("TStatus")) {
|
if (command.starts_with("TStatus")) {
|
||||||
// no tracepoint support
|
// no tracepoint support
|
||||||
SendReply("T0");
|
SendReply("T0");
|
||||||
} else if (command.starts_with("Supported")) {
|
} else if (command.starts_with("Supported")) {
|
||||||
SendReply("PacketSize=4000;qXfer:features:read+;qXfer:threads:read+;qXfer:libraries:read+");
|
SendReply("PacketSize=4000;qXfer:features:read+;qXfer:threads:read+;qXfer:libraries:read+;"
|
||||||
|
"vContSupported+;QStartNoAckMode+");
|
||||||
} else if (command.starts_with("Xfer:features:read:target.xml:")) {
|
} else if (command.starts_with("Xfer:features:read:target.xml:")) {
|
||||||
const auto offset{command.substr(30)};
|
const auto offset{command.substr(30)};
|
||||||
const auto amount{command.substr(command.find(',') + 1)};
|
const auto amount{command.substr(command.find(',') + 1)};
|
||||||
|
@ -297,18 +339,57 @@ void GDBStub::HandleQuery(std::string_view command) {
|
||||||
|
|
||||||
const auto& threads = system.GlobalSchedulerContext().GetThreadList();
|
const auto& threads = system.GlobalSchedulerContext().GetThreadList();
|
||||||
for (const auto& thread : threads) {
|
for (const auto& thread : threads) {
|
||||||
buffer +=
|
buffer += fmt::format(R"(<thread id="{:x}" core="{:d}" name="Thread {:d}">{}</thread>)",
|
||||||
fmt::format(R"(<thread id="{:x}" core="{:d}" name="Thread {:d}"/>)",
|
thread->GetThreadID(), thread->GetActiveCore(),
|
||||||
thread->GetThreadID(), thread->GetActiveCore(), thread->GetThreadID());
|
thread->GetThreadID(), GetThreadState(thread));
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer += "</threads>";
|
buffer += "</threads>";
|
||||||
SendReply(buffer);
|
SendReply(buffer);
|
||||||
|
} else if (command.starts_with("Attached")) {
|
||||||
|
SendReply("0");
|
||||||
|
} else if (command.starts_with("StartNoAckMode")) {
|
||||||
|
no_ack = true;
|
||||||
|
SendReply(GDB_STUB_REPLY_OK);
|
||||||
} else {
|
} else {
|
||||||
SendReply(GDB_STUB_REPLY_EMPTY);
|
SendReply(GDB_STUB_REPLY_EMPTY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GDBStub::HandleVCont(std::string_view command, std::vector<DebuggerAction>& actions) {
|
||||||
|
if (command == "?") {
|
||||||
|
// Continuing and stepping are supported
|
||||||
|
// (signal is ignored, but required for GDB to use vCont)
|
||||||
|
SendReply("vCont;c;C;s;S");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Kernel::KThread* stepped_thread{nullptr};
|
||||||
|
bool lock_execution{true};
|
||||||
|
|
||||||
|
std::vector<std::string> entries;
|
||||||
|
boost::split(entries, command.substr(1), boost::is_any_of(";"));
|
||||||
|
for (const auto& thread_action : entries) {
|
||||||
|
std::vector<std::string> parts;
|
||||||
|
boost::split(parts, thread_action, boost::is_any_of(":"));
|
||||||
|
|
||||||
|
if (parts.size() == 1 && (parts[0] == "c" || parts[0].starts_with("C"))) {
|
||||||
|
lock_execution = false;
|
||||||
|
}
|
||||||
|
if (parts.size() == 2 && (parts[0] == "s" || parts[0].starts_with("S"))) {
|
||||||
|
stepped_thread = GetThreadByID(strtoll(parts[1].data(), nullptr, 16));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stepped_thread) {
|
||||||
|
backend.SetActiveThread(stepped_thread);
|
||||||
|
actions.push_back(lock_execution ? DebuggerAction::StepThreadLocked
|
||||||
|
: DebuggerAction::StepThreadUnlocked);
|
||||||
|
} else {
|
||||||
|
actions.push_back(DebuggerAction::Continue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) {
|
Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) {
|
||||||
const auto& threads{system.GlobalSchedulerContext().GetThreadList()};
|
const auto& threads{system.GlobalSchedulerContext().GetThreadList()};
|
||||||
for (auto* thread : threads) {
|
for (auto* thread : threads) {
|
||||||
|
@ -374,6 +455,10 @@ void GDBStub::SendReply(std::string_view data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GDBStub::SendStatus(char status) {
|
void GDBStub::SendStatus(char status) {
|
||||||
|
if (no_ack) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
std::array<u8, 1> buf = {static_cast<u8>(status)};
|
std::array<u8, 1> buf = {static_cast<u8>(status)};
|
||||||
LOG_TRACE(Debug_GDBStub, "Writing status: {}", status);
|
LOG_TRACE(Debug_GDBStub, "Writing status: {}", status);
|
||||||
backend.WriteToClient(buf);
|
backend.WriteToClient(buf);
|
||||||
|
|
|
@ -28,6 +28,7 @@ public:
|
||||||
private:
|
private:
|
||||||
void ProcessData(std::vector<DebuggerAction>& actions);
|
void ProcessData(std::vector<DebuggerAction>& actions);
|
||||||
void ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions);
|
void ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions);
|
||||||
|
void HandleVCont(std::string_view command, std::vector<DebuggerAction>& actions);
|
||||||
void HandleQuery(std::string_view command);
|
void HandleQuery(std::string_view command);
|
||||||
std::vector<char>::const_iterator CommandEnd() const;
|
std::vector<char>::const_iterator CommandEnd() const;
|
||||||
std::optional<std::string> DetachCommand();
|
std::optional<std::string> DetachCommand();
|
||||||
|
@ -42,6 +43,7 @@ private:
|
||||||
std::unique_ptr<GDBStubArch> arch;
|
std::unique_ptr<GDBStubArch> arch;
|
||||||
std::vector<char> current_command;
|
std::vector<char> current_command;
|
||||||
std::map<VAddr, u32> replaced_instructions;
|
std::map<VAddr, u32> replaced_instructions;
|
||||||
|
bool no_ack{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
|
|
@ -100,6 +100,12 @@ enum class ThreadWaitReasonForDebugging : u32 {
|
||||||
Suspended, ///< Thread is waiting due to process suspension
|
Suspended, ///< Thread is waiting due to process suspension
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class StepState : u32 {
|
||||||
|
NotStepping, ///< Thread is not currently stepping
|
||||||
|
StepPending, ///< Thread will step when next scheduled
|
||||||
|
StepPerformed, ///< Thread has stepped, waiting to be scheduled again
|
||||||
|
};
|
||||||
|
|
||||||
[[nodiscard]] KThread* GetCurrentThreadPointer(KernelCore& kernel);
|
[[nodiscard]] KThread* GetCurrentThreadPointer(KernelCore& kernel);
|
||||||
[[nodiscard]] KThread& GetCurrentThread(KernelCore& kernel);
|
[[nodiscard]] KThread& GetCurrentThread(KernelCore& kernel);
|
||||||
[[nodiscard]] s32 GetCurrentCoreId(KernelCore& kernel);
|
[[nodiscard]] s32 GetCurrentCoreId(KernelCore& kernel);
|
||||||
|
@ -267,6 +273,14 @@ public:
|
||||||
|
|
||||||
void SetState(ThreadState state);
|
void SetState(ThreadState state);
|
||||||
|
|
||||||
|
[[nodiscard]] StepState GetStepState() const {
|
||||||
|
return step_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetStepState(StepState state) {
|
||||||
|
step_state = state;
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] s64 GetLastScheduledTick() const {
|
[[nodiscard]] s64 GetLastScheduledTick() const {
|
||||||
return last_scheduled_tick;
|
return last_scheduled_tick;
|
||||||
}
|
}
|
||||||
|
@ -769,6 +783,7 @@ private:
|
||||||
std::shared_ptr<Common::Fiber> host_context{};
|
std::shared_ptr<Common::Fiber> host_context{};
|
||||||
bool is_single_core{};
|
bool is_single_core{};
|
||||||
ThreadType thread_type{};
|
ThreadType thread_type{};
|
||||||
|
StepState step_state{};
|
||||||
std::mutex dummy_wait_lock;
|
std::mutex dummy_wait_lock;
|
||||||
std::condition_variable dummy_wait_cv;
|
std::condition_variable dummy_wait_cv;
|
||||||
|
|
||||||
|
|
|
@ -277,3 +277,7 @@ else()
|
||||||
$<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable>
|
$<$<CXX_COMPILER_ID:GNU>:-Werror=unused-but-set-variable>
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (ARCHITECTURE_x86_64)
|
||||||
|
target_link_libraries(video_core PRIVATE dynarmic)
|
||||||
|
endif()
|
||||||
|
|
|
@ -319,3 +319,7 @@ endif()
|
||||||
if (NOT APPLE)
|
if (NOT APPLE)
|
||||||
target_compile_definitions(yuzu PRIVATE HAS_OPENGL)
|
target_compile_definitions(yuzu PRIVATE HAS_OPENGL)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (ARCHITECTURE_x86_64)
|
||||||
|
target_link_libraries(yuzu PRIVATE dynarmic)
|
||||||
|
endif()
|
||||||
|
|
Loading…
Reference in a new issue