Path: blob/master/Core/Debugger/WebSocket/SteppingSubscriber.cpp
3187 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"] = std::bind(&WebSocketSteppingState::Into, p, std::placeholders::_1);54map["cpu.stepOver"] = std::bind(&WebSocketSteppingState::Over, p, std::placeholders::_1);55map["cpu.stepOut"] = std::bind(&WebSocketSteppingState::Out, p, std::placeholders::_1);56map["cpu.runUntil"] = std::bind(&WebSocketSteppingState::RunUntil, p, std::placeholders::_1);57map["cpu.nextHLE"] = std::bind(&WebSocketSteppingState::HLE, p, std::placeholders::_1);5859return p;60}6162static DebugInterface *CPUFromRequest(DebuggerRequest &req, uint32_t *threadID = nullptr) {63if (!req.HasParam("thread")) {64if (threadID)65*threadID = -1;66return currentDebugMIPS;67}6869uint32_t uid;70if (!req.ParamU32("thread", &uid))71return nullptr;7273DebugInterface *cpuDebug = KernelDebugThread((SceUID)uid);74if (!cpuDebug)75req.Fail("Thread could not be found");76if (threadID)77*threadID = uid;78return cpuDebug;79}8081// Single step into the next instruction (cpu.stepInto)82//83// Parameters:84// - thread: optional number indicating the thread id to plan stepping on.85//86// No immediate response. A cpu.stepping event will be sent once complete.87//88// Note: any thread can wake the cpu when it hits the next instruction currently.89void WebSocketSteppingState::Into(DebuggerRequest &req) {90if (!currentDebugMIPS->isAlive())91return req.Fail("CPU not started");92if (!Core_IsStepping()) {93Core_Break(BreakReason::DebugStepInto, 0);94return;95}9697uint32_t threadID;98auto cpuDebug = CPUFromRequest(req, &threadID);99if (!cpuDebug)100return;101102if (cpuDebug == currentDebugMIPS) {103// If the current PC is on a breakpoint, the user doesn't want to do nothing.104g_breakpoints.SetSkipFirst(currentMIPS->pc);105106int c = GetNextInstructionCount(cpuDebug);107Core_RequestCPUStep(CPUStepType::Into, c);108} else {109uint32_t breakpointAddress = cpuDebug->GetPC();110PrepareResume();111// Could have advanced to the breakpoint already in PrepareResume().112// Note: we need to get cpuDebug again anyway (in case we ran some HLE above.)113cpuDebug = CPUFromRequest(req);114if (cpuDebug != currentDebugMIPS) {115g_breakpoints.AddBreakPoint(breakpointAddress, true);116AddThreadCondition(breakpointAddress, threadID);117Core_Resume();118}119}120}121122// Step over the next instruction (cpu.stepOver)123//124// Note: this jumps over function calls, but also delay slots.125//126// Parameters:127// - thread: optional number indicating the thread id to plan stepping on.128//129// No immediate response. A cpu.stepping event will be sent once complete.130//131// Note: any thread can wake the cpu when it hits the next instruction currently.132void WebSocketSteppingState::Over(DebuggerRequest &req) {133if (!currentDebugMIPS->isAlive())134return req.Fail("CPU not started");135if (!Core_IsStepping())136return req.Fail("CPU currently running (cpu.stepping first)");137138uint32_t threadID;139auto cpuDebug = CPUFromRequest(req, &threadID);140if (!cpuDebug)141return;142143MipsOpcodeInfo info = GetOpcodeInfo(cpuDebug, cpuDebug->GetPC());144uint32_t breakpointAddress = GetNextAddress(cpuDebug);145if (info.isBranch) {146if (info.isConditional && !info.isLinkedBranch) {147if (info.conditionMet) {148breakpointAddress = info.branchTarget;149} else {150// Skip over the delay slot.151breakpointAddress += 4;152}153} else {154if (info.isLinkedBranch) {155// jal or jalr - a function call. Skip the delay slot.156breakpointAddress += 4;157} else {158// j - for absolute branches, set the breakpoint at the branch target.159breakpointAddress = info.branchTarget;160}161}162}163164PrepareResume();165// Could have advanced to the breakpoint already in PrepareResume().166cpuDebug = CPUFromRequest(req);167if (cpuDebug->GetPC() != breakpointAddress) {168g_breakpoints.AddBreakPoint(breakpointAddress, true);169if (cpuDebug != currentDebugMIPS)170AddThreadCondition(breakpointAddress, threadID);171Core_Resume();172}173}174175// Step out of a function based on a stack walk (cpu.stepOut)176//177// Parameters:178// - thread: optional number indicating the thread id to plan stepping on.179//180// No immediate response. A cpu.stepping event will be sent once complete.181//182// Note: any thread can wake the cpu when it hits the next instruction currently.183void WebSocketSteppingState::Out(DebuggerRequest &req) {184if (!currentDebugMIPS->isAlive())185return req.Fail("CPU not started");186if (!Core_IsStepping())187return req.Fail("CPU currently running (cpu.stepping first)");188189uint32_t threadID;190auto cpuDebug = CPUFromRequest(req, &threadID);191if (!cpuDebug)192return;193194auto threads = GetThreadsInfo();195uint32_t entry = cpuDebug->GetPC();196uint32_t stackTop = 0;197for (const DebugThreadInfo &th : threads) {198if ((threadID == -1 && th.isCurrent) || th.id == threadID) {199entry = th.entrypoint;200stackTop = th.initialStack;201break;202}203}204205uint32_t ra = cpuDebug->GetRegValue(0, MIPS_REG_RA);206uint32_t sp = cpuDebug->GetRegValue(0, MIPS_REG_SP);207auto frames = MIPSStackWalk::Walk(cpuDebug->GetPC(), ra, sp, entry, stackTop);208if (frames.size() < 2) {209return req.Fail("Could not find function call to step out into");210}211212uint32_t breakpointAddress = frames[1].pc;213PrepareResume();214// Could have advanced to the breakpoint already in PrepareResume().215cpuDebug = CPUFromRequest(req);216if (cpuDebug->GetPC() != breakpointAddress) {217g_breakpoints.AddBreakPoint(breakpointAddress, true);218if (cpuDebug != currentDebugMIPS)219AddThreadCondition(breakpointAddress, threadID);220Core_Resume();221}222}223224// Run until a certain address (cpu.runUntil)225//226// Parameters:227// - address: number parameter for destination.228//229// No immediate response. A cpu.stepping event will be sent once complete.230void WebSocketSteppingState::RunUntil(DebuggerRequest &req) {231if (!currentDebugMIPS->isAlive()) {232return req.Fail("CPU not started");233}234235uint32_t address = 0;236if (!req.ParamU32("address", &address)) {237// Error already sent.238return;239}240241bool wasAtAddress = currentMIPS->pc == address;242PrepareResume();243// We may have arrived already if PauseResume() stepped out of a delay slot.244if (currentMIPS->pc != address || wasAtAddress) {245g_breakpoints.AddBreakPoint(address, true);246Core_Resume();247}248}249250// Jump after the next HLE call (cpu.nextHLE)251//252// No parameters.253//254// No immediate response. A cpu.stepping event will be sent once complete.255void WebSocketSteppingState::HLE(DebuggerRequest &req) {256if (!currentDebugMIPS->isAlive()) {257return req.Fail("CPU not started");258}259260PrepareResume();261hleDebugBreak();262Core_Resume();263}264265uint32_t WebSocketSteppingState::GetNextAddress(DebugInterface *cpuDebug) {266uint32_t current = g_disassemblyManager.getStartAddress(cpuDebug->GetPC());267return g_disassemblyManager.getNthNextAddress(current, 1);268}269270int WebSocketSteppingState::GetNextInstructionCount(DebugInterface *cpuDebug) {271return (GetNextAddress(cpuDebug) - cpuDebug->GetPC()) / 4;272}273274void WebSocketSteppingState::PrepareResume() {275if (currentMIPS->inDelaySlot) {276// Delay slot instructions are never joined, so we pass 1.277Core_RequestCPUStep(CPUStepType::Into, 1);278} else {279// If the current PC is on a breakpoint, the user doesn't want to do nothing.280g_breakpoints.SetSkipFirst(currentMIPS->pc);281}282}283284void WebSocketSteppingState::AddThreadCondition(uint32_t breakpointAddress, uint32_t threadID) {285BreakPointCond cond;286cond.debug = currentDebugMIPS;287cond.expressionString = StringFromFormat("threadid == 0x%08x", threadID);288if (initExpression(currentDebugMIPS, cond.expressionString.c_str(), cond.expression))289g_breakpoints.ChangeBreakPointAddCond(breakpointAddress, cond);290}291292293