Implement thread listing for IOP

Abstracts away threads behind a common interface for both EE and IOP
This commit is contained in:
Ziemas 2021-10-08 13:12:03 +02:00 committed by refractionpcsx2
parent 7198c6b8c6
commit 9420615317
11 changed files with 310 additions and 83 deletions

View File

@ -107,18 +107,15 @@ QVariant StackModel::headerData(int section, Qt::Orientation orientation, int ro
void StackModel::refreshData()
{
if (m_cpu.getCpuType() == BREAKPOINT_IOP)
return;
// Hopefully in the near future we can get a stack frame for
// each thread
beginResetModel();
for (const auto& thread : getEEThreads())
for (const auto& thread : m_cpu.GetThreadList())
{
if (thread.data.status == THS_RUN)
if (thread->Status() == ThreadStatus::THS_RUN)
{
m_stackFrames = MipsStackWalk::Walk(&m_cpu, m_cpu.getPC(), m_cpu.getRegister(0, 31), m_cpu.getRegister(0, 29),
thread.data.entry_init, thread.data.stack);
thread->EntryPoint(), thread->StackTop());
break;
}
}

View File

@ -27,10 +27,7 @@ ThreadModel::ThreadModel(DebugInterface& cpu, QObject* parent)
int ThreadModel::rowCount(const QModelIndex&) const
{
if (m_cpu.getCpuType() == BREAKPOINT_EE)
return getEEThreads().size();
else
return 0;
return m_cpu.GetThreadList().size();
}
int ThreadModel::columnCount(const QModelIndex&) const
@ -40,66 +37,65 @@ int ThreadModel::columnCount(const QModelIndex&) const
QVariant ThreadModel::data(const QModelIndex& index, int role) const
{
const auto threads = m_cpu.GetThreadList();
auto* const thread = threads.at(index.row()).get();
if (role == Qt::DisplayRole)
{
const auto thread = getEEThreads().at(index.row());
switch (index.column())
{
case ThreadModel::ID:
return thread.tid;
return thread->TID();
case ThreadModel::PC:
{
if (thread.data.status == THS_RUN)
if (thread->Status() == ThreadStatus::THS_RUN)
return QtUtils::FilledQStringFromValue(m_cpu.getPC(), 16);
else
return QtUtils::FilledQStringFromValue(thread.data.entry, 16);
return QtUtils::FilledQStringFromValue(thread->PC(), 16);
}
case ThreadModel::ENTRY:
return QtUtils::FilledQStringFromValue(thread.data.entry_init, 16);
return QtUtils::FilledQStringFromValue(thread->EntryPoint(), 16);
case ThreadModel::PRIORITY:
return QString::number(thread.data.currentPriority);
return QString::number(thread->Priority());
case ThreadModel::STATE:
{
const auto& state = ThreadStateStrings.find(thread.data.status);
const auto& state = ThreadStateStrings.find(thread->Status());
if (state != ThreadStateStrings.end())
return state->second;
else
return tr("INVALID");
return tr("INVALID");
}
case ThreadModel::WAIT_TYPE:
{
const auto& waitType = ThreadWaitStrings.find(thread.data.waitType);
const auto& waitType = ThreadWaitStrings.find(thread->Wait());
if (waitType != ThreadWaitStrings.end())
return waitType->second;
else
return tr("INVALID");
return tr("INVALID");
}
}
}
else if (role == Qt::UserRole)
{
const auto thread = getEEThreads().at(index.row());
switch (index.column())
{
case ThreadModel::ID:
return thread.tid;
return thread->TID();
case ThreadModel::PC:
{
if (thread.data.status == THS_RUN)
if (thread->Status() == ThreadStatus::THS_RUN)
return m_cpu.getPC();
else
return thread.data.entry;
return thread->PC();
}
case ThreadModel::ENTRY:
return thread.data.entry_init;
return thread->EntryPoint();
case ThreadModel::PRIORITY:
return thread.data.currentPriority;
return thread->Priority();
case ThreadModel::STATE:
return thread.data.status;
return static_cast<u32>(thread->Status());
case ThreadModel::WAIT_TYPE:
return thread.data.waitType;
return static_cast<u32>(thread->Wait());
default:
return QVariant();
}

View File

@ -48,19 +48,27 @@ public:
void refreshData();
private:
const std::map<int, QString> ThreadStateStrings{
{THS_BAD, tr("BAD")},
{THS_RUN, tr("RUN")},
{THS_READY, tr("READY")},
{THS_WAIT, tr("WAIT")},
{THS_SUSPEND, tr("SUSPEND")},
{THS_WAIT_SUSPEND, tr("WAIT SUSPEND")},
{THS_DORMANT, tr("DORMANT")}};
const std::map<ThreadStatus, QString> ThreadStateStrings{
{ThreadStatus::THS_BAD, tr("BAD")},
{ThreadStatus::THS_RUN, tr("RUN")},
{ThreadStatus::THS_READY, tr("READY")},
{ThreadStatus::THS_WAIT, tr("WAIT")},
{ThreadStatus::THS_SUSPEND, tr("SUSPEND")},
{ThreadStatus::THS_WAIT_SUSPEND, tr("WAIT SUSPEND")},
{ThreadStatus::THS_DORMANT, tr("DORMANT")},
};
const std::map<int, QString> ThreadWaitStrings{
{WAIT_NONE, tr("NONE")},
{WAIT_WAKEUP_REQ, tr("WAKEUP REQUEST")},
{WAIT_SEMA, tr("SEMAPHORE")}};
const std::map<WaitState, QString> ThreadWaitStrings{
{WaitState::NONE, tr("NONE")},
{WaitState::WAKEUP_REQ, tr("WAKEUP REQUEST")},
{WaitState::SEMA, tr("SEMAPHORE")},
{WaitState::SLEEP, tr("SLEEP")},
{WaitState::DELAY, tr("DELAY")},
{WaitState::EVENTFLAG, tr("EVENTFLAG")},
{WaitState::MBOX, tr("MBOX")},
{WaitState::VPOOL, tr("VPOOL")},
{WaitState::FIXPOOL, tr("FIXPOOL")},
};
DebugInterface& m_cpu;
};

View File

@ -15,29 +15,70 @@
#include "PrecompiledHeader.h"
#include "BiosDebugData.h"
#include "IopMem.h"
#include "Memory.h"
std::vector<EEThread> getEEThreads()
{
std::vector<EEThread> threads;
if (CurrentBiosInformation.threadListAddr <= 0)
std::vector<std::unique_ptr<BiosThread>> getEEThreads()
{
std::vector<std::unique_ptr<BiosThread>> threads;
if (CurrentBiosInformation.eeThreadListAddr <= 0)
return threads;
const u32 start = CurrentBiosInformation.threadListAddr & 0x3fffff;
const u32 start = CurrentBiosInformation.eeThreadListAddr & 0x3fffff;
for (int tid = 0; tid < 256; tid++)
{
EEThread thread;
EEInternalThread* internal = static_cast<EEInternalThread*>(PSM(start + tid * sizeof(EEInternalThread)));
if (internal->status != THS_BAD)
if (internal->status != (int)ThreadStatus::THS_BAD)
{
thread.tid = tid;
thread.data = *internal;
threads.push_back(thread);
auto thread = std::make_unique<EEThread>(tid, *internal);
threads.push_back(std::move(thread));
}
}
return threads;
}
std::vector<std::unique_ptr<BiosThread>> getIOPThreads()
{
std::vector<std::unique_ptr<BiosThread>> threads;
if (CurrentBiosInformation.iopThreadListAddr <= 0)
return threads;
u32 item = iopMemRead32(CurrentBiosInformation.iopThreadListAddr);
while (item != 0)
{
IOPInternalThread data{};
u16 tag = iopMemRead16(item + 0x8);
if (tag != 0x7f01)
{
// something went wrong
return {};
}
data.stackTop = iopMemRead32(item + 0x3c);
data.status = iopMemRead8(item + 0xc);
data.tid = iopMemRead16(item + 0xa);
data.entrypoint = iopMemRead32(item + 0x38);
data.waitstate = iopMemRead16(item + 0xe);
data.initPriority = iopMemRead16(item + 0x2e);
data.SavedSP = iopMemRead32(item + 0x10);
data.PC = iopMemRead32(data.SavedSP + 0x8c);
auto thread = std::make_unique<IOPThread>(data);
threads.push_back(std::move(thread));
item = iopMemRead32(item + 0x24);
}
return threads;
}

View File

@ -15,9 +15,24 @@
#pragma once
#include "common/Pcsx2Types.h"
#include <memory>
#include <vector>
#include "ps2/BiosTools.h"
struct EEInternalThread { // internal struct
enum class ThreadStatus
{
THS_BAD = 0x00,
THS_RUN = 0x01,
THS_READY = 0x02,
THS_WAIT = 0x04,
THS_SUSPEND = 0x08,
THS_WAIT_SUSPEND = 0x0C,
THS_DORMANT = 0x10,
};
struct EEInternalThread
{ // internal struct
u32 prev;
u32 next;
int status;
@ -40,26 +55,139 @@ struct EEInternalThread { // internal struct
u32 heap_base;
};
enum {
THS_BAD = 0x00,
THS_RUN = 0x01,
THS_READY = 0x02,
THS_WAIT = 0x04,
THS_SUSPEND = 0x08,
THS_WAIT_SUSPEND = 0x0C,
THS_DORMANT = 0x10,
};
enum {
WAIT_NONE = 0,
WAIT_WAKEUP_REQ = 1,
WAIT_SEMA = 2,
};
struct EEThread
// Not the full struct, just what we care about
struct IOPInternalThread
{
int tid;
u32 tid;
u32 PC;
u32 stackTop;
u32 SavedSP;
u32 status;
u32 entrypoint;
u32 waitstate;
u32 initPriority;
};
enum class IOPWaitStatus
{
TSW_SLEEP = 1,
TSW_DELAY = 2,
TSW_SEMA = 3,
TSW_EVENTFLAG = 4,
TSW_MBX = 5,
TSW_VPL = 6,
TSW_FPL = 7,
};
enum class EEWaitStatus
{
WAIT_NONE = 0,
WAIT_WAKEUP_REQ = 1,
WAIT_SEMA = 2,
};
enum class WaitState
{
NONE,
WAKEUP_REQ,
SEMA,
SLEEP,
DELAY,
EVENTFLAG,
MBOX,
VPOOL,
FIXPOOL,
};
class BiosThread
{
public:
virtual ~BiosThread() = default;
[[nodiscard]] virtual u32 TID() const = 0;
[[nodiscard]] virtual u32 PC() const = 0;
[[nodiscard]] virtual ThreadStatus Status() const = 0;
[[nodiscard]] virtual WaitState Wait() const = 0;
[[nodiscard]] virtual u32 EntryPoint() const = 0;
[[nodiscard]] virtual u32 StackTop() const = 0;
[[nodiscard]] virtual u32 Priority() const = 0;
};
class EEThread : public BiosThread
{
public:
EEThread(int tid, EEInternalThread th)
: tid(tid)
, data(th)
{
}
~EEThread() override = default;
[[nodiscard]] u32 TID() const override { return tid; };
[[nodiscard]] u32 PC() const override { return data.entry; };
[[nodiscard]] ThreadStatus Status() const override { return static_cast<ThreadStatus>(data.status); };
[[nodiscard]] WaitState Wait() const override
{
auto wait = static_cast<EEWaitStatus>(data.waitType);
switch (wait)
{
case EEWaitStatus::WAIT_NONE:
return WaitState::NONE;
case EEWaitStatus::WAIT_WAKEUP_REQ:
return WaitState::WAKEUP_REQ;
case EEWaitStatus::WAIT_SEMA:
return WaitState::SEMA;
}
};
[[nodiscard]] u32 EntryPoint() const override { return data.entry_init; };
[[nodiscard]] u32 StackTop() const override { return data.stack; };
[[nodiscard]] u32 Priority() const override { return data.currentPriority; };
private:
u32 tid;
EEInternalThread data;
};
std::vector<EEThread> getEEThreads();
class IOPThread : public BiosThread
{
public:
IOPThread(IOPInternalThread th)
: data(th)
{
}
~IOPThread() override = default;
[[nodiscard]] u32 TID() const override { return data.tid; };
[[nodiscard]] u32 PC() const override { return data.PC; };
[[nodiscard]] ThreadStatus Status() const override { return static_cast<ThreadStatus>(data.status); };
[[nodiscard]] WaitState Wait() const override
{
auto wait = static_cast<IOPWaitStatus>(data.waitstate);
switch (wait)
{
case IOPWaitStatus::TSW_DELAY:
return WaitState::DELAY;
case IOPWaitStatus::TSW_EVENTFLAG:
return WaitState::EVENTFLAG;
case IOPWaitStatus::TSW_SLEEP:
return WaitState::SLEEP;
case IOPWaitStatus::TSW_SEMA:
return WaitState::SEMA;
case IOPWaitStatus::TSW_MBX:
return WaitState::MBOX;
case IOPWaitStatus::TSW_VPL:
return WaitState::VPOOL;
case IOPWaitStatus::TSW_FPL:
return WaitState::FIXPOOL;
}
return WaitState::NONE;
};
[[nodiscard]] u32 EntryPoint() const override { return data.entrypoint; };
[[nodiscard]] u32 StackTop() const override { return data.stackTop; };
[[nodiscard]] u32 Priority() const override { return data.initPriority; };
private:
IOPInternalThread data;
};
std::vector<std::unique_ptr<BiosThread>> getIOPThreads();
std::vector<std::unique_ptr<BiosThread>> getEEThreads();

View File

@ -728,6 +728,12 @@ SymbolMap& R5900DebugInterface::GetSymbolMap() const
return R5900SymbolMap;
}
std::vector<std::unique_ptr<BiosThread>> R5900DebugInterface::GetThreadList() const
{
return getEEThreads();
}
//
// R3000DebugInterface
//
@ -1000,3 +1006,8 @@ SymbolMap& R3000DebugInterface::GetSymbolMap() const
{
return R3000SymbolMap;
}
std::vector<std::unique_ptr<BiosThread>> R3000DebugInterface::GetThreadList() const
{
return getIOPThreads();
}

View File

@ -14,6 +14,7 @@
*/
#pragma once
#include "DebugTools/BiosDebugData.h"
#include "MemoryTypes.h"
#include "ExpressionParser.h"
#include "SymbolMap.h"
@ -84,6 +85,7 @@ public:
virtual u32 getCycles() = 0;
virtual BreakPointCpu getCpuType() = 0;
[[nodiscard]] virtual SymbolMap& GetSymbolMap() const = 0;
[[nodiscard]] virtual std::vector<std::unique_ptr<BiosThread>> GetThreadList() const = 0;
bool initExpression(const char* exp, PostfixExpression& dest);
bool parseExpression(PostfixExpression& exp, u64& dest);
@ -130,6 +132,7 @@ public:
void setPc(u32 newPc) override;
void setRegister(int cat, int num, u128 newValue) override;
[[nodiscard]] SymbolMap& GetSymbolMap() const override;
[[nodiscard]] std::vector<std::unique_ptr<BiosThread>> GetThreadList() const override;
std::string disasm(u32 address, bool simplify) override;
bool isValidAddress(u32 address) override;
@ -168,6 +171,7 @@ public:
void setPc(u32 newPc) override;
void setRegister(int cat, int num, u128 newValue) override;
[[nodiscard]] SymbolMap& GetSymbolMap() const override;
[[nodiscard]] std::vector<std::unique_ptr<BiosThread>> GetThreadList() const override;
std::string disasm(u32 address, bool simplify) override;
bool isValidAddress(u32 address) override;

View File

@ -20,8 +20,12 @@
#include "R5900.h" // for g_GameStarted
#include "IopBios.h"
#include "IopMem.h"
#include "iR3000A.h"
#include "ps2/BiosTools.h"
#include "DebugTools/SymbolMap.h"
#include <ctype.h>
#include <fmt/format.h>
#include <string.h>
#include <sys/stat.h>
#include "common/FileSystem.h"
@ -983,6 +987,37 @@ namespace R3000A
namespace loadcore
{
// Gets the thread list ptr from thbase
u32 GetThreadList(u32 a0reg, u32 version)
{
// Function 3 returns the main thread manager struct
u32 function = iopMemRead32(a0reg + 0x20);
// read the lui
u32 thstruct = (iopMemRead32(function) & 0xFFFF) << 16;
thstruct |= iopMemRead32(function + 4) & 0xFFFF;
u32 list = thstruct + 0x42c;
if (version > 0x101)
list = thstruct + 0x430;
return list;
}
int RegisterLibraryEntries_HLE()
{
const std::string modname = iopMemReadString(a0 + 12);
if (modname == "thbase")
{
const u32 version = iopMemRead32(a0 + 8);
CurrentBiosInformation.iopThreadListAddr = GetThreadList(a0, version);
}
return 0;
}
void RegisterLibraryEntries_DEBUG()
{
const std::string modname = iopMemReadString(a0 + 12);
@ -1093,6 +1128,11 @@ namespace R3000A
EXPORT_H( 14, Kprintf)
END_MODULE
// For grabbing the thread list from thbase
MODULE(loadcore)
EXPORT_H( 6, RegisterLibraryEntries)
END_MODULE
// Special case with ioman and iomanX
// They are mostly compatible excluding stat structures
if(libname == "ioman" || libname == "iomanx")

View File

@ -1012,7 +1012,7 @@ void SYSCALL()
case Syscall::StartThread:
case Syscall::ChangeThreadPriority:
{
if (CurrentBiosInformation.threadListAddr == 0)
if (CurrentBiosInformation.eeThreadListAddr == 0)
{
u32 offset = 0x0;
// Suprisingly not that slow :)
@ -1030,16 +1030,16 @@ void SYSCALL()
// We've found the instruction pattern!
// We (well, I) know that the thread address is always 0x8001 + the immediate of the 6th instruction from here
const u32 op = memRead32(0x80000000 + offset + (sizeof(u32) * 6));
CurrentBiosInformation.threadListAddr = 0x80010000 + static_cast<u16>(op) - 8; // Subtract 8 because the address here is offset by 8.
DevCon.WriteLn("BIOS: Successfully found the instruction pattern. Assuming the thread list is here: %0x", CurrentBiosInformation.threadListAddr);
CurrentBiosInformation.eeThreadListAddr = 0x80010000 + static_cast<u16>(op) - 8; // Subtract 8 because the address here is offset by 8.
DevCon.WriteLn("BIOS: Successfully found the instruction pattern. Assuming the thread list is here: %0x", CurrentBiosInformation.eeThreadListAddr);
break;
}
offset += 4;
}
if (!CurrentBiosInformation.threadListAddr)
if (!CurrentBiosInformation.eeThreadListAddr)
{
// We couldn't find the address
CurrentBiosInformation.threadListAddr = -1;
CurrentBiosInformation.eeThreadListAddr = -1;
// If you're here because a user has reported this message, this means that the instruction pattern is not present on their bios, or it is aligned weirdly.
Console.Warning("BIOS Warning: Unable to get a thread list offset. The debugger thread and stack frame views will not be functional.");
}

View File

@ -311,7 +311,7 @@ bool LoadBIOS()
if (EmuConfig.CurrentIRX.length() > 3)
LoadIrx(EmuConfig.CurrentIRX, &eeMem->ROM[0x3C0000], sizeof(eeMem->ROM) - 0x3C0000);
CurrentBiosInformation.threadListAddr = 0;
CurrentBiosInformation.eeThreadListAddr = 0;
return true;
}

View File

@ -25,7 +25,9 @@ const u32 ThreadListInstructions[3] =
struct BiosDebugInformation
{
u32 threadListAddr;
u32 eeThreadListAddr;
u32 iopThreadListAddr;
u32 iopModListAddr;
};
extern BiosDebugInformation CurrentBiosInformation;