mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-09-01 10:15:21 +02:00
Reported hang: the CPU thread held g_frameMutex (NativeFrame) and blocked on g_shutdownLock inside a queued memory.read, while the GUI thread held g_shutdownLock (CtrlMemView::onPaint) and blocked on g_frameMutex. Textbook ABBA. The CPU thread's order is structural - NativeFrame wraps everything below it in g_frameMutex, and both Core_ProcessCPUQueue() and runImDebugger() -> DisassembleRange() lock memory from under there - so the GUI side is the one that has to match. Swaps the three handlers that had it backwards (CtrlMemView::onPaint, CtrlDisAsmView::onPaint, CtrlStackTraceView:: loadStackTrace) to take g_frameMutex first. They already took both locks, so this is ordering only, and g_shutdownLock is recursive so nesting is fine. Also drops the Memory::MemoryInitedLock from the WebSocket LockMemory(), which is what made the CPU thread want that lock in the first place. It was guarding against another thread tearing down the memory system, but that doesn't happen: Memory::Shutdown() is only reached via CPU_Shutdown() <- PSP_Shutdown(), whose callers all run on the CPU thread, and Memory::Reinit() runs from Memory::DoState() on savestate load, likewise. WebSocket.cpp additionally holds lifecycleLock across the whole handler and takes it on STOPPING. Note this second part isn't sufficient on its own - ImMemView's copy-disassembly path also locks memory from inside the frame span - which is why the ordering fix is the real one. Not removing Memory::Lock() from the Win32 paint handlers: teardown isn't fully inside the g_frameMutex span yet. EmuScreen::render()'s PSP_Shutdown() is, but the ones in EmuScreen::sendMessage() (game reset, loading a new game) run from g_screenManager->sendMessage(), above where NativeFrame takes the guard. Closing that is the prerequisite, and is left for later. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
1357 lines
38 KiB
C++
1357 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/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. See
|
|
// g_frameMutex in Core.h.
|
|
std::lock_guard<std::mutex> frameGuard(g_frameMutex);
|
|
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)
|
|
{
|
|
Memory::MemoryInitedLock memLock = Memory::Lock();
|
|
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> ¤tArguments) {
|
|
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 Memory::Lock() - never the other way around. See CtrlMemView::onPaint.
|
|
std::lock_guard<std::mutex> frameGuard(g_frameMutex);
|
|
Memory::MemoryInitedLock memLock = Memory::Lock();
|
|
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()
|
|
{
|
|
auto memLock = Memory::Lock();
|
|
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);
|
|
|
|
const std::string label = g_symbolMap->GetLabelString(line.info.opcodeAddress);
|
|
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)
|
|
{
|
|
auto memLock = Memory::Lock();
|
|
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);
|
|
}
|
|
}
|
|
|