Debugger: Add a Thread widget

DebugInterface: Add GetThreads

WatchWidget: Update widget on AddWatch
This commit is contained in:
Sepalani
2020-03-21 11:48:49 +04:00
parent 9ffa72ad1f
commit 39d34e133f
24 changed files with 1090 additions and 12 deletions

View 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();
}

View 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

View 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