Files
ppsspp/Windows/Debugger/CtrlDisAsmView.cpp
Henrik RydgårdandClaude Opus 5 3f0c2da5a9 Debugger: surface source line info in six more places
Following on from the DWARF line table: the lookup was only reachable from
hle.backtrace, the breakpoint hit object and the ImGui disassembly status bar.
Now also in

- the ImDebugger call stack (new Source column),
- the Win32 call stack (new Source column),
- the Win32 disassembly status bar, matching the ImGui one,
- the ImDisasmView right-click menu, which showed a bare address as its heading
  and now leads with "mesh.zig:163 (08841f98)" when there's a line for it,
- breakpoint log lines - a log-only breakpoint's entire output is those lines,
  and "BKP PC=08841f98 mesh.zig:163" reads a great deal better than an address
  when you're scanning a few thousand of them,
- crash stack traces, via FormatStackTrace, which is what the crash screen and
  crash reporting both use.

That last one is where it earns its keep, and it needed the invalid-jump path to
produce a stack trace at all - it was the one exec exception that didn't. It's
also the one that most deserves it: the address it jumped to tells you nothing,
the callers tell you everything. Execution has already moved to the bad address
by the time it's noticed, so a walk from pc finds no function to start from;
WalkCurrentStack takes an explicit starting pc now, and falling back to ra
recovers the chain. Reproducing the original CrossCraft bug:

  CPU Jump: Invalid jump to ae870000 from PC ae870000(invalid) RA 08841f98
  MIPS call stack:
  rendering.mesh.Mesh(PspVertex).draw at mesh.zig:163 (08841c30+368, ...)
  state.MenuState.draw at MenuState.zig:821 (0883ab90+414, ...)
  engine.Engine.stepFrameInternal at State.zig:40 (08820f74+5164, ...)
  utils.module._module_main_thread at engine.zig:468 (088272c4+2fb8, ...)

Fixed a pre-existing double-report while in there: every case in
Core_ExecException sent its message and then fell through to an unconditional
send of the same message, so each exec exception was logged twice. The message
is built in the switch and sent once at the end now.

pspautotests 314/314, UnitTest 55/55.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-18 13:42:14 +02:00

1364 lines
38 KiB
C++

