Path: blob/master/Core/Debugger/WebSocket/SteppingSubscriber.cpp
5667 views
// Copyright (c) 2018- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include "Common/StringUtils.h"18#include "Core/Debugger/Breakpoints.h"19#include "Core/Debugger/DisassemblyManager.h"20#include "Core/Debugger/WebSocket/SteppingSubscriber.h"21#include "Core/Debugger/WebSocket/WebSocketUtils.h"22#include "Core/Core.h"23#include "Core/HLE/HLE.h"24#include "Core/HLE/sceKernelThread.h"25#include "Core/MIPS/MIPSDebugInterface.h"26#include "Core/MIPS/MIPSStackWalk.h"2728using namespace MIPSAnalyst;2930struct WebSocketSteppingState : public DebuggerSubscriber {31WebSocketSteppingState() {32g_disassemblyManager.setCpu(currentDebugMIPS);33}34~WebSocketSteppingState() {35g_disassemblyManager.clear();36}3738void Into(DebuggerRequest &req);39void Over(DebuggerRequest &req);40void Out(DebuggerRequest &req);41void RunUntil(DebuggerRequest &req);42void HLE(DebuggerRequest &req);4344protected:45uint32_t GetNextAddress(DebugInterface *cpuDebug);46int GetNextInstructionCount(DebugInterface *cpuDebug);47void PrepareResume();48void AddThreadCondition(uint32_t breakpointAddress, uint32_t threadID);49};5051DebuggerSubscriber *WebSocketSteppingInit(DebuggerEventHandlerMap &map) {52auto p = new WebSocketSteppingState();53map["cpu.stepInto"] = [p](DebuggerRequest &req) { p->Into(req); };54map["cpu.stepOver"] = [p](DebuggerRequest &req) { p->Over(req); };55map["cpu.stepOut"] = [p](DebuggerRequest &req) { p->Out(req); };56map["cpu.runUntil"] = [p](DebuggerRequest &req) { p->RunUntil(req); };57map["cpu.nextHLE"] = [p](DebuggerRequest &req) { p->HLE(req); };58return p;59}6061static DebugInterface *CPUFromRequest(DebuggerRequest &req, uint32_t *threadID = nullptr) {62if (!req.HasParam("thread")) {63if (threadID)64*threadID = -1;65return currentDebugMIPS;66}6768uint32_t uid;69if (!req.ParamU32("thread", &uid))70return nullptr;7172DebugInterface *cpuDebug = KernelDebugThread((SceUID)uid);73if (!cpuDebug)74req.Fail("Thread could not be found");75if (threadID)76*threadID = uid;77return cpuDebug;78}7980// Single step into the next instruction (cpu.stepInto)81//82// Parameters:83// - thread: optional number indicating the thread id to plan stepping on.84//85// No immediate response. A cpu.stepping event will be sent once complete.86//87// Note: any thread can wake the cpu when it hits the next instruction currently.88void WebSocketSteppingState::Into(DebuggerRequest &req) {89if (!currentDebugMIPS->isAlive())90return req.Fail("CPU not started");91if (!Core_IsStepping()) {92Core_Break(BreakReason::DebugStepInto, 0);93return;94}9596uint32_t threadID;97auto cpuDebug = CPUFromRequest(req, &threadID);98if (!cpuDebug)99return;100101if (cpuDebug == currentDebugMIPS) {102// If the current PC is on a breakpoint, the user doesn't want to do nothing.103g_breakpoints.SetSkipFirst(currentMIPS->pc);104105int c = GetNextInstructionCount(cpuDebug);106Core_RequestCPUStep(CPUStepType::Into, c);107} else {108uint32_t breakpointAddress = cpuDebug->GetPC();109PrepareResume();110// Could have advanced to the breakpoint already in PrepareResume().111// Note: we need to get cpuDebug again anyway (in case we ran some HLE above.)112cpuDebug = CPUFromRequest(req);113if (cpuDebug != currentDebugMIPS) {114g_breakpoints.AddBreakPoint(breakpointAddress, true);115AddThreadCondition(breakpointAddress, threadID);116Core_Resume();117}118}119}120121// Step over the next instruction (cpu.stepOver)122//123// Note: this jumps over function calls, but also delay slots.124//125// Parameters:126// - thread: optional number indicating the thread id to plan stepping on.127//128// No immediate response. A cpu.stepping event will be sent once complete.129//130// Note: any thread can wake the cpu when it hits the next instruction currently.131void WebSocketSteppingState::Over(DebuggerRequest &req) {132if (!currentDebugMIPS->isAlive())133return req.Fail("CPU not started");134if (!Core_IsStepping())135return req.Fail("CPU currently running (cpu.stepping first)");136137uint32_t threadID;138auto cpuDebug = CPUFromRequest(req, &threadID);139if (!cpuDebug)140return;141142MipsOpcodeInfo info = GetOpcodeInfo(cpuDebug, cpuDebug->GetPC());143uint32_t breakpointAddress = GetNextAddress(cpuDebug);144if (info.isBranch) {145if (info.isConditional && !info.isLinkedBranch) {146if (info.conditionMet) {147breakpointAddress = info.branchTarget;148} else {149// Skip over the delay slot.150breakpointAddress += 4;151}152} else {153if (info.isLinkedBranch) {154// jal or jalr - a function call. Skip the delay slot.155breakpointAddress += 4;156} else {157// j - for absolute branches, set the breakpoint at the branch target.158breakpointAddress = info.branchTarget;159}160}161}162163PrepareResume();164// Could have advanced to the breakpoint already in PrepareResume().165cpuDebug = CPUFromRequest(req);166if (cpuDebug->GetPC() != breakpointAddress) {167g_breakpoints.AddBreakPoint(breakpointAddress, true);168if (cpuDebug != currentDebugMIPS)169AddThreadCondition(breakpointAddress, threadID);170Core_Resume();171}172}173174// Step out of a function based on a stack walk (cpu.stepOut)175//176// Parameters:177// - thread: optional number indicating the thread id to plan stepping on.178//179// No immediate response. A cpu.stepping event will be sent once complete.180//181// Note: any thread can wake the cpu when it hits the next instruction currently.182void WebSocketSteppingState::Out(DebuggerRequest &req) {183if (!currentDebugMIPS->isAlive())184return req.Fail("CPU not started");185if (!Core_IsStepping())186return req.Fail("CPU currently running (cpu.stepping first)");187188uint32_t threadID;189auto cpuDebug = CPUFromRequest(req, &threadID);190if (!cpuDebug)191return;192193auto threads = GetThreadsInfo();194uint32_t entry = cpuDebug->GetPC();195uint32_t stackTop = 0;196for (const DebugThreadInfo &th : threads) {197if ((threadID == -1 && th.isCurrent) || th.id == threadID) {198entry = th.entrypoint;199stackTop = th.initialStack;200break;201}202}203204uint32_t ra = cpuDebug->GetRegValue(0, MIPS_REG_RA);205uint32_t sp = cpuDebug->GetRegValue(0, MIPS_REG_SP);206auto frames = MIPSStackWalk::Walk(cpuDebug->GetPC(), ra, sp, entry, stackTop);207if (frames.size() < 2) {208return req.Fail("Could not find function call to step out into");209}210211uint32_t breakpointAddress = frames[1].pc;212PrepareResume();213// Could have advanced to the breakpoint already in PrepareResume().214cpuDebug = CPUFromRequest(req);215if (cpuDebug->GetPC() != breakpointAddress) {216g_breakpoints.AddBreakPoint(breakpointAddress, true);217if (cpuDebug != currentDebugMIPS)218AddThreadCondition(breakpointAddress, threadID);219Core_Resume();220}221}222223// Run until a certain address (cpu.runUntil)224//225// Parameters:226// - address: number parameter for destination.227//228// No immediate response. A cpu.stepping event will be sent once complete.229void WebSocketSteppingState::RunUntil(DebuggerRequest &req) {230if (!currentDebugMIPS->isAlive()) {231return req.Fail("CPU not started");232}233234uint32_t address = 0;235if (!req.ParamU32("address", &address)) {236// Error already sent.237return;238}239240bool wasAtAddress = currentMIPS->pc == address;241PrepareResume();242// We may have arrived already if PauseResume() stepped out of a delay slot.243if (currentMIPS->pc != address || wasAtAddress) {244g_breakpoints.AddBreakPoint(address, true);245Core_Resume();246}247}248249// Jump after the next HLE call (cpu.nextHLE)250//251// No parameters.252//253// No immediate response. A cpu.stepping event will be sent once complete.254void WebSocketSteppingState::HLE(DebuggerRequest &req) {255if (!currentDebugMIPS->isAlive()) {256return req.Fail("CPU not started");257}258259PrepareResume();260hleDebugBreak();261Core_Resume();262}263264uint32_t WebSocketSteppingState::GetNextAddress(DebugInterface *cpuDebug) {265uint32_t current = g_disassemblyManager.getStartAddress(cpuDebug->GetPC());266return g_disassemblyManager.getNthNextAddress(current, 1);267}268269int WebSocketSteppingState::GetNextInstructionCount(DebugInterface *cpuDebug) {270return (GetNextAddress(cpuDebug) - cpuDebug->GetPC()) / 4;271}272273void WebSocketSteppingState::PrepareResume() {274if (currentMIPS->inDelaySlot) {275// Delay slot instructions are never joined, so we pass 1.276Core_RequestCPUStep(CPUStepType::Into, 1);277} else {278// If the current PC is on a breakpoint, the user doesn't want to do nothing.279g_breakpoints.SetSkipFirst(currentMIPS->pc);280}281}282283void WebSocketSteppingState::AddThreadCondition(uint32_t breakpointAddress, uint32_t threadID) {284BreakPointCond cond;285cond.debug = currentDebugMIPS;286cond.expressionString = StringFromFormat("threadid == 0x%08x", threadID);287if (initExpression(currentDebugMIPS, cond.expressionString.c_str(), cond.expression))288g_breakpoints.ChangeBreakPointAddCond(breakpointAddress, cond);289}290291292