Files
ppsspp/Windows/Debugger/Debugger_Lists.cpp
T
Henrik RydgårdandClaude Opus 5.5 7dd1475ed7 HLE: Pass the MIPSState to syscalls and their implementations
HLEFunc and ReplaceFunc now take the MIPSState they run on. The dispatch
paths (both interpreters and every JIT backend) pass in the state they
belong to, and CallSyscall*, hleFinishSyscall and the HLE mips call
machinery use it instead of currentMIPS.

The wrappers in FunctionWrappers.h take the function as `auto` and call it
through HLECall, which also accepts an implementation with an extra leading
MIPSState * parameter and passes the state to it. Any other signature still
fails to compile, as before. PARAM/RETURN now refer to a local `mips`.

The HLE functions (and replacement functions) that used currentMIPS now take
the state that way. Callers outside a syscall's own frame - CoreTiming
events, the adhoc matching thread, dialogs, PPGe, the debugger UIs - pass
currentMIPS or &mipsr4k explicitly. The kernel's thread internals still use
currentMIPS.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 15:45:14 +02:00

1062 lines
27 KiB
C++

#include "Windows/Debugger/Debugger_Lists.h"
#include "Common/CommonWindows.h"
#include <windowsx.h>
#include <commctrl.h>
#include "Windows/Debugger/BreakpointWindow.h"
#include "Windows/Debugger/CtrlDisAsmView.h"
#include "Windows/Debugger/DebuggerShared.h"
#include "Windows/Debugger/WatchItemWindow.h"
#include "Windows/W32Util/ContextMenu.h"
#include "Windows/MainWindow.h"
#include "Windows/resource.h"
#include "Windows/main.h"
#include "Common/Data/Encoding/Utf8.h"
#include "Core/Core.h"
#include "Core/Debugger/LineInfo.h"
#include "Core/MemMap.h"
#include "Core/HLE/sceKernelThread.h"
enum { TL_NAME, TL_PROGRAMCOUNTER, TL_ENTRYPOINT, TL_PRIORITY, TL_STATE, TL_WAITTYPE, TL_COLUMNCOUNT };
enum { BPL_ENABLED, BPL_TYPE, BPL_OFFSET, BPL_SIZELABEL, BPL_OPCODE, BPL_CONDITION, BPL_HITS, BPL_COLUMNCOUNT };
enum { SF_ENTRY, SF_ENTRYNAME, SF_CURPC, SF_CUROPCODE, SF_CURSP, SF_FRAMESIZE, SF_SOURCE, SF_COLUMNCOUNT };
enum { ML_NAME, ML_ADDRESS, ML_SIZE, ML_ACTIVE, ML_COLUMNCOUNT };
enum { WL_NAME, WL_EXPRESSION, WL_VALUE, WL_COLUMNCOUNT };
GenericListViewColumn threadColumns[TL_COLUMNCOUNT] = {
{ L"Name", 0.20f },
{ L"PC", 0.15f },
{ L"Entry Point", 0.15f },
{ L"Priority", 0.15f },
{ L"State", 0.15f },
{ L"Wait type", 0.20f }
};
GenericListViewDef threadListDef = {
threadColumns, ARRAY_SIZE(threadColumns), NULL, false
};
GenericListViewColumn breakpointColumns[BPL_COLUMNCOUNT] = {
{ L"", 0.03f }, // enabled
{ L"Type", 0.15f },
{ L"Offset", 0.12f },
{ L"Size/Label", 0.20f },
{ L"Opcode", 0.28f },
{ L"Condition", 0.17f },
{ L"Hits", 0.05f },
};
GenericListViewDef breakpointListDef = {
breakpointColumns, ARRAY_SIZE(breakpointColumns), NULL, true
};
GenericListViewColumn stackTraceColumns[SF_COLUMNCOUNT] = {
{ L"Entry", 0.12f },
{ L"Name", 0.24f },
{ L"PC", 0.12f },
{ L"Opcode", 0.28f },
{ L"SP", 0.10f },
{ L"Frame Size", 0.10f },
{ L"Source", 0.16f }
};
GenericListViewDef stackTraceListDef = {
stackTraceColumns, ARRAY_SIZE(stackTraceColumns), NULL, false
};
GenericListViewColumn moduleListColumns[ML_COLUMNCOUNT] = {
{ L"Name", 0.25f },
{ L"Address", 0.25f },
{ L"Size", 0.25f },
{ L"Active", 0.25f },
};
GenericListViewDef moduleListDef = {
moduleListColumns, ARRAY_SIZE(moduleListColumns), NULL, false
};
GenericListViewColumn watchListColumns[WL_COLUMNCOUNT] = {
{ L"Name", 0.25f },
{ L"Expression", 0.5f },
{ L"Value", 0.25f },
};
GenericListViewDef watchListDef = {
watchListColumns, ARRAY_SIZE(watchListColumns), nullptr, false,
};
//
// CtrlThreadList
//
CtrlThreadList::CtrlThreadList(HWND hwnd): GenericListControl(hwnd,threadListDef)
{
Update();
}
bool CtrlThreadList::WindowMessage(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT& returnValue)
{
switch (msg)
{
case WM_KEYDOWN:
if (wParam == VK_TAB)
{
SendMessage(GetParent(GetHandle()),WM_DEB_TABPRESSED,0,0);
returnValue = 0;
return true;
}
break;
case WM_GETDLGCODE:
if (lParam && ((MSG*)lParam)->message == WM_KEYDOWN)
{
if (wParam == VK_TAB)
{
returnValue = DLGC_WANTMESSAGE;
return true;
}
}
break;
}
return false;
}
void CtrlThreadList::showMenu(int itemIndex, const POINT &pt)
{
auto threadInfo = threads[itemIndex];
// Can't do it, sorry. Needs to not be running.
if (Core_IsActive())
return;
HMENU subMenu = GetContextMenu(ContextMenuID::THREADLIST);
switch (threadInfo.status) {
case THREADSTATUS_DEAD:
case THREADSTATUS_DORMANT:
case THREADSTATUS_RUNNING:
EnableMenuItem(subMenu, ID_DISASM_THREAD_FORCERUN, MF_BYCOMMAND | MF_DISABLED);
EnableMenuItem(subMenu, ID_DISASM_THREAD_KILL, MF_BYCOMMAND | MF_DISABLED);
break;
case THREADSTATUS_READY:
EnableMenuItem(subMenu, ID_DISASM_THREAD_FORCERUN, MF_BYCOMMAND | MF_DISABLED);
EnableMenuItem(subMenu, ID_DISASM_THREAD_KILL, MF_BYCOMMAND | MF_ENABLED);
break;
case THREADSTATUS_SUSPEND:
case THREADSTATUS_WAIT:
case THREADSTATUS_WAITSUSPEND:
default:
EnableMenuItem(subMenu, ID_DISASM_THREAD_FORCERUN, MF_BYCOMMAND | MF_ENABLED);
EnableMenuItem(subMenu, ID_DISASM_THREAD_KILL, MF_BYCOMMAND | MF_ENABLED);
break;
}
switch (TriggerContextMenu(ContextMenuID::THREADLIST, GetHandle(), ContextPoint::FromClient(pt)))
{
case ID_DISASM_THREAD_FORCERUN:
{
// Route the actual thread-state mutation to the CPU thread instead of poking at it
// directly from this GUI thread - see Core_RunOnCPUThread() in Core.h.
SceUID threadID = threadInfo.id;
Core_RunOnCPUThread([&] { __KernelResumeThreadFromWait(threadID, 0); });
}
reloadThreads();
break;
case ID_DISASM_THREAD_KILL:
{
SceUID threadID = threadInfo.id;
Core_RunOnCPUThread([&] { sceKernelTerminateThread(&mipsr4k, threadID); });
}
reloadThreads();
break;
}
}
void CtrlThreadList::GetColumnText(wchar_t* dest, size_t destSize, int row, int col)
{
if (row < 0 || row >= (int)threads.size()) {
return;
}
switch (col)
{
case TL_NAME:
wcscpy(dest, ConvertUTF8ToWString(threads[row].name).c_str());
break;
case TL_PROGRAMCOUNTER:
switch (threads[row].status)
{
case THREADSTATUS_DORMANT:
case THREADSTATUS_DEAD:
wcscpy(dest, L"N/A");
break;
default:
wsprintf(dest, L"0x%08X",threads[row].curPC);
break;
};
break;
case TL_ENTRYPOINT:
wsprintf(dest,L"0x%08X",threads[row].entrypoint);
break;
case TL_PRIORITY:
wsprintf(dest,L"%d",threads[row].priority);
break;
case TL_STATE:
switch (threads[row].status)
{
case THREADSTATUS_RUNNING:
wcscpy(dest,L"Running");
break;
case THREADSTATUS_READY:
wcscpy(dest,L"Ready");
break;
case THREADSTATUS_WAIT:
wcscpy(dest,L"Waiting");
break;
case THREADSTATUS_SUSPEND:
wcscpy(dest,L"Suspended");
break;
case THREADSTATUS_DORMANT:
wcscpy(dest,L"Dormant");
break;
case THREADSTATUS_DEAD:
wcscpy(dest,L"Dead");
break;
case THREADSTATUS_WAITSUSPEND:
wcscpy(dest,L"Waiting/Suspended");
break;
default:
wcscpy(dest,L"Invalid");
break;
}
break;
case TL_WAITTYPE:
wcscpy(dest, ConvertUTF8ToWString(WaitTypeToString(threads[row].waitType)).c_str());
break;
}
}
void CtrlThreadList::OnDoubleClick(int itemIndex, int column)
{
u32 address;
switch (threads[itemIndex].status)
{
case THREADSTATUS_DORMANT:
case THREADSTATUS_DEAD:
address = threads[itemIndex].entrypoint;
break;
default:
address = threads[itemIndex].curPC;
break;
}
SendMessage(GetParent(GetHandle()),WM_DEB_GOTOWPARAM,address,0);
}
void CtrlThreadList::OnRightClick(int itemIndex, int column, const POINT& point)
{
showMenu(itemIndex,point);
}
void CtrlThreadList::reloadThreads()
{
// Reading live kernel thread state here on the GUI thread would otherwise race with the CPU
// thread - hold g_frameMutex for the duration of the read, which NativeFrame() also holds
// while it's actually touching that state. See g_frameMutex in Core.h.
{
std::lock_guard<std::mutex> frameGuard(g_frameMutex);
CoreShutdownLock coreLock = Core_LockAgainstShutdown();
threads = GetThreadsInfo();
}
Update();
}
const char* CtrlThreadList::getCurrentThreadName()
{
for (size_t i = 0; i < threads.size(); i++)
{
if (threads[i].isCurrent) return threads[i].name;
}
return "N/A";
}
//
// CtrlBreakpointList
//
CtrlBreakpointList::CtrlBreakpointList(HWND hwnd, MIPSDebugInterface* cpu, CtrlDisAsmView* disasm)
: GenericListControl(hwnd,breakpointListDef),cpu(cpu),disasm(disasm)
{
SetSendInvalidRows(true);
Update();
}
bool CtrlBreakpointList::WindowMessage(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT& returnValue)
{
switch(msg)
{
case WM_KEYDOWN:
returnValue = 0;
if(wParam == VK_RETURN)
{
int index = GetSelectedIndex();
editBreakpoint(index);
return true;
} else if (wParam == VK_DELETE)
{
int index = GetSelectedIndex();
removeBreakpoint(index);
return true;
} else if (wParam == VK_TAB)
{
SendMessage(GetParent(GetHandle()),WM_DEB_TABPRESSED,0,0);
return true;
} else if (wParam == VK_SPACE)
{
int index = GetSelectedIndex();
toggleEnabled(index);
return true;
}
break;
case WM_GETDLGCODE:
if (lParam && ((MSG*)lParam)->message == WM_KEYDOWN)
{
if (wParam == VK_TAB || wParam == VK_RETURN)
{
returnValue = DLGC_WANTMESSAGE;
return true;
}
}
break;
}
return false;
}
void CtrlBreakpointList::reloadBreakpoints()
{
// Update the items we're displaying from the debugger. g_frameMutex guards this against races
// with the CPU thread - see g_frameMutex in Core.h.
{
std::lock_guard<std::mutex> frameGuard(g_frameMutex);
CoreShutdownLock coreLock = Core_LockAgainstShutdown();
displayedBreakPoints_ = g_breakpoints.GetBreakpoints();
displayedMemChecks_= g_breakpoints.GetMemChecks();
}
for (int i = 0; i < GetRowCount(); i++)
{
bool isMemory;
int index = getBreakpointIndex(i, isMemory);
if (index < 0)
continue;
if (isMemory)
SetCheckState(i, displayedMemChecks_[index].IsEnabled());
else
SetCheckState(i, displayedBreakPoints_[index].IsEnabled());
}
Update();
}
void CtrlBreakpointList::editBreakpoint(int itemIndex)
{
bool isMemory;
int index = getBreakpointIndex(itemIndex, isMemory);
if (index == -1) return;
// Route the breakpoint mutation to the CPU thread instead of poking at it directly from this
// GUI thread - see Core_RunOnCPUThread() in Core.h. win.exec() is a modal dialog, so it must
// stay outside any queued callback, or we'd block the CPU thread on user input.
BreakpointWindow win(GetHandle(),cpu);
if (isMemory)
{
auto mem = displayedMemChecks_[index];
win.loadFromMemcheck(mem);
if (win.exec())
{
Core_RunOnCPUThread([&] {
g_breakpoints.RemoveMemCheck(mem.start,mem.end);
win.addBreakpoint();
});
}
} else {
auto bp = displayedBreakPoints_[index];
win.loadFromBreakpoint(bp);
if (win.exec())
{
Core_RunOnCPUThread([&] {
g_breakpoints.RemoveBreakPoint(bp.addr);
win.addBreakpoint();
});
}
}
}
void CtrlBreakpointList::toggleEnabled(int itemIndex)
{
bool isMemory;
int index = getBreakpointIndex(itemIndex, isMemory);
if (index == -1) return;
// Route the breakpoint mutation to the CPU thread instead of poking at it directly from this
// GUI thread - see Core_RunOnCPUThread() in Core.h.
if (isMemory) {
MemCheck mcPrev = displayedMemChecks_[index];
Core_RunOnCPUThread([&] { g_breakpoints.ChangeMemCheck(mcPrev.start, mcPrev.end, mcPrev.cond, BreakAction(mcPrev.action ^ BREAK_ACTION_PAUSE)); });
} else {
BreakPoint bpPrev = displayedBreakPoints_[index];
Core_RunOnCPUThread([&] { g_breakpoints.ChangeBreakPoint(bpPrev.addr, BreakAction(bpPrev.action ^ BREAK_ACTION_PAUSE)); });
}
}
void CtrlBreakpointList::gotoBreakpointAddress(int itemIndex)
{
bool isMemory;
int index = getBreakpointIndex(itemIndex, isMemory);
if (index == -1)
return;
if (isMemory) {
u32 address = displayedMemChecks_[index].start;
MainWindow::CreateMemoryWindow();
if (memoryWindow)
memoryWindow->Goto(address);
} else {
u32 address = displayedBreakPoints_[index].addr;
MainWindow::CreateDisasmWindow();
if (disasmWindow)
disasmWindow->Goto(address);
}
}
void CtrlBreakpointList::removeBreakpoint(int itemIndex)
{
bool isMemory;
int index = getBreakpointIndex(itemIndex,isMemory);
if (index == -1) return;
// Route the breakpoint mutation to the CPU thread instead of poking at it directly from this
// GUI thread - see Core_RunOnCPUThread() in Core.h.
if (isMemory) {
auto mc = displayedMemChecks_[index];
Core_RunOnCPUThread([&] { g_breakpoints.RemoveMemCheck(mc.start, mc.end); });
} else {
u32 address = displayedBreakPoints_[index].addr;
Core_RunOnCPUThread([&] { g_breakpoints.RemoveBreakPoint(address); });
}
}
int CtrlBreakpointList::getTotalBreakpointCount() {
// displayedBreakPoints_ never contains the internal step-over/run-until breakpoint.
return (int)displayedMemChecks_.size() + (int)displayedBreakPoints_.size();
}
int CtrlBreakpointList::getBreakpointIndex(int itemIndex, bool& isMemory)
{
// memory breakpoints first
if (itemIndex < (int)displayedMemChecks_.size())
{
isMemory = true;
return itemIndex;
}
itemIndex -= (int)displayedMemChecks_.size();
size_t i = 0;
while (i < displayedBreakPoints_.size())
{
// the index is 0 when there are no more breakpoints to skip
if (itemIndex == 0)
{
isMemory = false;
return (int)i;
}
i++;
itemIndex--;
}
return -1;
}
void CtrlBreakpointList::GetColumnText(wchar_t* dest, size_t destSize, int row, int col)
{
if (!PSP_IsInited()) {
return;
}
bool isMemory;
int index = getBreakpointIndex(row,isMemory);
if (index == -1) return;
switch (col)
{
case BPL_TYPE:
{
if (isMemory) {
switch ((int)displayedMemChecks_[index].cond) {
case MEMCHECK_READ:
wcscpy(dest,L"Read");
break;
case MEMCHECK_WRITE:
wcscpy(dest,L"Write");
break;
case MEMCHECK_READWRITE:
wcscpy(dest,L"Read/Write");
break;
case MEMCHECK_WRITE | MEMCHECK_WRITE_ONCHANGE:
wcscpy(dest,L"Write Change");
break;
case MEMCHECK_READWRITE | MEMCHECK_WRITE_ONCHANGE:
wcscpy(dest,L"Read/Write Change");
break;
}
} else {
wcscpy(dest, L"Execute");
}
}
break;
case BPL_OFFSET:
{
if (isMemory) {
wsprintf(dest,L"0x%08X",displayedMemChecks_[index].start);
} else {
wsprintf(dest,L"0x%08X",displayedBreakPoints_[index].addr);
}
}
break;
case BPL_SIZELABEL:
{
if (isMemory) {
auto mc = displayedMemChecks_[index];
if (mc.end == 0)
wsprintf(dest,L"0x%08X",1);
else
wsprintf(dest,L"0x%08X",mc.end-mc.start);
} else {
const std::string sym = g_symbolMap->GetLabelString(displayedBreakPoints_[index].addr);
if (!sym.empty()) {
ConvertUTF8ToWString(dest, destSize, sym);
} else {
wcscpy(dest,L"-");
}
}
}
break;
case BPL_OPCODE:
{
if (isMemory) {
wcscpy(dest,L"-");
} else {
char temp[256];
disasm->getOpcodeText(displayedBreakPoints_[index].addr, temp, sizeof(temp));
ConvertUTF8ToWString(dest, destSize, temp);
}
}
break;
case BPL_CONDITION:
{
if (isMemory || displayedBreakPoints_[index].hasCond == false) {
wcscpy(dest,L"-");
} else {
std::wstring s = ConvertUTF8ToWString(displayedBreakPoints_[index].cond.expressionString);
wcscpy(dest,s.c_str());
}
}
break;
case BPL_HITS:
{
if (isMemory) {
wsprintf(dest,L"%d",displayedMemChecks_[index].numHits);
} else {
wsprintf(dest,L"-");
}
}
break;
case BPL_ENABLED:
{
wsprintf(dest,L"\xFFFE");
}
break;
}
}
void CtrlBreakpointList::OnDoubleClick(int itemIndex, int column)
{
gotoBreakpointAddress(itemIndex);
}
void CtrlBreakpointList::OnRightClick(int itemIndex, int column, const POINT& point)
{
showBreakpointMenu(itemIndex,point);
}
void CtrlBreakpointList::OnToggle(int item, bool newValue)
{
toggleEnabled(item);
}
void CtrlBreakpointList::showBreakpointMenu(int itemIndex, const POINT &pt)
{
bool isMemory;
int index = getBreakpointIndex(itemIndex, isMemory);
if (index == -1)
{
switch (TriggerContextMenu(ContextMenuID::NEWBREAKPOINT, GetHandle(), ContextPoint::FromClient(pt)))
{
case ID_DISASM_ADDNEWBREAKPOINT:
{
// bpw.exec() is a modal dialog - must stay outside any queued callback, or we'd
// block the CPU thread on user input - see Core_RunOnCPUThread() in Core.h.
BreakpointWindow bpw(GetHandle(),cpu);
if (bpw.exec()) {
Core_RunOnCPUThread([&] { bpw.addBreakpoint(); });
}
}
break;
}
} else {
MemCheck mcPrev;
BreakPoint bpPrev;
if (isMemory) {
mcPrev = displayedMemChecks_[index];
} else {
bpPrev = displayedBreakPoints_[index];
}
HMENU subMenu = GetContextMenu(ContextMenuID::BREAKPOINTLIST);
if (isMemory) {
CheckMenuItem(subMenu, ID_DISASM_DISABLEBREAKPOINT, MF_BYCOMMAND | (mcPrev.IsEnabled() ? MF_CHECKED : MF_UNCHECKED));
} else {
CheckMenuItem(subMenu, ID_DISASM_DISABLEBREAKPOINT, MF_BYCOMMAND | (bpPrev.IsEnabled() ? MF_CHECKED : MF_UNCHECKED));
}
switch (TriggerContextMenu(ContextMenuID::BREAKPOINTLIST, GetHandle(), ContextPoint::FromClient(pt)))
{
case ID_DISASM_DISABLEBREAKPOINT:
// Route the breakpoint mutation to the CPU thread instead of poking at it directly
// from this GUI thread - see Core_RunOnCPUThread() in Core.h.
if (isMemory) {
Core_RunOnCPUThread([&] { g_breakpoints.ChangeMemCheck(mcPrev.start, mcPrev.end, mcPrev.cond, BreakAction(mcPrev.action ^ BREAK_ACTION_PAUSE)); });
} else {
Core_RunOnCPUThread([&] { g_breakpoints.ChangeBreakPoint(bpPrev.addr, BreakAction(bpPrev.action ^ BREAK_ACTION_PAUSE)); });
}
break;
case ID_DISASM_EDITBREAKPOINT:
editBreakpoint(itemIndex);
break;
case ID_DISASM_ADDNEWBREAKPOINT:
{
// bpw.exec() is a modal dialog - must stay outside any queued callback, or we'd
// block the CPU thread on user input - see Core_RunOnCPUThread() in Core.h.
BreakpointWindow bpw(GetHandle(),cpu);
if (bpw.exec()) {
Core_RunOnCPUThread([&] { bpw.addBreakpoint(); });
}
}
break;
case ID_DISASM_DELETEBREAKPOINT:
removeBreakpoint(itemIndex);
break;
}
}
}
//
// CtrlStackTraceView
//
CtrlStackTraceView::CtrlStackTraceView(HWND hwnd, DebugInterface* cpu, CtrlDisAsmView* disasm)
: GenericListControl(hwnd,stackTraceListDef),cpu(cpu),disasm(disasm)
{
Update();
}
bool CtrlStackTraceView::WindowMessage(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT& returnValue)
{
switch(msg)
{
case WM_KEYDOWN:
if (wParam == VK_TAB)
{
returnValue = 0;
SendMessage(GetParent(GetHandle()),WM_DEB_TABPRESSED,0,0);
return true;
}
break;
case WM_GETDLGCODE:
if (lParam && ((MSG*)lParam)->message == WM_KEYDOWN)
{
if (wParam == VK_TAB || wParam == VK_RETURN)
{
returnValue = DLGC_WANTMESSAGE;
return true;
}
}
break;
}
return false;
}
void CtrlStackTraceView::GetColumnText(wchar_t* dest, size_t destSize, int row, int col)
{
// We should have emptied the list if g_symbolMap is nullptr, but apparently we don't,
// so let's have a sanity check here.
if (row < 0 || row >= (int)frames.size() || !g_symbolMap) {
return;
}
switch (col)
{
case SF_ENTRY:
wsprintf(dest,L"%08X",frames[row].entry);
break;
case SF_ENTRYNAME:
{
const std::string sym = g_symbolMap->GetLabelString(frames[row].entry);
if (!sym.empty()) {
wcscpy(dest, ConvertUTF8ToWString(sym).c_str());
} else {
wcscpy(dest,L"-");
}
}
break;
case SF_CURPC:
wsprintf(dest,L"%08X",frames[row].pc);
break;
case SF_CUROPCODE:
{
char temp[512];
disasm->getOpcodeText(frames[row].pc, temp, sizeof(temp));
wcscpy(dest, ConvertUTF8ToWString(temp).c_str());
}
break;
case SF_CURSP:
wsprintf(dest,L"%08X",frames[row].sp);
break;
case SF_FRAMESIZE:
wsprintf(dest,L"%08X",frames[row].stackSize);
break;
case SF_SOURCE:
{
// Empty unless the game shipped an unstripped ELF - see Core/Debugger/LineInfo.h.
const std::string source = g_lineInfo.LookupString(frames[row].pc);
wcscpy(dest, source.empty() ? L"-" : ConvertUTF8ToWString(source).c_str());
}
break;
}
}
void CtrlStackTraceView::OnDoubleClick(int itemIndex, int column)
{
SendMessage(GetParent(GetHandle()),WM_DEB_GOTOWPARAM,frames[itemIndex].pc,0);
}
void CtrlStackTraceView::loadStackTrace() {
// Reading live thread/register/stack state here on the GUI thread would otherwise race with
// the CPU thread - hold g_frameMutex for the duration of the read, which NativeFrame() also
// holds while it's actually touching that state. See g_frameMutex in Core.h.
//
// g_frameMutex first, then Core_LockAgainstShutdown() - never the other way around. See CtrlMemView::onPaint.
std::lock_guard<std::mutex> frameGuard(g_frameMutex);
CoreShutdownLock coreLock = Core_LockAgainstShutdown();
if (!PSP_IsInited())
return;
std::vector<DebugThreadInfo> threads = GetThreadsInfo();
u32 entry = 0, stackTop = 0;
for (size_t i = 0; i < threads.size(); i++)
{
if (threads[i].isCurrent)
{
entry = threads[i].entrypoint;
stackTop = threads[i].initialStack;
break;
}
}
if (entry != 0) {
frames = MIPSStackWalk::Walk(cpu->GetPC(),cpu->GetRegValue(0,31),cpu->GetRegValue(0,29),entry,stackTop);
} else {
frames.clear();
}
Update();
}
//
// CtrlModuleList
//
CtrlModuleList::CtrlModuleList(HWND hwnd, DebugInterface* cpu)
: GenericListControl(hwnd,moduleListDef),cpu(cpu)
{
Update();
}
bool CtrlModuleList::WindowMessage(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT& returnValue)
{
switch(msg)
{
case WM_KEYDOWN:
if (wParam == VK_TAB)
{
returnValue = 0;
SendMessage(GetParent(GetHandle()),WM_DEB_TABPRESSED,0,0);
return true;
}
break;
case WM_GETDLGCODE:
if (lParam && ((MSG*)lParam)->message == WM_KEYDOWN)
{
if (wParam == VK_TAB || wParam == VK_RETURN)
{
returnValue = DLGC_WANTMESSAGE;
return true;
}
}
break;
}
return false;
}
void CtrlModuleList::GetColumnText(wchar_t* dest, size_t destSize, int row, int col)
{
if (row < 0 || row >= (int)modules.size()) {
return;
}
switch (col) {
case ML_NAME:
ConvertUTF8ToWString(dest, destSize, modules[row].name);
break;
case ML_ADDRESS:
wsprintf(dest,L"%08X",modules[row].address);
break;
case ML_SIZE:
wsprintf(dest,L"%08X",modules[row].size);
break;
case ML_ACTIVE:
wcscpy(dest,modules[row].active ? L"true" : L"false");
break;
}
}
void CtrlModuleList::OnDoubleClick(int itemIndex, int column)
{
SendMessage(GetParent(GetHandle()),WM_DEB_GOTOWPARAM,modules[itemIndex].address,0);
}
void CtrlModuleList::loadModules()
{
// Reading the live symbol map here on the GUI thread would otherwise race with the CPU thread -
// hold g_frameMutex for the duration of the read, which NativeFrame() also holds while it's
// actually touching that state. See g_frameMutex in Core.h.
{
std::lock_guard<std::mutex> frameGuard(g_frameMutex);
CoreShutdownLock coreLock = Core_LockAgainstShutdown();
if (g_symbolMap) {
modules = g_symbolMap->getAllModules();
} else {
modules.clear();
}
}
Update();
}
// In case you modify things in the memory view.
static constexpr UINT_PTR IDT_CHECK_REFRESH = 0xC0DE0044;
CtrlWatchList::CtrlWatchList(HWND hwnd, DebugInterface *cpu)
: GenericListControl(hwnd, watchListDef), cpu_(cpu) {
SetSendInvalidRows(true);
Update();
SetTimer(GetHandle(), IDT_CHECK_REFRESH, 1000U, nullptr);
}
void CtrlWatchList::RefreshValues() {
// Evaluating watch expressions reads live registers/memory here on the GUI thread, which would
// otherwise race with the CPU thread - hold g_frameMutex for the duration, which NativeFrame()
// also holds while it's actually touching that state. See g_frameMutex in Core.h.
std::lock_guard<std::mutex> frameGuard(g_frameMutex);
CoreShutdownLock coreLock = Core_LockAgainstShutdown();
int steppingCounter = Core_GetSteppingCounter();
int changes = false;
for (auto &watch : watches_) {
if (watch.steppingCounter != steppingCounter) {
watch.lastValue = watch.currentValue;
watch.steppingCounter = steppingCounter;
changes = true;
}
uint32_t prevValue = watch.currentValue;
watch.evaluateFailed = !parseExpression(cpu_, watch.expression, watch.currentValue);
if (prevValue != watch.currentValue)
changes = true;
}
if (changes)
Update();
}
bool CtrlWatchList::WindowMessage(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT &returnValue) {
switch (msg) {
case WM_KEYDOWN:
switch (wParam) {
case VK_TAB:
returnValue = 0;
SendMessage(GetParent(GetHandle()), WM_DEB_TABPRESSED, 0, 0);
return true;
case VK_RETURN:
returnValue = 0;
EditWatch(GetSelectedIndex());
return true;
case VK_DELETE:
returnValue = 0;
DeleteWatch(GetSelectedIndex());
return true;
default:
break;
}
break;
case WM_GETDLGCODE:
if (lParam && ((MSG *)lParam)->message == WM_KEYDOWN) {
if (wParam == VK_TAB || wParam == VK_RETURN || wParam == VK_DELETE) {
returnValue = DLGC_WANTMESSAGE;
return true;
}
}
break;
case WM_TIMER:
if (wParam == IDT_CHECK_REFRESH) {
RefreshValues();
return true;
}
break;
}
return false;
}
void CtrlWatchList::GetColumnText(wchar_t *dest, size_t destSize, int row, int col) {
const auto &watch = watches_[row];
switch (col) {
case WL_NAME:
wcsncpy(dest, ConvertUTF8ToWString(watch.name).c_str(), 255);
dest[255] = 0;
break;
case WL_EXPRESSION:
wcsncpy(dest, ConvertUTF8ToWString(watch.originalExpression).c_str(), 255);
dest[255] = 0;
break;
case WL_VALUE:
if (watch.evaluateFailed) {
wcscpy(dest, L"(failed to evaluate)");
} else {
const uint32_t &value = watch.currentValue;
float valuef = 0.0f;
switch (watch.format) {
case WatchFormat::HEX:
wsprintf(dest, L"0x%08X", value);
break;
case WatchFormat::INT:
wsprintf(dest, L"%d", (int32_t)value);
break;
case WatchFormat::FLOAT:
memcpy(&valuef, &value, sizeof(valuef));
swprintf_s(dest, destSize, L"%f", valuef);
break;
case WatchFormat::STR:
if (Memory::IsValidAddress(value)) {
uint32_t len = Memory::ClampValidSizeAt(value, 255);
swprintf_s(dest, destSize, L"%.*S", len, Memory::GetCharPointer(value));
} else {
wsprintf(dest, L"(0x%08X)", value);
}
break;
}
}
break;
}
}
void CtrlWatchList::OnRightClick(int itemIndex, int column, const POINT &pt) {
if (itemIndex == -1) {
switch (TriggerContextMenu(ContextMenuID::CPUADDWATCH, GetHandle(), ContextPoint::FromClient(pt))) {
case ID_DISASM_ADDNEWBREAKPOINT:
AddWatch();
break;
}
} else {
switch (TriggerContextMenu(ContextMenuID::CPUWATCHLIST, GetHandle(), ContextPoint::FromClient(pt))) {
case ID_DISASM_EDITBREAKPOINT:
EditWatch(itemIndex);
break;
case ID_DISASM_DELETEBREAKPOINT:
DeleteWatch(itemIndex);
break;
case ID_DISASM_ADDNEWBREAKPOINT:
AddWatch();
break;
}
}
}
bool CtrlWatchList::OnRowPrePaint(int row, LPNMLVCUSTOMDRAW msg) {
if (row >= 0 && HasWatchChanged(row)) {
msg->clrText = RGB(255, 0, 0);
return true;
}
return false;
}
void CtrlWatchList::AddWatch() {
WatchItemWindow win(nullptr, GetHandle(), cpu_);
if (win.Exec()) {
WatchInfo info;
if (initExpression(cpu_, win.GetExpression().c_str(), info.expression)) {
info.name = win.GetName();
info.originalExpression = win.GetExpression();
info.format = win.GetFormat();
watches_.push_back(info);
RefreshValues();
} else {
char errorMessage[512];
snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", win.GetExpression().c_str(), getExpressionError());
MessageBoxA(GetHandle(), errorMessage, "Error", MB_OK);
}
}
}
void CtrlWatchList::EditWatch(int pos) {
auto &watch = watches_[pos];
WatchItemWindow win(nullptr, GetHandle(), cpu_);
win.Init(watch.name, watch.originalExpression, watch.format);
if (win.Exec()) {
if (initExpression(cpu_, win.GetExpression().c_str(), watch.expression)) {
watch.name = win.GetName();
watch.originalExpression = win.GetExpression();
watch.format = win.GetFormat();
RefreshValues();
} else {
char errorMessage[512];
snprintf(errorMessage, sizeof(errorMessage), "Invalid expression \"%s\": %s", win.GetExpression().c_str(), getExpressionError());
MessageBoxA(GetHandle(), errorMessage, "Error", MB_OK);
}
}
}
void CtrlWatchList::DeleteWatch(int pos) {
watches_.erase(watches_.begin() + pos);
Update();
}
bool CtrlWatchList::HasWatchChanged(int pos) {
return watches_[pos].lastValue != watches_[pos].currentValue;
}