mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-23 06:09:50 -06:00
Debugger: Add a Thread widget
DebugInterface: Add GetThreads WatchWidget: Update widget on AddWatch
This commit is contained in:
204
Source/Core/Common/Debug/OSThread.cpp
Normal file
204
Source/Core/Common/Debug/OSThread.cpp
Normal file
@ -0,0 +1,204 @@
|
||||
// Copyright 2020 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "Common/Debug/OSThread.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "Core/PowerPC/MMU.h"
|
||||
|
||||
// Context offsets based on the following functions:
|
||||
// - OSSaveContext
|
||||
// - OSSaveFPUContext
|
||||
// - OSDumpContext
|
||||
// - OSClearContext
|
||||
// - OSExceptionVector
|
||||
void Common::Debug::OSContext::Read(u32 addr)
|
||||
{
|
||||
for (std::size_t i = 0; i < gpr.size(); i++)
|
||||
gpr[i] = PowerPC::HostRead_U32(addr + u32(i * sizeof(int)));
|
||||
cr = PowerPC::HostRead_U32(addr + 0x80);
|
||||
lr = PowerPC::HostRead_U32(addr + 0x84);
|
||||
ctr = PowerPC::HostRead_U32(addr + 0x88);
|
||||
xer = PowerPC::HostRead_U32(addr + 0x8C);
|
||||
for (std::size_t i = 0; i < fpr.size(); i++)
|
||||
fpr[i] = PowerPC::HostRead_F64(addr + 0x90 + u32(i * sizeof(double)));
|
||||
fpscr = PowerPC::HostRead_U64(addr + 0x190);
|
||||
srr0 = PowerPC::HostRead_U32(addr + 0x198);
|
||||
srr1 = PowerPC::HostRead_U32(addr + 0x19c);
|
||||
dummy = PowerPC::HostRead_U16(addr + 0x1a0);
|
||||
state = static_cast<OSContext::State>(PowerPC::HostRead_U16(addr + 0x1a2));
|
||||
for (std::size_t i = 0; i < gqr.size(); i++)
|
||||
gqr[i] = PowerPC::HostRead_U32(addr + 0x1a4 + u32(i * sizeof(int)));
|
||||
psf_padding = 0;
|
||||
for (std::size_t i = 0; i < psf.size(); i++)
|
||||
psf[i] = PowerPC::HostRead_F64(addr + 0x1c8 + u32(i * sizeof(double)));
|
||||
}
|
||||
|
||||
// Mutex offsets based on the following functions:
|
||||
// - OSInitMutex
|
||||
// - OSLockMutex
|
||||
// - __OSUnlockAllMutex
|
||||
void Common::Debug::OSMutex::Read(u32 addr)
|
||||
{
|
||||
thread_queue.head = PowerPC::HostRead_U32(addr);
|
||||
thread_queue.tail = PowerPC::HostRead_U32(addr + 0x4);
|
||||
owner_addr = PowerPC::HostRead_U32(addr + 0x8);
|
||||
lock_count = PowerPC::HostRead_U32(addr + 0xc);
|
||||
link.next = PowerPC::HostRead_U32(addr + 0x10);
|
||||
link.prev = PowerPC::HostRead_U32(addr + 0x14);
|
||||
}
|
||||
|
||||
// Thread offsets based on the following functions:
|
||||
// - OSCreateThread
|
||||
// - OSIsThreadTerminated
|
||||
// - OSJoinThread
|
||||
// - OSSuspendThread
|
||||
// - OSSetPriority
|
||||
// - OSExitThread
|
||||
// - OSLockMutex
|
||||
// - __OSUnlockAllMutex
|
||||
// - __OSThreadInit
|
||||
// - OSSetThreadSpecific
|
||||
// - SOInit (for errno)
|
||||
void Common::Debug::OSThread::Read(u32 addr)
|
||||
{
|
||||
context.Read(addr);
|
||||
state = PowerPC::HostRead_U16(addr + 0x2c8);
|
||||
is_detached = PowerPC::HostRead_U16(addr + 0x2ca);
|
||||
suspend = PowerPC::HostRead_U32(addr + 0x2cc);
|
||||
effective_priority = PowerPC::HostRead_U32(addr + 0x2d0);
|
||||
base_priority = PowerPC::HostRead_U32(addr + 0x2d4);
|
||||
exit_code_addr = PowerPC::HostRead_U32(addr + 0x2d8);
|
||||
|
||||
queue_addr = PowerPC::HostRead_U32(addr + 0x2dc);
|
||||
queue_link.next = PowerPC::HostRead_U32(addr + 0x2e0);
|
||||
queue_link.prev = PowerPC::HostRead_U32(addr + 0x2e4);
|
||||
|
||||
join_queue.head = PowerPC::HostRead_U32(addr + 0x2e8);
|
||||
join_queue.tail = PowerPC::HostRead_U32(addr + 0x2ec);
|
||||
|
||||
mutex_addr = PowerPC::HostRead_U32(addr + 0x2f0);
|
||||
mutex_queue.head = PowerPC::HostRead_U32(addr + 0x2f4);
|
||||
mutex_queue.tail = PowerPC::HostRead_U32(addr + 0x2f8);
|
||||
|
||||
thread_link.next = PowerPC::HostRead_U32(addr + 0x2fc);
|
||||
thread_link.prev = PowerPC::HostRead_U32(addr + 0x300);
|
||||
|
||||
stack_addr = PowerPC::HostRead_U32(addr + 0x304);
|
||||
stack_end = PowerPC::HostRead_U32(addr + 0x308);
|
||||
error = PowerPC::HostRead_U32(addr + 0x30c);
|
||||
specific[0] = PowerPC::HostRead_U32(addr + 0x310);
|
||||
specific[1] = PowerPC::HostRead_U32(addr + 0x314);
|
||||
}
|
||||
|
||||
bool Common::Debug::OSThread::IsValid() const
|
||||
{
|
||||
return PowerPC::HostIsRAMAddress(stack_end) && PowerPC::HostRead_U32(stack_end) == STACK_MAGIC;
|
||||
}
|
||||
|
||||
Common::Debug::OSThreadView::OSThreadView(u32 addr)
|
||||
{
|
||||
m_address = addr;
|
||||
m_thread.Read(addr);
|
||||
}
|
||||
|
||||
const Common::Debug::OSThread& Common::Debug::OSThreadView::Data() const
|
||||
{
|
||||
return m_thread;
|
||||
}
|
||||
|
||||
Common::Debug::PartialContext Common::Debug::OSThreadView::GetContext() const
|
||||
{
|
||||
PartialContext context;
|
||||
|
||||
if (!IsValid())
|
||||
return context;
|
||||
|
||||
context.gpr = m_thread.context.gpr;
|
||||
context.cr = m_thread.context.cr;
|
||||
context.lr = m_thread.context.lr;
|
||||
context.ctr = m_thread.context.ctr;
|
||||
context.xer = m_thread.context.xer;
|
||||
context.fpr = m_thread.context.fpr;
|
||||
context.fpscr = m_thread.context.fpscr;
|
||||
context.srr0 = m_thread.context.srr0;
|
||||
context.srr1 = m_thread.context.srr1;
|
||||
context.dummy = m_thread.context.dummy;
|
||||
context.state = static_cast<u16>(m_thread.context.state);
|
||||
context.gqr = m_thread.context.gqr;
|
||||
context.psf = m_thread.context.psf;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
u32 Common::Debug::OSThreadView::GetAddress() const
|
||||
{
|
||||
return m_address;
|
||||
}
|
||||
|
||||
u16 Common::Debug::OSThreadView::GetState() const
|
||||
{
|
||||
return m_thread.state;
|
||||
}
|
||||
|
||||
bool Common::Debug::OSThreadView::IsSuspended() const
|
||||
{
|
||||
return m_thread.suspend > 0;
|
||||
}
|
||||
|
||||
bool Common::Debug::OSThreadView::IsDetached() const
|
||||
{
|
||||
return m_thread.is_detached != 0;
|
||||
}
|
||||
|
||||
s32 Common::Debug::OSThreadView::GetBasePriority() const
|
||||
{
|
||||
return m_thread.base_priority;
|
||||
}
|
||||
|
||||
s32 Common::Debug::OSThreadView::GetEffectivePriority() const
|
||||
{
|
||||
return m_thread.effective_priority;
|
||||
}
|
||||
|
||||
u32 Common::Debug::OSThreadView::GetStackStart() const
|
||||
{
|
||||
return m_thread.stack_addr;
|
||||
}
|
||||
|
||||
u32 Common::Debug::OSThreadView::GetStackEnd() const
|
||||
{
|
||||
return m_thread.stack_end;
|
||||
}
|
||||
|
||||
std::size_t Common::Debug::OSThreadView::GetStackSize() const
|
||||
{
|
||||
return GetStackStart() - GetStackEnd();
|
||||
}
|
||||
|
||||
s32 Common::Debug::OSThreadView::GetErrno() const
|
||||
{
|
||||
return m_thread.error;
|
||||
}
|
||||
|
||||
std::string Common::Debug::OSThreadView::GetSpecific() const
|
||||
{
|
||||
std::string specific;
|
||||
|
||||
for (u32 addr : m_thread.specific)
|
||||
{
|
||||
if (!PowerPC::HostIsRAMAddress(addr))
|
||||
break;
|
||||
specific += fmt::format("{:08x} \"{}\"\n", addr, PowerPC::HostGetString(addr));
|
||||
}
|
||||
|
||||
return specific;
|
||||
}
|
||||
|
||||
bool Common::Debug::OSThreadView::IsValid() const
|
||||
{
|
||||
return m_thread.IsValid();
|
||||
}
|
154
Source/Core/Common/Debug/OSThread.h
Normal file
154
Source/Core/Common/Debug/OSThread.h
Normal file
@ -0,0 +1,154 @@
|
||||
// Copyright 2020 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Debug/Threads.h"
|
||||
|
||||
namespace Common::Debug
|
||||
{
|
||||
template <class C>
|
||||
struct OSQueue
|
||||
{
|
||||
u32 head;
|
||||
u32 tail;
|
||||
};
|
||||
template <class C>
|
||||
struct OSLink
|
||||
{
|
||||
u32 next;
|
||||
u32 prev;
|
||||
};
|
||||
|
||||
struct OSMutex;
|
||||
struct OSThread;
|
||||
|
||||
using OSThreadQueue = OSQueue<OSThread>;
|
||||
using OSThreadLink = OSLink<OSThread>;
|
||||
|
||||
using OSMutexQueue = OSQueue<OSMutex>;
|
||||
using OSMutexLink = OSLink<OSMutex>;
|
||||
|
||||
struct OSContext
|
||||
{
|
||||
enum class State : u16
|
||||
{
|
||||
HasFPU = 1,
|
||||
HasException = 2,
|
||||
};
|
||||
std::array<u32, 32> gpr;
|
||||
u32 cr;
|
||||
u32 lr;
|
||||
u32 ctr;
|
||||
u32 xer;
|
||||
std::array<double, 32> fpr;
|
||||
u64 fpscr;
|
||||
u32 srr0;
|
||||
u32 srr1;
|
||||
u16 dummy;
|
||||
State state;
|
||||
std::array<u32, 8> gqr;
|
||||
u32 psf_padding;
|
||||
std::array<double, 32> psf;
|
||||
|
||||
void Read(u32 addr);
|
||||
};
|
||||
|
||||
static_assert(std::is_trivially_copyable_v<OSContext>);
|
||||
static_assert(std::is_standard_layout_v<OSContext>);
|
||||
static_assert(offsetof(OSContext, cr) == 0x80);
|
||||
static_assert(offsetof(OSContext, fpscr) == 0x190);
|
||||
static_assert(offsetof(OSContext, gqr) == 0x1a4);
|
||||
static_assert(offsetof(OSContext, psf) == 0x1c8);
|
||||
|
||||
struct OSThread
|
||||
{
|
||||
OSContext context;
|
||||
|
||||
u16 state; // Thread state (ready, running, waiting, moribund)
|
||||
u16 is_detached; // Is thread detached
|
||||
s32 suspend; // Suspended if greater than zero
|
||||
s32 effective_priority; // Effective priority
|
||||
s32 base_priority; // Base priority
|
||||
u32 exit_code_addr; // Exit value address
|
||||
|
||||
u32 queue_addr; // Address of the queue the thread is on
|
||||
OSThreadLink queue_link; // Used to traverse the thread queue
|
||||
// OSSleepThread uses it to insert the current thread at the end of the thread queue
|
||||
|
||||
OSThreadQueue join_queue; // Threads waiting to be joined
|
||||
|
||||
u32 mutex_addr; // Mutex waiting
|
||||
OSMutexQueue mutex_queue; // Mutex owned
|
||||
|
||||
OSThreadLink thread_link; // Link containing all active threads
|
||||
|
||||
// The STACK_MAGIC is written at stack_end
|
||||
u32 stack_addr;
|
||||
u32 stack_end;
|
||||
|
||||
s32 error; // errno value
|
||||
std::array<u32, 2> specific; // Pointers to data (can be used to store thread names)
|
||||
|
||||
static constexpr u32 STACK_MAGIC = 0xDEADBABE;
|
||||
void Read(u32 addr);
|
||||
bool IsValid() const;
|
||||
};
|
||||
|
||||
static_assert(std::is_trivially_copyable_v<OSThread>);
|
||||
static_assert(std::is_standard_layout_v<OSThread>);
|
||||
static_assert(offsetof(OSThread, state) == 0x2c8);
|
||||
static_assert(offsetof(OSThread, mutex_addr) == 0x2f0);
|
||||
static_assert(offsetof(OSThread, stack_addr) == 0x304);
|
||||
static_assert(offsetof(OSThread, specific) == 0x310);
|
||||
|
||||
struct OSMutex
|
||||
{
|
||||
OSThreadQueue thread_queue; // Threads waiting to own the mutex
|
||||
u32 owner_addr; // Thread owning the mutex
|
||||
s32 lock_count; // Mutex lock count
|
||||
OSMutexLink link; // Used to traverse the thread's mutex queue
|
||||
// OSLockMutex uses it to insert the acquired mutex at the end of the queue
|
||||
|
||||
void Read(u32 addr);
|
||||
};
|
||||
|
||||
static_assert(std::is_trivially_copyable_v<OSMutex>);
|
||||
static_assert(std::is_standard_layout_v<OSMutex>);
|
||||
static_assert(offsetof(OSMutex, owner_addr) == 0x8);
|
||||
static_assert(offsetof(OSMutex, link) == 0x10);
|
||||
|
||||
class OSThreadView : public Common::Debug::ThreadView
|
||||
{
|
||||
public:
|
||||
explicit OSThreadView(u32 addr);
|
||||
~OSThreadView() = default;
|
||||
|
||||
const OSThread& Data() const;
|
||||
|
||||
PartialContext GetContext() const override;
|
||||
u32 GetAddress() const override;
|
||||
u16 GetState() const override;
|
||||
bool IsSuspended() const override;
|
||||
bool IsDetached() const override;
|
||||
s32 GetBasePriority() const override;
|
||||
s32 GetEffectivePriority() const override;
|
||||
u32 GetStackStart() const override;
|
||||
u32 GetStackEnd() const override;
|
||||
std::size_t GetStackSize() const override;
|
||||
s32 GetErrno() const override;
|
||||
std::string GetSpecific() const override;
|
||||
bool IsValid() const override;
|
||||
|
||||
private:
|
||||
u32 m_address = 0;
|
||||
OSThread m_thread;
|
||||
};
|
||||
|
||||
} // namespace Common::Debug
|
63
Source/Core/Common/Debug/Threads.h
Normal file
63
Source/Core/Common/Debug/Threads.h
Normal file
@ -0,0 +1,63 @@
|
||||
// Copyright 2020 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
namespace Common::Debug
|
||||
{
|
||||
struct PartialContext
|
||||
{
|
||||
std::optional<std::array<u32, 32>> gpr;
|
||||
std::optional<u32> cr;
|
||||
std::optional<u32> lr;
|
||||
std::optional<u32> ctr;
|
||||
std::optional<u32> xer;
|
||||
std::optional<std::array<double, 32>> fpr;
|
||||
std::optional<u64> fpscr;
|
||||
std::optional<u32> srr0;
|
||||
std::optional<u32> srr1;
|
||||
std::optional<u16> dummy;
|
||||
std::optional<u16> state;
|
||||
std::optional<std::array<u32, 8>> gqr;
|
||||
std::optional<std::array<double, 32>> psf;
|
||||
};
|
||||
|
||||
class ThreadView
|
||||
{
|
||||
public:
|
||||
virtual ~ThreadView() = default;
|
||||
|
||||
enum class API
|
||||
{
|
||||
OSThread, // Nintendo SDK thread
|
||||
LWPThread, // devkitPro libogc thread
|
||||
};
|
||||
|
||||
virtual PartialContext GetContext() const = 0;
|
||||
virtual u32 GetAddress() const = 0;
|
||||
virtual u16 GetState() const = 0;
|
||||
virtual bool IsSuspended() const = 0;
|
||||
virtual bool IsDetached() const = 0;
|
||||
virtual s32 GetBasePriority() const = 0;
|
||||
virtual s32 GetEffectivePriority() const = 0;
|
||||
virtual u32 GetStackStart() const = 0;
|
||||
virtual u32 GetStackEnd() const = 0;
|
||||
virtual std::size_t GetStackSize() const = 0;
|
||||
virtual s32 GetErrno() const = 0;
|
||||
// Implementation specific, used to store arbitrary data
|
||||
virtual std::string GetSpecific() const = 0;
|
||||
virtual bool IsValid() const = 0;
|
||||
};
|
||||
|
||||
using Threads = std::vector<std::unique_ptr<ThreadView>>;
|
||||
|
||||
} // namespace Common::Debug
|
Reference in New Issue
Block a user