#include "Windows/resource.h"
#include "Core/MemMap.h"
#include "Windows/W32Util/ContextMenu.h"
#include "Windows/W32Util/Misc.h"
#include "Windows/W32Util/ShellUtil.h"
#include "Windows/MainWindow.h"
#include "Windows/InputBox.h"
#include "Core/Core.h"
#include "Core/MIPS/MIPSAsm.h"
#include "Core/MIPS/MIPSAnalyst.h"
#include "Core/MIPS/MIPSTables.h"
#include "Core/Config.h"
#include "Core/Debugger/Breakpoints.h"
#include "Core/Debugger/LineInfo.h"
#include "Core/Debugger/SymbolMap.h"
#include "Core/Reporting.h"
#include "Common/StringUtils.h"
#include "Windows/Debugger/CtrlDisAsmView.h"
#include "Windows/Debugger/DebuggerShared.h"
#include "Windows/Debugger/BreakpointWindow.h"
#include "Windows/Debugger/EditSymbolsWindow.h"
#include "Core/RetroAchievements.h"
#include "Windows/main.h"
#include "Common/CommonWindows.h"
#include "Common/Data/Encoding/Utf8.h"
#include "Common/System/Display.h"
#include <set>
constexpr const wchar_t *szClassName = L"CtrlDisAsmView";
static constexpr UINT_PTR IDT_REDRAW = 0xC0DE0001;
static constexpr UINT REDRAW_DELAY = 1000 / 60;
void CtrlDisAsmView::init()
{
WNDCLASSEX wc;
wc.cbSize = sizeof(wc);
wc.lpszClassName = szClassName;
wc.hInstance = GetModuleHandle(0);
wc.lpfnWndProc = CtrlDisAsmView::wndProc;
wc.hCursor = LoadCursor (NULL, IDC_ARROW);
wc.hIcon = 0;
wc.lpszMenuName = 0;
wc.hbrBackground = (HBRUSH)GetSysColorBrush(COLOR_WINDOW);
wc.style = 0;
wc.cbClsExtra = 0;
wc.cbWndExtra = sizeof( CtrlDisAsmView * );
wc.hIconSm = 0;
RegisterClassEx(&wc);
}
void CtrlDisAsmView::deinit()
{
//UnregisterClass(szClassName, hInst)
}
void CtrlDisAsmView::scanVisibleFunctions()
{
// Reads live memory/symbol state to detect function boundaries - hold g_frameMutex for the
// duration, which NativeFrame() also holds while it's actually touching that state, and
// Core_LockAgainstShutdown() so the core can't be torn down mid-read. See g_frameMutex in Core.h.
std::lock_guard<std::mutex> frameGuard(g_frameMutex);
CoreShutdownLock coreLock = Core_LockAgainstShutdown();
g_disassemblyManager.analyze(windowStart, g_disassemblyManager.getNthNextAddress(windowStart,visibleRows)-windowStart);
}
LRESULT CALLBACK CtrlDisAsmView::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
CtrlDisAsmView *ccp = CtrlDisAsmView::getFrom(hwnd);
static bool lmbDown=false,rmbDown=false;
switch(msg)
{
case WM_NCCREATE:
// Allocate a new CustCtrl structure for this window.
ccp = new CtrlDisAsmView(hwnd);
// Continue with window creation.
return ccp != NULL;
// Clean up when the window is destroyed.
case WM_NCDESTROY:
delete ccp;
break;
case WM_SETFONT:
break;
case WM_SIZE:
ccp->redraw();
break;
case WM_PAINT:
ccp->onPaint(wParam,lParam);
break;
case WM_VSCROLL:
ccp->onVScroll(wParam,lParam);
break;
case WM_MOUSEWHEEL:
ccp->dontRedraw = false;
if (GET_WHEEL_DELTA_WPARAM(wParam) > 0)
{
ccp->scrollWindow(-3);
} else if (GET_WHEEL_DELTA_WPARAM(wParam) < 0) {
ccp->scrollWindow(3);
}
break;
case WM_ERASEBKGND:
return FALSE;
case WM_KEYDOWN:
ccp->onKeyDown(wParam,lParam);
return 0;
case WM_CHAR:
ccp->onChar(wParam,lParam);
return 0;
case WM_SYSKEYDOWN:
ccp->onKeyDown(wParam,lParam);
return 0; // return a value so that windows doesn't execute the standard syskey action
case WM_KEYUP:
ccp->onKeyUp(wParam,lParam);
return 0;
case WM_LBUTTONDOWN: lmbDown=true; ccp->onMouseDown(wParam,lParam,1); break;
case WM_RBUTTONDOWN: rmbDown=true; ccp->onMouseDown(wParam,lParam,2); break;
case WM_MOUSEMOVE: ccp->onMouseMove(wParam,lParam,(lmbDown?1:0) | (rmbDown?2:0)); break;
case WM_LBUTTONUP: lmbDown=false; ccp->onMouseUp(wParam,lParam,1); break;
case WM_RBUTTONUP: rmbDown=false; ccp->onMouseUp(wParam,lParam,2); break;
case WM_SETFOCUS:
SetFocus(hwnd);
ccp->hasFocus=true;
ccp->redraw();
break;
case WM_KILLFOCUS:
ccp->hasFocus=false;
lmbDown = false;
rmbDown = false;
ccp->redraw();
break;
case WM_GETDLGCODE:
if (lParam && ((MSG*)lParam)->message == WM_KEYDOWN)
{
switch (wParam)
{
case VK_TAB:
return DLGC_WANTMESSAGE;
default:
return DLGC_WANTCHARS|DLGC_WANTARROWS;
}
}
return DLGC_WANTCHARS|DLGC_WANTARROWS;
case WM_TIMER:
if (wParam == IDT_REDRAW) {
InvalidateRect(hwnd, nullptr, FALSE);
UpdateWindow(hwnd);
ccp->redrawScheduled_ = false;
KillTimer(hwnd, wParam);
}
break;
default:
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
CtrlDisAsmView *CtrlDisAsmView::getFrom(HWND hwnd)
{
return (CtrlDisAsmView *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
}
CtrlDisAsmView::CtrlDisAsmView(HWND _wnd)
{
wnd=_wnd;
SetWindowLongPtr(wnd, GWLP_USERDATA, (LONG_PTR)this);
SetWindowLong(wnd, GWL_STYLE, GetWindowLong(wnd,GWL_STYLE) | WS_VSCROLL);
SetScrollRange(wnd, SB_VERT, -1, 1, TRUE);
const float fontScale = 1.0f / g_display.dpi_scale_real_y;
charWidth = g_Config.iFontWidth * fontScale;
rowHeight = (g_Config.iFontHeight + 2) * fontScale;
int scaledFontHeight = g_Config.iFontHeight * fontScale;
font = CreateFont(scaledFontHeight, charWidth, 0, 0,
FW_DONTCARE, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH,
L"Lucida Console");
boldfont = CreateFont(scaledFontHeight, charWidth, 0, 0,
FW_DEMIBOLD, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH,
L"Lucida Console");
curAddress = 0;
showHex = false;
hasFocus = false;
dontRedraw = false;
keyTaken = false;
matchAddress = -1;
searching = false;
searchQuery.clear();
windowStart = curAddress;
whiteBackground = false;
displaySymbols = true;
calculatePixelPositions();
}
CtrlDisAsmView::~CtrlDisAsmView()
{
DeleteObject(font);
DeleteObject(boldfont);
g_disassemblyManager.clear();
}
static COLORREF scaleColor(COLORREF color, float factor)
{
unsigned char r = color & 0xFF;
unsigned char g = (color >> 8) & 0xFF;
unsigned char b = (color >> 16) & 0xFF;
r = std::min(255, std::max((int)(r * factor), 0));
g = std::min(255, std::max((int)(g * factor), 0));
b = std::min(255, std::max((int)(b * factor), 0));
return (color & 0xFF000000) | (b << 16) | (g << 8) | r;
}
std::string trimString(std::string input)
{
size_t pos = input.find_first_not_of(" \t");
if (pos != 0 && pos != std::string::npos)
{
input = input.erase(0,pos);
}
pos = input.find_last_not_of(" \t");
if (pos != std::string::npos)
{
size_t size = input.length()-pos-1;
input = input.erase(pos+1,size);
}
return input;
}
void CtrlDisAsmView::assembleOpcode(u32 address, const std::string &defaultText)
{
CoreShutdownLock coreLock = Core_LockAgainstShutdown();
if (!Core_IsStepping()) {
MessageBox(wnd,L"Cannot change code while the core is running!",L"Error",MB_OK);
return;
}
std::string op;
bool result = InputBox_GetString(MainWindow::GetHInstance(), wnd, L"Assemble opcode", defaultText, op, InputBoxFlags::Default);
if (!result) {
return;
}
// check if it changes registers first
size_t separator = op.find('=');
if (separator != std::string::npos)
{
std::string registerName = trimString(op.substr(0,separator));
std::string expression = trimString(op.substr(separator+1));
u32 value;
if (parseExpression(expression.c_str(),debugger,value) == true)
{
for (int cat = 0; cat < debugger->GetNumCategories(); cat++)
{
for (int reg = 0; reg < debugger->GetNumRegsInCategory(cat); reg++)
{
if (strcasecmp(debugger->GetRegName(cat,reg).c_str(), registerName.c_str()) == 0)
{
// Route the actual register write to the CPU thread instead of poking at it
// directly from this GUI thread - see Core_RunOnCPUThread() in Core.h.
Core_RunOnCPUThread([&] {
debugger->SetRegValue(cat,reg,value);
Reporting::NotifyDebugger();
});
SendMessage(GetParent(wnd),WM_DEB_UPDATE,0,0);
return;
}
}
}
}
// try to assemble the input if it failed
}
// Route the actual code assembly to the CPU thread instead of poking at it directly
// from this GUI thread - see Core_RunOnCPUThread() in Core.h.
std::string error;
Core_RunOnCPUThread([&] {
result = MipsAssembleOpcode(op, debugger, address, &error);
Reporting::NotifyDebugger();
});
if (result) {
scanVisibleFunctions();
if (address == curAddress) {
gotoAddr(g_disassemblyManager.getNthNextAddress(curAddress, 1));
}
redraw();
} else {
std::wstring werror = ConvertUTF8ToWString(error.c_str());
MessageBox(wnd, werror.c_str(), L"Error", MB_OK);
}
}
void CtrlDisAsmView::drawBranchLine(HDC hdc, std::map<u32,int> &addressPositions, const BranchLine &line) {
HPEN pen;
u32 windowEnd = g_disassemblyManager.getNthNextAddress(windowStart,visibleRows);
int topY;
int bottomY;
if (line.first < windowStart) {
topY = -1;
} else if (line.first >= windowEnd) {
topY = rect.bottom+1;
} else {
topY = addressPositions[line.first] + rowHeight/2;
}
if (line.second < windowStart) {
bottomY = -1;
} else if (line.second >= windowEnd) {
bottomY = rect.bottom+1;
} else {
bottomY = addressPositions[line.second] + rowHeight/2;
}
if ((topY < 0 && bottomY < 0) || (topY > rect.bottom && bottomY > rect.bottom)) {
return;
}
// highlight line in a different color if it affects the currently selected opcode
if (line.first == curAddress || line.second == curAddress) {
pen = CreatePen(0,0,0x257AFA);
} else {
pen = CreatePen(0,0,0xFF3020);
}
HPEN oldPen = (HPEN) SelectObject(hdc,pen);
int x = pixelPositions.arrowsStart+line.laneIndex*8;
if (topY < 0) // first is not visible, but second is
{
MoveToEx(hdc,x-2,bottomY,0);
LineTo(hdc,x+2,bottomY);
LineTo(hdc,x+2,0);
if (line.type == LINE_DOWN)
{
MoveToEx(hdc,x,bottomY-4,0);
LineTo(hdc,x-4,bottomY);
LineTo(hdc,x+1,bottomY+5);
}
} else if (bottomY > rect.bottom) // second is not visible, but first is
{
MoveToEx(hdc,x-2,topY,0);
LineTo(hdc,x+2,topY);
LineTo(hdc,x+2,rect.bottom);
if (line.type == LINE_UP)
{
MoveToEx(hdc,x,topY-4,0);
LineTo(hdc,x-4,topY);
LineTo(hdc,x+1,topY+5);
}
} else { // both are visible
if (line.type == LINE_UP)
{
MoveToEx(hdc,x-2,bottomY,0);
LineTo(hdc,x+2,bottomY);
LineTo(hdc,x+2,topY);
LineTo(hdc,x-4,topY);
MoveToEx(hdc,x,topY-4,0);
LineTo(hdc,x-4,topY);
LineTo(hdc,x+1,topY+5);
} else {
MoveToEx(hdc,x-2,topY,0);
LineTo(hdc,x+2,topY);
LineTo(hdc,x+2,bottomY);
LineTo(hdc,x-4,bottomY);
MoveToEx(hdc,x,bottomY-4,0);
LineTo(hdc,x-4,bottomY);
LineTo(hdc,x+1,bottomY+5);
}
}
SelectObject(hdc,oldPen);
DeleteObject(pen);
}
std::set<std::string> CtrlDisAsmView::getSelectedLineArguments() {
std::set<std::string> args;
DisassemblyLineInfo line;
for (u32 addr = selectRangeStart; addr < selectRangeEnd; addr += 4) {
g_disassemblyManager.getLine(addr, displaySymbols, line, debugger);
size_t p = 0, nextp = line.params.find(',');
while (nextp != line.params.npos) {
args.emplace(line.params.substr(p, nextp - p));
p = nextp + 1;
nextp = line.params.find(',', p);
}
if (p < line.params.size()) {
args.emplace(line.params.substr(p));
}
}
return args;
}
void CtrlDisAsmView::drawArguments(HDC hdc, const DisassemblyLineInfo &line, int x, int y, int textColor, const std::set<std::string> &currentArguments) {
if (line.params.empty()) {
return;
}
// Don't highlight the selected lines.
if (isInInterval(selectRangeStart, selectRangeEnd - selectRangeStart, line.info.opcodeAddress)) {
TextOutA(hdc, x, y, line.params.c_str(), (int)line.params.size());
return;
}
int highlightedColor = 0xaabb00;
if (textColor == 0x0000ff) {
highlightedColor = 0xaabb77;
}
UINT prevAlign = SetTextAlign(hdc, TA_UPDATECP);
MoveToEx(hdc, x, y, NULL);
size_t p = 0, nextp = line.params.find(',');
while (nextp != line.params.npos) {
const std::string arg = line.params.substr(p, nextp - p);
if (currentArguments.find(arg) != currentArguments.end() && textColor != 0xffffff) {
SetTextColor(hdc, highlightedColor);
}
TextOutA(hdc, 0, 0, arg.c_str(), (int)arg.size());
SetTextColor(hdc,textColor);
p = nextp + 1;
nextp = line.params.find(',', p);
TextOutA(hdc, 0, 0, ",", 1);
}
if (p < line.params.size()) {
const std::string arg = line.params.substr(p);
if (currentArguments.find(arg) != currentArguments.end() && textColor != 0xffffff) {
SetTextColor(hdc, highlightedColor);
}
TextOutA(hdc, 0, 0, arg.c_str(), (int)arg.size());
SetTextColor(hdc,textColor);
}
SetTextAlign(hdc, prevAlign);
}
void CtrlDisAsmView::onPaint(WPARAM wParam, LPARAM lParam)
{
// Reading live disassembly/symbol/breakpoint 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 (!debugger->isAlive() || Achievements::HardcoreModeActive()) return;
PAINTSTRUCT ps;
HDC actualHdc = BeginPaint(wnd, &ps);
HDC hdc = CreateCompatibleDC(actualHdc);
HBITMAP hBM = CreateCompatibleBitmap(actualHdc, rect.right-rect.left, rect.bottom-rect.top);
SelectObject(hdc, hBM);
SetBkMode(hdc, TRANSPARENT);
HPEN nullPen=CreatePen(0,0,0xffffff);
HBRUSH nullBrush=CreateSolidBrush(0xffffff);
HBRUSH currentBrush=CreateSolidBrush(0xffefe8);
HPEN oldPen=(HPEN)SelectObject(hdc,nullPen);
HBRUSH oldBrush=(HBRUSH)SelectObject(hdc,nullBrush);
HFONT oldFont = (HFONT)SelectObject(hdc,(HGDIOBJ)font);
HICON breakPoint = (HICON)LoadIcon(GetModuleHandle(0),(LPCWSTR)IDI_STOP);
HICON breakPointDisable = (HICON)LoadIcon(GetModuleHandle(0),(LPCWSTR)IDI_STOPDISABLE);
unsigned int address = windowStart;
std::map<u32,int> addressPositions;
const std::set<std::string> currentArguments = getSelectedLineArguments();
DisassemblyLineInfo line;
for (int i = 0; i < visibleRows; i++)
{
g_disassemblyManager.getLine(address,displaySymbols,line, debugger);
int rowY1 = rowHeight*i;
int rowY2 = rowHeight*(i+1);
addressPositions[address] = rowY1;
// draw background
COLORREF backgroundColor = whiteBackground ? 0xFFFFFF : (debugger->getColor(address, false) & 0xFFFFFF);
COLORREF textColor = 0x000000;
if (isInInterval(address, line.totalSize, debugger->GetPC()))
{
backgroundColor = scaleColor(backgroundColor,1.05f);
}
if (address >= selectRangeStart && address < selectRangeEnd && searching == false)
{
if (hasFocus)
{
backgroundColor = address == curAddress ? 0xFF8822 : 0xFF9933;
textColor = 0xFFFFFF;
} else {
backgroundColor = 0xC0C0C0;
}
}
HBRUSH backgroundBrush = CreateSolidBrush(backgroundColor);
HPEN backgroundPen = CreatePen(0,0,backgroundColor);
SelectObject(hdc,backgroundBrush);
SelectObject(hdc,backgroundPen);
Rectangle(hdc,0,rowY1,rect.right,rowY1+rowHeight);
SelectObject(hdc,currentBrush);
SelectObject(hdc,nullPen);
DeleteObject(backgroundBrush);
DeleteObject(backgroundPen);
// display address/symbol
bool enabled;
if (g_breakpoints.IsAddressBreakPoint(address,&enabled))
{
if (enabled) textColor = 0x0000FF;
int yOffset = std::max(-1, (rowHeight - 14 + 1) / 2);
if (!enabled) yOffset++;
DrawIconEx(hdc,2,rowY1+1+yOffset,enabled ? breakPoint : breakPointDisable,32,32,0,0,DI_NORMAL);
}
SetTextColor(hdc,textColor);
char addressText[64];
GetDisasmAddressText(address, addressText, sizeof(addressText), true, line.type == DISTYPE_OPCODE, displaySymbols);
TextOutA(hdc, pixelPositions.addressStart, rowY1+2, addressText, (int)strlen(addressText));
if (isInInterval(address,line.totalSize,debugger->GetPC()))
{
TextOut(hdc,pixelPositions.opcodeStart-8,rowY1,L"\x25A0",1);
}
// display whether the condition of a branch is met
if (line.info.isConditional && address == debugger->GetPC())
{
line.params += line.info.conditionMet ? " ; true" : " ; false";
}
drawArguments(hdc, line, pixelPositions.argumentsStart, rowY1 + 2, textColor, currentArguments);
SelectObject(hdc,boldfont);
TextOutA(hdc,pixelPositions.opcodeStart,rowY1+2,line.name.c_str(),(int)line.name.size());
SelectObject(hdc,font);
address += line.totalSize;
}
std::vector<BranchLine> branchLines = g_disassemblyManager.getBranchLines(windowStart,address-windowStart);
for (size_t i = 0; i < branchLines.size(); i++)
{
drawBranchLine(hdc,addressPositions,branchLines[i]);
}
SelectObject(hdc,oldFont);
SelectObject(hdc,oldPen);
SelectObject(hdc,oldBrush);
// copy bitmap to the actual hdc
BitBlt(actualHdc, 0, 0, rect.right, rect.bottom, hdc, 0, 0, SRCCOPY);
DeleteObject(hBM);
DeleteDC(hdc);
DeleteObject(nullPen);
DeleteObject(nullBrush);
DeleteObject(currentBrush);
DestroyIcon(breakPoint);
DestroyIcon(breakPointDisable);
EndPaint(wnd, &ps);
}
void CtrlDisAsmView::onVScroll(WPARAM wParam, LPARAM lParam)
{
switch (wParam & 0xFFFF)
{
case SB_LINEDOWN:
windowStart = g_disassemblyManager.getNthNextAddress(windowStart,1);
break;
case SB_LINEUP:
windowStart = g_disassemblyManager.getNthPreviousAddress(windowStart,1);
break;
case SB_PAGEDOWN:
windowStart = g_disassemblyManager.getNthNextAddress(windowStart,visibleRows);
break;
case SB_PAGEUP:
windowStart = g_disassemblyManager.getNthPreviousAddress(windowStart,visibleRows);
break;
default:
return;
}
scanVisibleFunctions();
redraw();
}
void CtrlDisAsmView::followBranch()
{
DisassemblyLineInfo line;
g_disassemblyManager.getLine(curAddress, true, line, debugger);
if (line.type == DISTYPE_OPCODE)
{
if (line.info.isBranch)
{
jumpStack.push_back(curAddress);
gotoAddr(line.info.branchTarget);
} else if (line.info.hasRelevantAddress)
{
// well, not exactly a branch, but we can do something anyway
SendMessage(GetParent(wnd),WM_DEB_GOTOHEXEDIT,line.info.relevantAddress,0);
SetFocus(wnd);
}
} else if (line.type == DISTYPE_DATA)
{
// jump to the start of the current line
SendMessage(GetParent(wnd),WM_DEB_GOTOHEXEDIT,curAddress,0);
SetFocus(wnd);
}
}
void CtrlDisAsmView::onChar(WPARAM wParam, LPARAM lParam)
{
if (Achievements::HardcoreModeActive())
return;
if (keyTaken) return;
char str[2];
str[0] = wParam;
str[1] = 0;
assembleOpcode(curAddress,str);
}
void CtrlDisAsmView::editBreakpoint()
{
BreakpointWindow win(wnd,debugger);
// Route the actual breakpoint read to the CPU thread instead of poking at it directly
// from this GUI thread - see Core_RunOnCPUThread() in Core.h.
bool exists = false;
Core_RunOnCPUThread([&] {
if (g_breakpoints.IsAddressBreakPoint(curAddress))
{
std::vector<BreakPoint> breakpoints = g_breakpoints.GetBreakpoints();
for (size_t i = 0; i < breakpoints.size(); i++)
{
if (breakpoints[i].addr == curAddress)
{
win.loadFromBreakpoint(breakpoints[i]);
exists = true;
break;
}
}
}
});
if (!exists)
win.initBreakpoint(curAddress);
// win.exec() is a modal dialog - must stay outside any queued callback, or we'd block the
// CPU thread on user input.
if (win.exec())
{
Core_RunOnCPUThread([&] {
if (exists)
g_breakpoints.RemoveBreakPoint(curAddress);
win.addBreakpoint();
});
}
}
void CtrlDisAsmView::onKeyDown(WPARAM wParam, LPARAM lParam)
{
if (Achievements::HardcoreModeActive())
return;
dontRedraw = false;
u32 windowEnd = g_disassemblyManager.getNthNextAddress(windowStart,visibleRows);
keyTaken = true;
if (KeyDownAsync(VK_CONTROL))
{
switch (tolower(wParam & 0xFFFF))
{
case 'f':
case 's':
search(false);
break;
case 'c':
case VK_INSERT:
CopyInstructions(selectRangeStart, selectRangeEnd, CopyInstructionsMode::DISASM);
break;
case 'x':
disassembleToFile();
break;
case 'a':
assembleOpcode(curAddress,"");
break;
case 'g':
{
u32 addr;
if (executeExpressionWindow(wnd,debugger,addr) == false) return;
gotoAddr(addr);
}
break;
case 'e': // edit breakpoint
editBreakpoint();
break;
case 'd': // toogle breakpoint enabled
toggleBreakpoint(true);
break;
case VK_UP:
scrollWindow(-1);
scanVisibleFunctions();
break;
case VK_DOWN:
scrollWindow(1);
scanVisibleFunctions();
break;
case VK_NEXT:
setCurAddress(g_disassemblyManager.getNthPreviousAddress(windowEnd,1),KeyDownAsync(VK_SHIFT));
break;
case VK_PRIOR:
setCurAddress(windowStart,KeyDownAsync(VK_SHIFT));
break;
}
} else {
switch (wParam & 0xFFFF)
{
case VK_DOWN:
setCurAddress(g_disassemblyManager.getNthNextAddress(curAddress,1), KeyDownAsync(VK_SHIFT));
scrollAddressIntoView();
break;
case VK_UP:
setCurAddress(g_disassemblyManager.getNthPreviousAddress(curAddress,1), KeyDownAsync(VK_SHIFT));
scrollAddressIntoView();
break;
case VK_NEXT:
if (g_disassemblyManager.getNthNextAddress(curAddress,1) != windowEnd && curAddressIsVisible()) {
setCurAddress(g_disassemblyManager.getNthPreviousAddress(windowEnd,1), KeyDownAsync(VK_SHIFT));
scrollAddressIntoView();
} else {
setCurAddress(g_disassemblyManager.getNthNextAddress(windowEnd,visibleRows-1), KeyDownAsync(VK_SHIFT));
scrollAddressIntoView();
}
break;
case VK_PRIOR:
if (curAddress != windowStart && curAddressIsVisible()) {
setCurAddress(windowStart, KeyDownAsync(VK_SHIFT));
scrollAddressIntoView();
} else {
setCurAddress(g_disassemblyManager.getNthPreviousAddress(windowStart,visibleRows), KeyDownAsync(VK_SHIFT));
scrollAddressIntoView();
}
break;
case VK_LEFT:
if (jumpStack.empty())
{
gotoPC();
} else {
u32 addr = jumpStack[jumpStack.size()-1];
jumpStack.pop_back();
gotoAddr(addr);
}
return;
case VK_RIGHT:
followBranch();
return;
case VK_TAB:
displaySymbols = !displaySymbols;
break;
case VK_SPACE:
{
u32 toggleAddress = curAddress;
Core_RunOnCPUThread([&] { debugger->toggleBreakpoint(toggleAddress); });
}
break;
case VK_F3:
search(true);
break;
default:
keyTaken = false;
return;
}
}
redraw();
}
void CtrlDisAsmView::onKeyUp(WPARAM wParam, LPARAM lParam)
{
}
void CtrlDisAsmView::scrollAddressIntoView()
{
u32 windowEnd = g_disassemblyManager.getNthNextAddress(windowStart,visibleRows);
if (curAddress < windowStart)
windowStart = curAddress;
else if (curAddress >= windowEnd)
windowStart = g_disassemblyManager.getNthPreviousAddress(curAddress,visibleRows-1);
scanVisibleFunctions();
}
bool CtrlDisAsmView::curAddressIsVisible()
{
u32 windowEnd = g_disassemblyManager.getNthNextAddress(windowStart,visibleRows);
return curAddress >= windowStart && curAddress < windowEnd;
}
void CtrlDisAsmView::redraw()
{
if (dontRedraw == true) return;
GetClientRect(wnd, &rect);
visibleRows = rect.bottom/rowHeight;
if (!redrawScheduled_) {
SetTimer(wnd, IDT_REDRAW, REDRAW_DELAY, nullptr);
redrawScheduled_ = true;
}
}
void CtrlDisAsmView::toggleBreakpoint(bool toggleEnabled)
{
// Route the breakpoint read to the CPU thread instead of poking at it directly from this GUI
// thread - see Core_RunOnCPUThread() in Core.h. The possible MessageBox() below is modal, so we
// can't just wrap the whole function - figure out what to do first, ask if needed, then mutate.
bool exists = false, enabled = false, hasCondition = false;
Core_RunOnCPUThread([&] {
exists = g_breakpoints.IsAddressBreakPoint(curAddress, &enabled);
if (exists)
hasCondition = g_breakpoints.GetBreakPointCondition(curAddress) != nullptr;
});
if (exists) {
if (!enabled) {
// enable disabled breakpoints
Core_RunOnCPUThread([&] { g_breakpoints.ChangeBreakPoint(curAddress, true); });
} else if (!toggleEnabled && hasCondition) {
// don't just delete a breakpoint with a custom condition
int ret = MessageBox(wnd,L"This breakpoint has a custom condition.\nDo you want to remove it?",L"Confirmation",MB_YESNO);
if (ret == IDYES)
Core_RunOnCPUThread([&] { g_breakpoints.RemoveBreakPoint(curAddress); });
} else if (toggleEnabled) {
// disable breakpoint
Core_RunOnCPUThread([&] { g_breakpoints.ChangeBreakPoint(curAddress, false); });
} else {
// otherwise just remove breakpoint
Core_RunOnCPUThread([&] { g_breakpoints.RemoveBreakPoint(curAddress); });
}
} else {
Core_RunOnCPUThread([&] { g_breakpoints.AddBreakPoint(curAddress); });
}
}
void CtrlDisAsmView::onMouseDown(WPARAM wParam, LPARAM lParam, int button)
{
if (Achievements::HardcoreModeActive())
return;
dontRedraw = false;
int y = HIWORD(lParam);
u32 newAddress = yToAddress(y);
bool extend = KeyDownAsync(VK_SHIFT);
if (button == 1)
{
if (newAddress == curAddress && hasFocus)
{
toggleBreakpoint();
}
}
else if (button == 2)
{
// Maintain the current selection if right clicking into it.
if (newAddress >= selectRangeStart && newAddress < selectRangeEnd)
extend = true;
}
setCurAddress(newAddress, extend);
SetFocus(wnd);
redraw();
}
void CtrlDisAsmView::CopyInstructions(u32 startAddr, u32 endAddr, CopyInstructionsMode mode) {
_assert_msg_((startAddr & 3) == 0, "readMemory() can't handle unaligned reads");
if (mode != CopyInstructionsMode::DISASM) {
int instructionSize = debugger->getInstructionSize(0);
int count = (endAddr - startAddr) / instructionSize;
int space = count * 32;
char *temp = new char[space];
char *p = temp, *end = temp + space;
for (u32 pos = startAddr; pos < endAddr && p < end; pos += instructionSize)
{
u32 data = mode == CopyInstructionsMode::OPCODES ? debugger->readMemory(pos) : pos;
p += snprintf(p, end - p, "%08X", data);
// Don't leave a trailing newline.
if (pos + instructionSize < endAddr && p < end)
p += snprintf(p, end - p, "\r\n");
}
W32Util::CopyTextToClipboard(wnd, temp);
delete [] temp;
} else {
std::string disassembly = DisassembleRange(startAddr,endAddr-startAddr, displaySymbols, debugger);
W32Util::CopyTextToClipboard(wnd, disassembly);
}
}
void CtrlDisAsmView::CopyFunctionHash(u32 addr) {
MIPSAnalyst::AnalyzedFunction func;
if (MIPSAnalyst::GetAnalyzedFunctionAt(addr, &func)) {
if (func.hasHash) {
char temp[256];
snprintf(temp, sizeof(temp), "%016llx:%d = %s\n", func.hash, func.size, func.name);
W32Util::CopyTextToClipboard(wnd, temp);
} else {
MessageBox(wnd, L"No hash", L"", 0);
}
} else {
MessageBox(wnd, L"No function", L"", 0);
}
}
void CtrlDisAsmView::NopInstructions(u32 selectRangeStart, u32 selectRangeEnd) {
// Route the memory writes to the CPU thread instead of poking at it directly from this GUI
// thread - see Core_RunOnCPUThread() in Core.h.
Core_RunOnCPUThread([&] {
if (!Memory::IsValid4AlignedRange(selectRangeStart, selectRangeEnd - selectRangeStart)) {
ERROR_LOG(Log::Debugger, "NopIntructions: Bad address range %08x->%08x", selectRangeStart, selectRangeEnd);
return;
}
for (u32 addr = selectRangeStart; addr < selectRangeEnd; addr += 4) {
Memory::WriteUnchecked_U32(0, addr);
}
if (currentMIPS) {
currentMIPS->InvalidateICacheRangeDeferred(selectRangeStart, selectRangeEnd - selectRangeStart);
}
});
}
void CtrlDisAsmView::onMouseUp(WPARAM wParam, LPARAM lParam, int button)
{
if (Achievements::HardcoreModeActive())
return;
if (button == 1)
{
int y = HIWORD(lParam);
setCurAddress(yToAddress(y), KeyDownAsync(VK_SHIFT));
redraw();
}
else if (button == 2)
{
// We don't want to let the users play with deallocated or uninitialized debugging objects
GlobalUIState state = GetUIState();
if (state != UISTATE_INGAME && state != UISTATE_PAUSEMENU) {
return;
}
switch (TriggerContextMenu(ContextMenuID::DISASM, wnd, ContextPoint::FromEvent(lParam)))
{
case ID_DISASM_GOTOINMEMORYVIEW:
SendMessage(GetParent(wnd),WM_DEB_GOTOHEXEDIT,curAddress,0);
break;
case ID_DISASM_TOGGLEBREAKPOINT:
toggleBreakpoint();
redraw();
break;
case ID_DISASM_ASSEMBLE:
assembleOpcode(curAddress,"");
break;
case ID_DISASM_COPYINSTRUCTIONDISASM:
CopyInstructions(selectRangeStart, selectRangeEnd, CopyInstructionsMode::DISASM);
break;
case ID_DISASM_COPYADDRESS:
CopyInstructions(selectRangeStart, selectRangeEnd, CopyInstructionsMode::ADDRESSES);
break;
case ID_DISASM_COPYINSTRUCTIONHEX:
CopyInstructions(selectRangeStart, selectRangeEnd, CopyInstructionsMode::OPCODES);
break;
case ID_DISASM_COPYFUNCTIONHASH:
CopyFunctionHash(curAddress);
break;
case ID_DISASM_NOPINSTRUCTION:
NopInstructions(selectRangeStart, selectRangeEnd);
redraw();
break;
case ID_DISASM_EDITSYMBOLS:
{
// esw.exec() is a modal dialog - must stay outside any queued callback, or we'd
// block the CPU thread on user input. Only the resulting mutation is routed to the
// CPU thread - see Core_RunOnCPUThread() in Core.h.
EditSymbolsWindow esw(wnd, debugger);
if (esw.exec()) {
Core_RunOnCPUThread([&] { esw.eval(); });
SendMessage(GetParent(wnd), WM_DEB_MAPLOADED, 0, 0);
redraw();
}
}
break;
case ID_DISASM_SETPCTOHERE:
{
u32 newPC = curAddress;
Core_RunOnCPUThread([&] { debugger->SetPC(newPC); });
}
redraw();
break;
case ID_DISASM_FOLLOWBRANCH:
followBranch();
break;
case ID_DISASM_RUNTOHERE:
{
SendMessage(GetParent(wnd), WM_COMMAND, ID_DEBUG_RUNTOLINE, 0);
redraw();
}
break;
case ID_DISASM_RENAMEFUNCTION:
{
// Route the symbol map reads/mutations to the CPU thread instead of poking at it
// directly from this GUI thread - see Core_RunOnCPUThread() in Core.h.
// InputBox_GetString() is modal, so it must stay outside any queued callback, or
// we'd block the CPU thread on user input.
u32 funcBegin = -1;
char name[256] = {0};
Core_RunOnCPUThread([&] {
funcBegin = g_symbolMap->GetFunctionStart(curAddress);
if (funcBegin != -1)
truncate_cpy(name, g_symbolMap->GetLabelString(funcBegin));
});
if (funcBegin != -1)
{
std::string newname;
if (InputBox_GetString(MainWindow::GetHInstance(), MainWindow::GetHWND(), L"New function name", name, newname)) {
Core_RunOnCPUThread([&] {
g_symbolMap->SetLabelName(newname.c_str(), funcBegin);
u32 funcSize = g_symbolMap->GetFunctionSize(funcBegin);
MIPSAnalyst::RegisterFunction(funcBegin, funcSize, newname.c_str());
MIPSAnalyst::UpdateHashMap();
MIPSAnalyst::ApplyHashMap();
});
SendMessage(GetParent(wnd),WM_DEB_MAPLOADED,0,0);
redraw();
}
}
else
{
MessageBox(MainWindow::GetHWND(), L"No symbol selected",0,0);
}
}
break;
case ID_DISASM_REMOVEFUNCTION:
{
// Route the symbol map reads/mutations to the CPU thread instead of poking at it
// directly from this GUI thread - see Core_RunOnCPUThread() in Core.h. SendMessage()
// must stay outside the lambda: this GUI thread is blocked waiting for the CPU
// thread while the lambda runs, so a SendMessage() to one of our own windows from
// inside it would deadlock.
bool found = false;
Core_RunOnCPUThread([&] {
u32 funcBegin = g_symbolMap->GetFunctionStart(curAddress);
if (funcBegin != -1)
{
found = true;
u32 prevBegin = g_symbolMap->GetFunctionStart(funcBegin-1);
if (prevBegin != -1)
{
u32 expandedSize = g_symbolMap->GetFunctionSize(prevBegin) + g_symbolMap->GetFunctionSize(funcBegin);
g_symbolMap->SetFunctionSize(prevBegin,expandedSize);
}
g_symbolMap->RemoveFunction(funcBegin,true);
g_symbolMap->SortSymbols();
g_disassemblyManager.clear();
}
});
if (found)
{
SendMessage(GetParent(wnd), WM_DEB_MAPLOADED, 0, 0);
}
else
{
char statusBarTextBuff[256];
snprintf(statusBarTextBuff,256, "WARNING: unable to find function symbol here");
SendMessage(GetParent(wnd), WM_DEB_SETSTATUSBARTEXT, 0, (LPARAM) statusBarTextBuff);
}
redraw();
}
break;
case ID_DISASM_ADDFUNCTION:
{
// Same SendMessage-must-stay-outside-the-lambda reasoning as ID_DISASM_REMOVEFUNCTION
// above.
bool alreadyExists = false;
Core_RunOnCPUThread([&] {
u32 prevBegin = g_symbolMap->GetFunctionStart(curAddress);
if (prevBegin != -1)
{
if (prevBegin == curAddress)
{
alreadyExists = true;
}
else
{
char symname[128];
u32 prevSize = g_symbolMap->GetFunctionSize(prevBegin);
u32 newSize = curAddress-prevBegin;
g_symbolMap->SetFunctionSize(prevBegin,newSize);
newSize = prevSize-newSize;
snprintf(symname,128,"u_un_%08X",curAddress);
g_symbolMap->AddFunction(symname,curAddress,newSize);
g_symbolMap->SortSymbols();
g_disassemblyManager.clear();
}
}
else
{
char symname[128];
int newSize = selectRangeEnd - selectRangeStart;
snprintf(symname, 128, "u_un_%08X", selectRangeStart);
g_symbolMap->AddFunction(symname, selectRangeStart, newSize);
g_symbolMap->SortSymbols();
}
});
if (alreadyExists)
{
char statusBarTextBuff[256];
snprintf(statusBarTextBuff,256, "WARNING: There's already a function entry point at this adress");
SendMessage(GetParent(wnd), WM_DEB_SETSTATUSBARTEXT, 0, (LPARAM) statusBarTextBuff);
}
else
{
SendMessage(GetParent(wnd), WM_DEB_MAPLOADED, 0, 0);
}
redraw();
}
break;
case ID_DISASM_DISASSEMBLETOFILE:
disassembleToFile();
break;
}
return;
}
redraw();
}
void CtrlDisAsmView::onMouseMove(WPARAM wParam, LPARAM lParam, int button)
{
if (Achievements::HardcoreModeActive())
return;
if ((button & 1) != 0)
{
int y = HIWORD(lParam);
setCurAddress(yToAddress(y), KeyDownAsync(VK_SHIFT));
redraw();
}
}
void CtrlDisAsmView::updateStatusBarText()
{
CoreShutdownLock coreLock = Core_LockAgainstShutdown();
if (!PSP_IsInited())
return;
char text[512];
DisassemblyLineInfo line;
g_disassemblyManager.getLine(curAddress,true,line, debugger);
line.ToString(text, sizeof(text), curAddress);
SendMessage(GetParent(wnd),WM_DEB_SETSTATUSBARTEXT,0,(LPARAM)text);
// Empty unless the game shipped an unstripped ELF - see Core/Debugger/LineInfo.h.
const std::string source = g_lineInfo.LookupString(curAddress);
std::string label = g_symbolMap->GetLabelString(line.info.opcodeAddress);
if (!source.empty()) {
label = label.empty() ? source : label + " " + source;
}
if (!label.empty()) {
SendMessage(GetParent(wnd),WM_DEB_SETSTATUSBARTEXT,1,(LPARAM)label.c_str());
}
}
u32 CtrlDisAsmView::yToAddress(int y)
{
int line = y/rowHeight;
return g_disassemblyManager.getNthNextAddress(windowStart,line);
}
void CtrlDisAsmView::calculatePixelPositions()
{
pixelPositions.addressStart = 16;
pixelPositions.opcodeStart = pixelPositions.addressStart + 18*charWidth;
pixelPositions.argumentsStart = pixelPositions.opcodeStart + 9*charWidth;
pixelPositions.arrowsStart = pixelPositions.argumentsStart + 30*charWidth;
}
void CtrlDisAsmView::search(bool continueSearch)
{
CoreShutdownLock coreLock = Core_LockAgainstShutdown();
u32 searchAddress;
if (continueSearch == false || searchQuery[0] == 0)
{
if (InputBox_GetString(MainWindow::GetHInstance(), MainWindow::GetHWND(), L"Search for:", searchQuery, searchQuery) == false
|| searchQuery[0] == 0)
{
SetFocus(wnd);
return;
}
for (size_t i = 0; i < searchQuery.size(); i++)
{
searchQuery[i] = tolower(searchQuery[i]);
}
SetFocus(wnd);
searchAddress = g_disassemblyManager.getNthNextAddress(curAddress,1);
} else {
searchAddress = g_disassemblyManager.getNthNextAddress(matchAddress,1);
}
// limit address to sensible ranges
if (searchAddress < 0x04000000) searchAddress = 0x04000000;
if (searchAddress >= 0x04200000 && searchAddress < 0x08000000) searchAddress = 0x08000000;
if (searchAddress >= 0x0A000000) {
MessageBox(wnd,L"Not found",L"Search",MB_OK);
return;
}
searching = true;
redraw(); // so the cursor is disabled
DisassemblyLineInfo lineInfo;
while (searchAddress < 0x0A000000)
{
g_disassemblyManager.getLine(searchAddress,displaySymbols,lineInfo, debugger);
char addressText[64];
GetDisasmAddressText(searchAddress, addressText, sizeof(addressText), true, lineInfo.type == DISTYPE_OPCODE, displaySymbols);
const char* opcode = lineInfo.name.c_str();
const char* arguments = lineInfo.params.c_str();
char merged[512];
int mergePos = 0;
// I'm doing it manually to convert everything to lowercase at the same time
for (int i = 0; addressText[i] != 0; i++) merged[mergePos++] = tolower(addressText[i]);
merged[mergePos++] = ' ';
for (int i = 0; opcode[i] != 0; i++) merged[mergePos++] = tolower(opcode[i]);
merged[mergePos++] = ' ';
for (int i = 0; arguments[i] != 0; i++) merged[mergePos++] = tolower(arguments[i]);
merged[mergePos] = 0;
// match!
if (strstr(merged, searchQuery.c_str()) != NULL)
{
matchAddress = searchAddress;
searching = false;
gotoAddr(searchAddress);
return;
}
// cancel search
if ((searchAddress % 256) == 0 && KeyDownAsync(VK_ESCAPE))
{
searching = false;
return;
}
searchAddress = g_disassemblyManager.getNthNextAddress(searchAddress,1);
if (searchAddress >= 0x04200000 && searchAddress < 0x08000000) searchAddress = 0x08000000;
}
MessageBox(wnd,L"Not found",L"Search",MB_OK);
searching = false;
}
void CtrlDisAsmView::disassembleToFile() {
// get size
u32 size;
if (executeExpressionWindow(wnd,debugger,size) == false)
return;
if (size == 0 || size > 10*1024*1024) {
MessageBox(wnd,L"Invalid size!",L"Error",MB_OK);
return;
}
std::string filename;
if (W32Util::BrowseForFileName(false, nullptr, L"Save Disassembly As...", nullptr, nullptr, L"All Files\0*.*\0\0", nullptr, filename)) {
std::wstring fileName = ConvertUTF8ToWString(filename);
FILE *output = _wfopen(fileName.c_str(), L"wb");
if (output == nullptr) {
MessageBox(wnd, L"Could not open file!", L"Error", MB_OK);
return;
}
std::string disassembly = DisassembleRange(curAddress, size, displaySymbols, debugger);
fprintf(output, "%s", disassembly.c_str());
fclose(output);
MessageBox(wnd, L"Finished!", L"Done", MB_OK);
}
}
void CtrlDisAsmView::getOpcodeText(u32 address, char* dest, int bufsize)
{
DisassemblyLineInfo line;
address = g_disassemblyManager.getStartAddress(address);
g_disassemblyManager.getLine(address,displaySymbols,line, debugger);
snprintf(dest, bufsize, "%s %s",line.name.c_str(),line.params.c_str());
}
void CtrlDisAsmView::scrollStepping(u32 newPc)
{
u32 windowEnd = g_disassemblyManager.getNthNextAddress(windowStart,visibleRows);
newPc = g_disassemblyManager.getStartAddress(newPc);
if (newPc >= windowEnd || newPc >= g_disassemblyManager.getNthPreviousAddress(windowEnd,1))
{
windowStart = g_disassemblyManager.getNthPreviousAddress(newPc,visibleRows-2);
}
}