From 94206153179cdadb4d1eb4a31332395dea9fd80d Mon Sep 17 00:00:00 2001 From: Ziemas Date: Fri, 8 Oct 2021 13:12:03 +0200 Subject: [PATCH] Implement thread listing for IOP Abstracts away threads behind a common interface for both EE and IOP --- pcsx2-qt/Debugger/Models/StackModel.cpp | 9 +- pcsx2-qt/Debugger/Models/ThreadModel.cpp | 52 ++++--- pcsx2-qt/Debugger/Models/ThreadModel.h | 32 +++-- pcsx2/DebugTools/BiosDebugData.cpp | 61 ++++++-- pcsx2/DebugTools/BiosDebugData.h | 168 ++++++++++++++++++++--- pcsx2/DebugTools/DebugInterface.cpp | 11 ++ pcsx2/DebugTools/DebugInterface.h | 4 + pcsx2/IopBios.cpp | 40 ++++++ pcsx2/R5900OpcodeImpl.cpp | 10 +- pcsx2/ps2/BiosTools.cpp | 2 +- pcsx2/ps2/BiosTools.h | 4 +- 11 files changed, 310 insertions(+), 83 deletions(-) diff --git a/pcsx2-qt/Debugger/Models/StackModel.cpp b/pcsx2-qt/Debugger/Models/StackModel.cpp index 2f79ae0d98..ff11fa16bb 100644 --- a/pcsx2-qt/Debugger/Models/StackModel.cpp +++ b/pcsx2-qt/Debugger/Models/StackModel.cpp @@ -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; } } diff --git a/pcsx2-qt/Debugger/Models/ThreadModel.cpp b/pcsx2-qt/Debugger/Models/ThreadModel.cpp index bf8b5789e7..65c16ce008 100644 --- a/pcsx2-qt/Debugger/Models/ThreadModel.cpp +++ b/pcsx2-qt/Debugger/Models/ThreadModel.cpp @@ -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(thread->Status()); case ThreadModel::WAIT_TYPE: - return thread.data.waitType; + return static_cast(thread->Wait()); default: return QVariant(); } diff --git a/pcsx2-qt/Debugger/Models/ThreadModel.h b/pcsx2-qt/Debugger/Models/ThreadModel.h index 7f34e13560..6d5f2d6c9b 100644 --- a/pcsx2-qt/Debugger/Models/ThreadModel.h +++ b/pcsx2-qt/Debugger/Models/ThreadModel.h @@ -48,19 +48,27 @@ public: void refreshData(); private: - const std::map 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 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 ThreadWaitStrings{ - {WAIT_NONE, tr("NONE")}, - {WAIT_WAKEUP_REQ, tr("WAKEUP REQUEST")}, - {WAIT_SEMA, tr("SEMAPHORE")}}; + const std::map 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; }; diff --git a/pcsx2/DebugTools/BiosDebugData.cpp b/pcsx2/DebugTools/BiosDebugData.cpp index b0f381290d..b9b01f843d 100644 --- a/pcsx2/DebugTools/BiosDebugData.cpp +++ b/pcsx2/DebugTools/BiosDebugData.cpp @@ -15,29 +15,70 @@ #include "PrecompiledHeader.h" #include "BiosDebugData.h" +#include "IopMem.h" #include "Memory.h" -std::vector getEEThreads() -{ - std::vector threads; - if (CurrentBiosInformation.threadListAddr <= 0) +std::vector> getEEThreads() +{ + std::vector> 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(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(tid, *internal); + threads.push_back(std::move(thread)); } } + return threads; +} + +std::vector> getIOPThreads() +{ + std::vector> 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(data); + threads.push_back(std::move(thread)); + + item = iopMemRead32(item + 0x24); + } + + return threads; } diff --git a/pcsx2/DebugTools/BiosDebugData.h b/pcsx2/DebugTools/BiosDebugData.h index c474634244..13495b02ac 100644 --- a/pcsx2/DebugTools/BiosDebugData.h +++ b/pcsx2/DebugTools/BiosDebugData.h @@ -15,9 +15,24 @@ #pragma once #include "common/Pcsx2Types.h" +#include +#include #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(data.status); }; + [[nodiscard]] WaitState Wait() const override + { + auto wait = static_cast(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 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(data.status); }; + [[nodiscard]] WaitState Wait() const override + { + auto wait = static_cast(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> getIOPThreads(); +std::vector> getEEThreads(); diff --git a/pcsx2/DebugTools/DebugInterface.cpp b/pcsx2/DebugTools/DebugInterface.cpp index c1e802404d..6d8bdbb8ad 100644 --- a/pcsx2/DebugTools/DebugInterface.cpp +++ b/pcsx2/DebugTools/DebugInterface.cpp @@ -728,6 +728,12 @@ SymbolMap& R5900DebugInterface::GetSymbolMap() const return R5900SymbolMap; } +std::vector> R5900DebugInterface::GetThreadList() const +{ + return getEEThreads(); +} + + // // R3000DebugInterface // @@ -1000,3 +1006,8 @@ SymbolMap& R3000DebugInterface::GetSymbolMap() const { return R3000SymbolMap; } + +std::vector> R3000DebugInterface::GetThreadList() const +{ + return getIOPThreads(); +} diff --git a/pcsx2/DebugTools/DebugInterface.h b/pcsx2/DebugTools/DebugInterface.h index 299ecd11c9..24a8bdaf8f 100644 --- a/pcsx2/DebugTools/DebugInterface.h +++ b/pcsx2/DebugTools/DebugInterface.h @@ -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> 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> 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> GetThreadList() const override; std::string disasm(u32 address, bool simplify) override; bool isValidAddress(u32 address) override; diff --git a/pcsx2/IopBios.cpp b/pcsx2/IopBios.cpp index df398d5262..2bb71728a0 100644 --- a/pcsx2/IopBios.cpp +++ b/pcsx2/IopBios.cpp @@ -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 +#include #include #include #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") diff --git a/pcsx2/R5900OpcodeImpl.cpp b/pcsx2/R5900OpcodeImpl.cpp index 2c9f087c25..5c95a7e20b 100644 --- a/pcsx2/R5900OpcodeImpl.cpp +++ b/pcsx2/R5900OpcodeImpl.cpp @@ -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(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(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."); } diff --git a/pcsx2/ps2/BiosTools.cpp b/pcsx2/ps2/BiosTools.cpp index cb02828ac0..8af51db6f8 100644 --- a/pcsx2/ps2/BiosTools.cpp +++ b/pcsx2/ps2/BiosTools.cpp @@ -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; } diff --git a/pcsx2/ps2/BiosTools.h b/pcsx2/ps2/BiosTools.h index 60173bb998..5ef31c6d01 100644 --- a/pcsx2/ps2/BiosTools.h +++ b/pcsx2/ps2/BiosTools.h @@ -25,7 +25,9 @@ const u32 ThreadListInstructions[3] = struct BiosDebugInformation { - u32 threadListAddr; + u32 eeThreadListAddr; + u32 iopThreadListAddr; + u32 iopModListAddr; }; extern BiosDebugInformation CurrentBiosInformation;