wip
This commit is contained in:
parent
cfd1d41ced
commit
0206d9b2d0
|
@ -1,16 +1,21 @@
|
||||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com> and contributors.
|
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com> and contributors.
|
||||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||||
|
|
||||||
#include "bios.h"
|
#include "bios.h"
|
||||||
|
|
||||||
|
#include "bus.h"
|
||||||
|
#include "cpu_disasm.h"
|
||||||
|
#include "host.h"
|
||||||
|
#include "settings.h"
|
||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/file_system.h"
|
#include "common/file_system.h"
|
||||||
#include "common/log.h"
|
#include "common/log.h"
|
||||||
#include "common/md5_digest.h"
|
#include "common/md5_digest.h"
|
||||||
#include "common/path.h"
|
#include "common/path.h"
|
||||||
#include "cpu_disasm.h"
|
|
||||||
#include "host.h"
|
|
||||||
#include "settings.h"
|
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
|
|
||||||
Log_SetChannel(BIOS);
|
Log_SetChannel(BIOS);
|
||||||
|
|
||||||
static constexpr BIOS::Hash MakeHashFromString(const char str[])
|
static constexpr BIOS::Hash MakeHashFromString(const char str[])
|
||||||
|
@ -426,3 +431,47 @@ bool BIOS::HasAnyBIOSImages()
|
||||||
{
|
{
|
||||||
return FindBIOSImageInDirectory(ConsoleRegion::Auto, EmuFolders::Bios.c_str()).has_value();
|
return FindBIOSImageInDirectory(ConsoleRegion::Auto, EmuFolders::Bios.c_str()).has_value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::span<const BIOS::ThreadControlBlock> BIOS::GetTCBs()
|
||||||
|
{
|
||||||
|
if (!Bus::g_ram)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
u32 base_address, num_threads;
|
||||||
|
std::memcpy(&base_address, &Bus::g_ram[0x110], sizeof(base_address));
|
||||||
|
std::memcpy(&num_threads, &Bus::g_ram[0x114], sizeof(num_threads));
|
||||||
|
|
||||||
|
base_address &= CPU::PHYSICAL_MEMORY_ADDRESS_MASK;
|
||||||
|
num_threads /= sizeof(ThreadControlBlock);
|
||||||
|
|
||||||
|
if (base_address == 0 || num_threads == 0 ||
|
||||||
|
(base_address + num_threads * sizeof(ThreadControlBlock)) > Bus::RAM_2MB_SIZE)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::span<const ThreadControlBlock>(reinterpret_cast<const ThreadControlBlock*>(&Bus::g_ram[base_address]),
|
||||||
|
num_threads);
|
||||||
|
}
|
||||||
|
|
||||||
|
const BIOS::ThreadControlBlock* BIOS::GetCurrentThreadTCB()
|
||||||
|
{
|
||||||
|
if (!Bus::g_ram)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
u32 pcb_base_address;
|
||||||
|
std::memcpy(&pcb_base_address, &Bus::g_ram[0x108], sizeof(pcb_base_address));
|
||||||
|
pcb_base_address &= CPU::PHYSICAL_MEMORY_ADDRESS_MASK;
|
||||||
|
|
||||||
|
if (pcb_base_address == 0 || (pcb_base_address + sizeof(u32)) > Bus::RAM_2MB_SIZE)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
u32 tcb_address;
|
||||||
|
std::memcpy(&tcb_address, &Bus::g_ram[pcb_base_address], sizeof(tcb_address));
|
||||||
|
tcb_address &= CPU::PHYSICAL_MEMORY_ADDRESS_MASK;
|
||||||
|
|
||||||
|
if (tcb_address == 0 || (tcb_address + sizeof(ThreadControlBlock)) > Bus::RAM_2MB_SIZE)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return reinterpret_cast<const ThreadControlBlock*>(&Bus::g_ram[tcb_address]);
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>.
|
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>.
|
||||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <span>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -88,4 +89,22 @@ std::vector<std::pair<std::string, const BIOS::ImageInfo*>> FindBIOSImagesInDire
|
||||||
|
|
||||||
/// Returns true if any BIOS images are found in the configured BIOS directory.
|
/// Returns true if any BIOS images are found in the configured BIOS directory.
|
||||||
bool HasAnyBIOSImages();
|
bool HasAnyBIOSImages();
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct ThreadControlBlock
|
||||||
|
{
|
||||||
|
u32 status;
|
||||||
|
u32 unused;
|
||||||
|
u32 regs[32];
|
||||||
|
u32 epc;
|
||||||
|
u32 hi;
|
||||||
|
u32 lo;
|
||||||
|
u32 sr;
|
||||||
|
u32 cause;
|
||||||
|
u32 unused2[9];
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
std::span<const ThreadControlBlock> GetTCBs();
|
||||||
|
const ThreadControlBlock* GetCurrentThreadTCB();
|
||||||
} // namespace BIOS
|
} // namespace BIOS
|
|
@ -31,6 +31,7 @@
|
||||||
<ClCompile Include="cpu_recompiler_register_cache.cpp">
|
<ClCompile Include="cpu_recompiler_register_cache.cpp">
|
||||||
<ExcludedFromBuild Condition="'$(Platform)'=='Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Platform)'=='Win32'">true</ExcludedFromBuild>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="cpu_stack_walk.cpp" />
|
||||||
<ClCompile Include="cpu_types.cpp" />
|
<ClCompile Include="cpu_types.cpp" />
|
||||||
<ClCompile Include="digital_controller.cpp" />
|
<ClCompile Include="digital_controller.cpp" />
|
||||||
<ClCompile Include="fullscreen_ui.cpp" />
|
<ClCompile Include="fullscreen_ui.cpp" />
|
||||||
|
@ -101,6 +102,7 @@
|
||||||
<ClInclude Include="cpu_recompiler_types.h">
|
<ClInclude Include="cpu_recompiler_types.h">
|
||||||
<ExcludedFromBuild Condition="'$(Platform)'=='Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Platform)'=='Win32'">true</ExcludedFromBuild>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="cpu_stack_walk.h" />
|
||||||
<ClInclude Include="digital_controller.h" />
|
<ClInclude Include="digital_controller.h" />
|
||||||
<ClInclude Include="fullscreen_ui.h" />
|
<ClInclude Include="fullscreen_ui.h" />
|
||||||
<ClInclude Include="game_database.h" />
|
<ClInclude Include="game_database.h" />
|
||||||
|
|
|
@ -60,6 +60,7 @@
|
||||||
<ClCompile Include="hotkeys.cpp" />
|
<ClCompile Include="hotkeys.cpp" />
|
||||||
<ClCompile Include="gpu_shadergen.cpp" />
|
<ClCompile Include="gpu_shadergen.cpp" />
|
||||||
<ClCompile Include="pch.cpp" />
|
<ClCompile Include="pch.cpp" />
|
||||||
|
<ClCompile Include="cpu_stack_walk.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="types.h" />
|
<ClInclude Include="types.h" />
|
||||||
|
@ -124,5 +125,6 @@
|
||||||
<ClInclude Include="shader_cache_version.h" />
|
<ClInclude Include="shader_cache_version.h" />
|
||||||
<ClInclude Include="gpu_shadergen.h" />
|
<ClInclude Include="gpu_shadergen.h" />
|
||||||
<ClInclude Include="pch.h" />
|
<ClInclude Include="pch.h" />
|
||||||
|
<ClInclude Include="cpu_stack_walk.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
|
@ -0,0 +1,194 @@
|
||||||
|
// SPDX-FileCopyrightText: 2012 PPSSPP Project, 2014-2014 PCSX2 Dev Team, 2023 Connor McLaughlin <stenzek@gmail.com>
|
||||||
|
// SPDX-License-Identifier: (GPL-2.0+)
|
||||||
|
|
||||||
|
#include "cpu_stack_walk.h"
|
||||||
|
#include "bus.h"
|
||||||
|
#include "cpu_core_private.h"
|
||||||
|
|
||||||
|
#define _RS ((rawOp >> 21) & 0x1F)
|
||||||
|
#define _RT ((rawOp >> 16) & 0x1F)
|
||||||
|
#define _RD ((rawOp >> 11) & 0x1F)
|
||||||
|
#define _IMM16 ((signed short)(rawOp & 0xFFFF))
|
||||||
|
#define MIPS_REG_SP 29
|
||||||
|
#define MIPS_REG_FP 30
|
||||||
|
#define MIPS_REG_RA 31
|
||||||
|
|
||||||
|
#define INVALIDTARGET 0xFFFFFFFF
|
||||||
|
|
||||||
|
#define MIPSTABLE_IMM_MASK 0xFC000000
|
||||||
|
#define MIPSTABLE_SPECIAL_MASK 0xFC00003F
|
||||||
|
|
||||||
|
namespace MipsStackWalk {
|
||||||
|
// In the worst case, we scan this far above the pc for an entry.
|
||||||
|
//constexpr int MAX_FUNC_SIZE = 32768 * 4;
|
||||||
|
constexpr int MAX_FUNC_SIZE = 1024 * 4;
|
||||||
|
// After this we assume we're stuck.
|
||||||
|
constexpr size_t MAX_DEPTH = 1024;
|
||||||
|
|
||||||
|
static bool IsSWInstr(u32 rawOp)
|
||||||
|
{
|
||||||
|
return (rawOp & MIPSTABLE_IMM_MASK) == 0xAC000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsAddImmInstr(u32 rawOp)
|
||||||
|
{
|
||||||
|
return (rawOp & MIPSTABLE_IMM_MASK) == 0x20000000 || (rawOp & MIPSTABLE_IMM_MASK) == 0x24000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsMovRegsInstr(u32 rawOp)
|
||||||
|
{
|
||||||
|
if ((rawOp & MIPSTABLE_SPECIAL_MASK) == 0x00000021)
|
||||||
|
{
|
||||||
|
return _RS == 0 || _RT == 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsValidAddress(u32 addr)
|
||||||
|
{
|
||||||
|
return (addr & CPU::PHYSICAL_MEMORY_ADDRESS_MASK) < Bus::RAM_2MB_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ScanForAllocaSignature(u32 pc)
|
||||||
|
{
|
||||||
|
// In God Eater Burst, for example, after 0880E750, there's what looks like an alloca().
|
||||||
|
// It's surrounded by "mov fp, sp" and "mov sp, fp", which is unlikely to be used for other reasons.
|
||||||
|
|
||||||
|
// It ought to be pretty close.
|
||||||
|
u32 stop = pc - 32 * 4;
|
||||||
|
for (; IsValidAddress(pc) && pc >= stop; pc -= 4)
|
||||||
|
{
|
||||||
|
u32 rawOp;
|
||||||
|
if (!CPU::SafeReadMemoryWord(pc, &rawOp))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// We're looking for a "mov fp, sp" close by a "addiu sp, sp, -N".
|
||||||
|
if (IsMovRegsInstr(rawOp) && _RD == MIPS_REG_FP && (_RS == MIPS_REG_SP || _RT == MIPS_REG_SP))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ScanForEntry(StackFrame& frame, u32 entry, u32& ra)
|
||||||
|
{
|
||||||
|
// Let's hope there are no > 1MB functions on the PSP, for the sake of humanity...
|
||||||
|
const u32 LONGEST_FUNCTION = 1024 * 1024;
|
||||||
|
// TODO: Check if found entry is in the same symbol? Might be wrong sometimes...
|
||||||
|
|
||||||
|
int ra_offset = -1;
|
||||||
|
const u32 start = frame.pc;
|
||||||
|
u32 stop = entry;
|
||||||
|
if (entry == INVALIDTARGET)
|
||||||
|
{
|
||||||
|
stop = 0x80000;
|
||||||
|
}
|
||||||
|
if (stop < start - LONGEST_FUNCTION)
|
||||||
|
{
|
||||||
|
stop = (LONGEST_FUNCTION > start) ? 0 : (start - LONGEST_FUNCTION);
|
||||||
|
}
|
||||||
|
for (u32 pc = start; IsValidAddress(pc) && pc >= stop; pc -= 4)
|
||||||
|
{
|
||||||
|
u32 rawOp;
|
||||||
|
if (!CPU::SafeReadMemoryWord(pc, &rawOp))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Here's where they store the ra address.
|
||||||
|
if (IsSWInstr(rawOp) && _RT == MIPS_REG_RA && _RS == MIPS_REG_SP)
|
||||||
|
{
|
||||||
|
ra_offset = _IMM16;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsAddImmInstr(rawOp) && _RT == MIPS_REG_SP && _RS == MIPS_REG_SP)
|
||||||
|
{
|
||||||
|
// A positive imm either means alloca() or we went too far.
|
||||||
|
if (_IMM16 > 0)
|
||||||
|
{
|
||||||
|
// TODO: Maybe check for any alloca() signature and bail?
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ScanForAllocaSignature(pc))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
frame.entry = pc;
|
||||||
|
frame.stackSize = -_IMM16;
|
||||||
|
if (ra_offset != -1 && IsValidAddress(frame.sp + ra_offset))
|
||||||
|
{
|
||||||
|
CPU::SafeReadMemoryWord(frame.sp + ra_offset, &ra);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool DetermineFrameInfo(StackFrame& frame, u32 possibleEntry, u32 threadEntry, u32& ra)
|
||||||
|
{
|
||||||
|
if (ScanForEntry(frame, possibleEntry, ra))
|
||||||
|
{
|
||||||
|
// Awesome, found one that looks right.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (ra != INVALIDTARGET && possibleEntry != INVALIDTARGET)
|
||||||
|
{
|
||||||
|
// Let's just assume it's a leaf.
|
||||||
|
frame.entry = possibleEntry;
|
||||||
|
frame.stackSize = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Okay, we failed to get one. Our possibleEntry could be wrong, it often is.
|
||||||
|
// Let's just scan upward.
|
||||||
|
u32 newPossibleEntry = frame.pc > threadEntry ? threadEntry : frame.pc - MAX_FUNC_SIZE;
|
||||||
|
return ScanForEntry(frame, newPossibleEntry, ra);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<StackFrame> Walk(u32 pc, u32 ra, u32 sp, u32 threadEntry, u32 threadStackTop)
|
||||||
|
{
|
||||||
|
std::vector<StackFrame> frames;
|
||||||
|
StackFrame current;
|
||||||
|
current.pc = pc;
|
||||||
|
current.sp = sp;
|
||||||
|
current.entry = INVALIDTARGET;
|
||||||
|
current.stackSize = -1;
|
||||||
|
|
||||||
|
u32 prevEntry = INVALIDTARGET;
|
||||||
|
while (pc != threadEntry)
|
||||||
|
{
|
||||||
|
u32 possibleEntry = INVALIDTARGET; // GuessEntry(cpu, current.pc);
|
||||||
|
if (DetermineFrameInfo(current, possibleEntry, threadEntry, ra))
|
||||||
|
{
|
||||||
|
frames.push_back(current);
|
||||||
|
if (current.entry == threadEntry /*|| GuessEntry(cpu, current.entry) == threadEntry*/)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (current.entry == prevEntry || frames.size() >= MAX_DEPTH)
|
||||||
|
{
|
||||||
|
// Recursion, means we're screwed. Let's just give up.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
prevEntry = current.entry;
|
||||||
|
|
||||||
|
current.pc = ra;
|
||||||
|
current.sp += current.stackSize;
|
||||||
|
ra = INVALIDTARGET;
|
||||||
|
current.entry = INVALIDTARGET;
|
||||||
|
current.stackSize = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Well, we got as far as we could.
|
||||||
|
current.entry = possibleEntry;
|
||||||
|
current.stackSize = 0;
|
||||||
|
frames.push_back(current);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return frames;
|
||||||
|
}
|
||||||
|
}; // namespace MipsStackWalk
|
|
@ -0,0 +1,26 @@
|
||||||
|
// SPDX-FileCopyrightText: 2012 PPSSPP Project, 2014-2014 PCSX2 Dev Team, 2023 Connor McLaughlin <stenzek@gmail.com>
|
||||||
|
// SPDX-License-Identifier: (GPL-2.0+)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "cpu_types.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace MipsStackWalk {
|
||||||
|
|
||||||
|
struct StackFrame
|
||||||
|
{
|
||||||
|
// Beginning of function symbol (may be estimated.)
|
||||||
|
u32 entry;
|
||||||
|
// Next position within function.
|
||||||
|
u32 pc;
|
||||||
|
// Value of SP inside this function (assuming no alloca()...)
|
||||||
|
u32 sp;
|
||||||
|
// Size of stack frame in bytes.
|
||||||
|
int stackSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<StackFrame> Walk(u32 pc, u32 ra, u32 sp, u32 threadEntry, u32 threadStackTop);
|
||||||
|
|
||||||
|
}; // namespace MipsStackWalk
|
|
@ -140,6 +140,8 @@ QVariant DebuggerCodeModel::data(const QModelIndex& index, int role /*= Qt::Disp
|
||||||
// return QApplication::palette().toolTipBase();
|
// return QApplication::palette().toolTipBase();
|
||||||
if (address == m_last_pc)
|
if (address == m_last_pc)
|
||||||
return QColor(100, 100, 0);
|
return QColor(100, 100, 0);
|
||||||
|
else if (address == m_last_highlight_address)
|
||||||
|
return QColor(80, 80, 80);
|
||||||
else
|
else
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
@ -244,6 +246,16 @@ void DebuggerCodeModel::setPC(VirtualMemoryAddress pc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DebuggerCodeModel::setHighlightAddress(VirtualMemoryAddress address)
|
||||||
|
{
|
||||||
|
if (m_last_highlight_address == address)
|
||||||
|
return;
|
||||||
|
|
||||||
|
emitDataChangedForAddress(m_last_highlight_address);
|
||||||
|
m_last_highlight_address = address;
|
||||||
|
emitDataChangedForAddress(address);
|
||||||
|
}
|
||||||
|
|
||||||
void DebuggerCodeModel::ensureAddressVisible(VirtualMemoryAddress address)
|
void DebuggerCodeModel::ensureAddressVisible(VirtualMemoryAddress address)
|
||||||
{
|
{
|
||||||
updateRegion(address);
|
updateRegion(address);
|
||||||
|
|
|
@ -1,27 +1,29 @@
|
||||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
|
||||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "core/bios.h"
|
||||||
#include "core/bus.h"
|
#include "core/bus.h"
|
||||||
#include "core/cpu_core.h"
|
#include "core/cpu_core.h"
|
||||||
#include "core/cpu_types.h"
|
#include "core/cpu_types.h"
|
||||||
|
|
||||||
#include <QtCore/QAbstractListModel>
|
#include <QtCore/QAbstractListModel>
|
||||||
#include <QtCore/QAbstractTableModel>
|
#include <QtCore/QAbstractTableModel>
|
||||||
#include <QtGui/QPixmap>
|
#include <QtGui/QPixmap>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
class DebuggerCodeModel : public QAbstractTableModel
|
class DebuggerCodeModel final : public QAbstractTableModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
DebuggerCodeModel(QObject* parent = nullptr);
|
DebuggerCodeModel(QObject* parent = nullptr);
|
||||||
virtual ~DebuggerCodeModel();
|
~DebuggerCodeModel() override;
|
||||||
|
|
||||||
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||||
virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override;
|
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||||
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||||
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
// Returns the row for this instruction pointer
|
// Returns the row for this instruction pointer
|
||||||
void resetCodeView(VirtualMemoryAddress start_address);
|
void resetCodeView(VirtualMemoryAddress start_address);
|
||||||
|
@ -30,6 +32,7 @@ public:
|
||||||
VirtualMemoryAddress getAddressForRow(int row) const;
|
VirtualMemoryAddress getAddressForRow(int row) const;
|
||||||
VirtualMemoryAddress getAddressForIndex(QModelIndex index) const;
|
VirtualMemoryAddress getAddressForIndex(QModelIndex index) const;
|
||||||
void setPC(VirtualMemoryAddress pc);
|
void setPC(VirtualMemoryAddress pc);
|
||||||
|
void setHighlightAddress(VirtualMemoryAddress address);
|
||||||
void ensureAddressVisible(VirtualMemoryAddress address);
|
void ensureAddressVisible(VirtualMemoryAddress address);
|
||||||
void setBreakpointList(std::vector<VirtualMemoryAddress> bps);
|
void setBreakpointList(std::vector<VirtualMemoryAddress> bps);
|
||||||
void setBreakpointState(VirtualMemoryAddress address, bool enabled);
|
void setBreakpointState(VirtualMemoryAddress address, bool enabled);
|
||||||
|
@ -45,24 +48,25 @@ private:
|
||||||
VirtualMemoryAddress m_code_region_start = 0;
|
VirtualMemoryAddress m_code_region_start = 0;
|
||||||
VirtualMemoryAddress m_code_region_end = 0;
|
VirtualMemoryAddress m_code_region_end = 0;
|
||||||
VirtualMemoryAddress m_last_pc = 0;
|
VirtualMemoryAddress m_last_pc = 0;
|
||||||
|
VirtualMemoryAddress m_last_highlight_address = 0xFFFFFFFFu;
|
||||||
std::vector<VirtualMemoryAddress> m_breakpoints;
|
std::vector<VirtualMemoryAddress> m_breakpoints;
|
||||||
|
|
||||||
QPixmap m_pc_pixmap;
|
QPixmap m_pc_pixmap;
|
||||||
QPixmap m_breakpoint_pixmap;
|
QPixmap m_breakpoint_pixmap;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DebuggerRegistersModel : public QAbstractListModel
|
class DebuggerRegistersModel final : public QAbstractListModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
DebuggerRegistersModel(QObject* parent = nullptr);
|
DebuggerRegistersModel(QObject* parent = nullptr);
|
||||||
virtual ~DebuggerRegistersModel();
|
~DebuggerRegistersModel() override;
|
||||||
|
|
||||||
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||||
virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override;
|
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||||
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||||
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
void invalidateView();
|
void invalidateView();
|
||||||
void saveCurrentValues();
|
void saveCurrentValues();
|
||||||
|
@ -71,18 +75,18 @@ private:
|
||||||
u32 m_old_reg_values[CPU::NUM_DEBUGGER_REGISTER_LIST_ENTRIES] = {};
|
u32 m_old_reg_values[CPU::NUM_DEBUGGER_REGISTER_LIST_ENTRIES] = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
class DebuggerStackModel : public QAbstractListModel
|
class DebuggerStackModel final : public QAbstractListModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
DebuggerStackModel(QObject* parent = nullptr);
|
DebuggerStackModel(QObject* parent = nullptr);
|
||||||
virtual ~DebuggerStackModel();
|
~DebuggerStackModel() override;
|
||||||
|
|
||||||
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||||
virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override;
|
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||||
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||||
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
void invalidateView();
|
void invalidateView();
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
|
||||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||||
|
|
||||||
#include "debuggerwindow.h"
|
#include "debuggerwindow.h"
|
||||||
#include "common/assert.h"
|
|
||||||
#include "core/cpu_core_private.h"
|
|
||||||
#include "debuggermodels.h"
|
#include "debuggermodels.h"
|
||||||
#include "qthost.h"
|
#include "qthost.h"
|
||||||
#include "qtutils.h"
|
#include "qtutils.h"
|
||||||
|
|
||||||
|
#include "core/bios.h"
|
||||||
|
#include "core/bus.h"
|
||||||
|
#include "core/cpu_core_private.h"
|
||||||
|
#include "core/cpu_stack_walk.h"
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
|
||||||
#include <QtCore/QSignalBlocker>
|
#include <QtCore/QSignalBlocker>
|
||||||
#include <QtGui/QFontDatabase>
|
#include <QtGui/QFontDatabase>
|
||||||
#include <QtWidgets/QFileDialog>
|
#include <QtWidgets/QFileDialog>
|
||||||
|
@ -57,6 +63,7 @@ void DebuggerWindow::refreshAll()
|
||||||
m_registers_model->invalidateView();
|
m_registers_model->invalidateView();
|
||||||
m_stack_model->invalidateView();
|
m_stack_model->invalidateView();
|
||||||
m_ui.memoryView->repaint();
|
m_ui.memoryView->repaint();
|
||||||
|
updateThreadsList();
|
||||||
|
|
||||||
m_code_model->setPC(CPU::g_state.pc);
|
m_code_model->setPC(CPU::g_state.pc);
|
||||||
scrollToPC();
|
scrollToPC();
|
||||||
|
@ -70,6 +77,7 @@ void DebuggerWindow::scrollToPC()
|
||||||
void DebuggerWindow::scrollToCodeAddress(VirtualMemoryAddress address)
|
void DebuggerWindow::scrollToCodeAddress(VirtualMemoryAddress address)
|
||||||
{
|
{
|
||||||
m_code_model->ensureAddressVisible(address);
|
m_code_model->ensureAddressVisible(address);
|
||||||
|
m_code_model->setHighlightAddress(address);
|
||||||
|
|
||||||
int row = m_code_model->getRowForAddress(address);
|
int row = m_code_model->getRowForAddress(address);
|
||||||
if (row >= 0)
|
if (row >= 0)
|
||||||
|
@ -388,6 +396,23 @@ void DebuggerWindow::onMemorySearchStringChanged(const QString&)
|
||||||
m_next_memory_search_address = 0;
|
m_next_memory_search_address = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DebuggerWindow::onThreadsItemDoubleClicked(const QTreeWidgetItem* item, int column)
|
||||||
|
{
|
||||||
|
if (column != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const QVariant vdata = item->data(0, Qt::UserRole);
|
||||||
|
if (!vdata.isValid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool okay;
|
||||||
|
uint addr = vdata.toUInt(&okay);
|
||||||
|
if (!okay)
|
||||||
|
return;
|
||||||
|
|
||||||
|
scrollToCodeAddress(addr);
|
||||||
|
}
|
||||||
|
|
||||||
void DebuggerWindow::closeEvent(QCloseEvent* event)
|
void DebuggerWindow::closeEvent(QCloseEvent* event)
|
||||||
{
|
{
|
||||||
g_emu_thread->disconnect(this);
|
g_emu_thread->disconnect(this);
|
||||||
|
@ -450,6 +475,8 @@ void DebuggerWindow::connectSignals()
|
||||||
|
|
||||||
connect(m_ui.memorySearch, &QPushButton::clicked, this, &DebuggerWindow::onMemorySearchTriggered);
|
connect(m_ui.memorySearch, &QPushButton::clicked, this, &DebuggerWindow::onMemorySearchTriggered);
|
||||||
connect(m_ui.memorySearchString, &QLineEdit::textChanged, this, &DebuggerWindow::onMemorySearchStringChanged);
|
connect(m_ui.memorySearchString, &QLineEdit::textChanged, this, &DebuggerWindow::onMemorySearchStringChanged);
|
||||||
|
|
||||||
|
connect(m_ui.threadsView, &QTreeWidget::itemDoubleClicked, this, &DebuggerWindow::onThreadsItemDoubleClicked);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebuggerWindow::disconnectSignals()
|
void DebuggerWindow::disconnectSignals()
|
||||||
|
@ -614,3 +641,48 @@ void DebuggerWindow::refreshBreakpointList()
|
||||||
m_ui.breakpointsWidget->addTopLevelItem(item);
|
m_ui.breakpointsWidget->addTopLevelItem(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DebuggerWindow::updateThreadsList()
|
||||||
|
{
|
||||||
|
const std::span<const BIOS::ThreadControlBlock> tcbs = BIOS::GetTCBs();
|
||||||
|
const BIOS::ThreadControlBlock* current_tcb = BIOS::GetCurrentThreadTCB();
|
||||||
|
|
||||||
|
while (m_ui.threadsView->topLevelItemCount() > 0)
|
||||||
|
delete m_ui.threadsView->takeTopLevelItem(0);
|
||||||
|
|
||||||
|
for (u32 i = 0; i < tcbs.size(); i++)
|
||||||
|
{
|
||||||
|
const BIOS::ThreadControlBlock& tcb = tcbs[i];
|
||||||
|
const bool is_current = (&tcb == current_tcb);
|
||||||
|
const bool is_unused = (tcb.status & 0x4000) != 0x4000;
|
||||||
|
|
||||||
|
// This is awful.
|
||||||
|
// TODO: Only walk stack when it's the current thread...
|
||||||
|
QTreeWidgetItem* item = new QTreeWidgetItem(m_ui.threadsView);
|
||||||
|
item->setText(0,
|
||||||
|
QString::asprintf("Thread %08X%s", static_cast<u32>(reinterpret_cast<const u8*>(&tcb) - Bus::g_ram),
|
||||||
|
is_current ? " [current]" : (is_unused ? "[unused]" : "")));
|
||||||
|
if (is_unused)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const u32 pc = is_current ? CPU::g_state.pc : tcb.regs[static_cast<u8>(CPU::Reg::ra)];
|
||||||
|
const u32 ra = is_current ? CPU::g_state.regs.ra : tcb.regs[static_cast<u8>(CPU::Reg::ra)];
|
||||||
|
const u32 sp = is_current ? CPU::g_state.regs.sp : tcb.regs[static_cast<u8>(CPU::Reg::sp)];
|
||||||
|
|
||||||
|
if (is_current)
|
||||||
|
{
|
||||||
|
const std::vector<MipsStackWalk::StackFrame> stack = MipsStackWalk::Walk(pc, ra, sp, 0xFFFFFFFFu, 0xFFFFFFFFu);
|
||||||
|
for (const MipsStackWalk::StackFrame& frame : stack)
|
||||||
|
{
|
||||||
|
QTreeWidgetItem* framei = new QTreeWidgetItem(item);
|
||||||
|
framei->setData(0, Qt::UserRole, QVariant(frame.pc));
|
||||||
|
framei->setText(0, QString::asprintf("0x%08X", frame.pc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QTreeWidgetItem* frame = new QTreeWidgetItem(item);
|
||||||
|
frame->setText(0, QString::asprintf("0x%08X", pc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -59,6 +59,8 @@ private Q_SLOTS:
|
||||||
void onMemorySearchTriggered();
|
void onMemorySearchTriggered();
|
||||||
void onMemorySearchStringChanged(const QString&);
|
void onMemorySearchStringChanged(const QString&);
|
||||||
|
|
||||||
|
void onThreadsItemDoubleClicked(const QTreeWidgetItem* item, int column);
|
||||||
|
void updateThreadsList();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupAdditionalUi();
|
void setupAdditionalUi();
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1210</width>
|
<width>1299</width>
|
||||||
<height>800</height>
|
<height>800</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
|
@ -22,8 +22,8 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1210</width>
|
<width>1299</width>
|
||||||
<height>21</height>
|
<height>22</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QMenu" name="menu_Debugger">
|
<widget class="QMenu" name="menu_Debugger">
|
||||||
|
@ -36,15 +36,14 @@
|
||||||
<addaction name="actionGoToPC"/>
|
<addaction name="actionGoToPC"/>
|
||||||
<addaction name="actionGoToAddress"/>
|
<addaction name="actionGoToAddress"/>
|
||||||
<addaction name="actionDumpAddress"/>
|
<addaction name="actionDumpAddress"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionTrace"/>
|
<addaction name="actionTrace"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionStepInto"/>
|
<addaction name="actionStepInto"/>
|
||||||
<addaction name="actionStepOver"/>
|
<addaction name="actionStepOver"/>
|
||||||
<addaction name="actionStepOut"/>
|
<addaction name="actionStepOut"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionClose"/>
|
<addaction name="actionClose"/>
|
||||||
|
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QMenu" name="menuBreakpoints">
|
<widget class="QMenu" name="menuBreakpoints">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
|
@ -82,7 +81,7 @@
|
||||||
<addaction name="actionStepInto"/>
|
<addaction name="actionStepInto"/>
|
||||||
<addaction name="actionStepOver"/>
|
<addaction name="actionStepOver"/>
|
||||||
<addaction name="actionStepOut"/>
|
<addaction name="actionStepOut"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionTrace"/>
|
<addaction name="actionTrace"/>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QDockWidget" name="dockWidget_2">
|
<widget class="QDockWidget" name="dockWidget_2">
|
||||||
|
@ -225,6 +224,33 @@
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
|
<widget class="QDockWidget" name="dockWidget_6">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>200</width>
|
||||||
|
<height>524287</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="features">
|
||||||
|
<set>QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable</set>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Threads</string>
|
||||||
|
</property>
|
||||||
|
<attribute name="dockWidgetArea">
|
||||||
|
<number>8</number>
|
||||||
|
</attribute>
|
||||||
|
<widget class="QTreeWidget" name="threadsView">
|
||||||
|
<property name="headerHidden">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>1</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
<widget class="QDockWidget" name="dockWidget_5">
|
<widget class="QDockWidget" name="dockWidget_5">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||||
|
@ -481,8 +507,6 @@
|
||||||
<string>Ctrl+T</string>
|
<string>Ctrl+T</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
|
||||||
|
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
|
|
Loading…
Reference in New Issue