CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Core/Debugger/WebSocket/SteppingSubscriber.cpp
Views: 1401
// 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() {32disasm_.setCpu(currentDebugMIPS);33}34~WebSocketSteppingState() {35disasm_.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);4950DisassemblyManager disasm_;51};5253DebuggerSubscriber *WebSocketSteppingInit(DebuggerEventHandlerMap &map) {54auto p = new WebSocketSteppingState();55map["cpu.stepInto"] = std::bind(&WebSocketSteppingState::Into, p, std::placeholders::_1);56map["cpu.stepOver"] = std::bind(&WebSocketSteppingState::Over, p, std::placeholders::_1);57map["cpu.stepOut"] = std::bind(&WebSocketSteppingState::Out, p, std::placeholders::_1);58map["cpu.runUntil"] = std::bind(&WebSocketSteppingState::RunUntil, p, std::placeholders::_1);59map["cpu.nextHLE"] = std::bind(&WebSocketSteppingState::HLE, p, std::placeholders::_1);6061return p;62}6364static DebugInterface *CPUFromRequest(DebuggerRequest &req, uint32_t *threadID = nullptr) {65if (!req.HasParam("thread")) {66if (threadID)67*threadID = -1;68return currentDebugMIPS;69}7071uint32_t uid;72if (!req.ParamU32("thread", &uid))73return nullptr;7475DebugInterface *cpuDebug = KernelDebugThread((SceUID)uid);76if (!cpuDebug)77req.Fail("Thread could not be found");78if (threadID)79*threadID = uid;80return cpuDebug;81}8283// Single step into the next instruction (cpu.stepInto)84//85// Parameters:86// - thread: optional number indicating the thread id to plan stepping on.87//88// No immediate response. A cpu.stepping event will be sent once complete.89//90// Note: any thread can wake the cpu when it hits the next instruction currently.91void WebSocketSteppingState::Into(DebuggerRequest &req) {92if (!currentDebugMIPS->isAlive())93return req.Fail("CPU not started");94if (!Core_IsStepping()) {95Core_EnableStepping(true, "cpu.stepInto", 0);96return;97}9899uint32_t threadID;100auto cpuDebug = CPUFromRequest(req, &threadID);101if (!cpuDebug)102return;103104if (cpuDebug == currentDebugMIPS) {105// If the current PC is on a breakpoint, the user doesn't want to do nothing.106CBreakPoints::SetSkipFirst(currentMIPS->pc);107108int c = GetNextInstructionCount(cpuDebug);109for (int i = 0; i < c; ++i) {110Core_DoSingleStep();111}112} else {113uint32_t breakpointAddress = cpuDebug->GetPC();114PrepareResume();115// Could have advanced to the breakpoint already in PrepareResume().116// Note: we need to get cpuDebug again anyway (in case we ran some HLE above.)117cpuDebug = CPUFromRequest(req);118if (cpuDebug != currentDebugMIPS) {119CBreakPoints::AddBreakPoint(breakpointAddress, true);120AddThreadCondition(breakpointAddress, threadID);121Core_EnableStepping(false);122}123}124}125126// Step over the next instruction (cpu.stepOver)127//128// Note: this jumps over function calls, but also delay slots.129//130// Parameters:131// - thread: optional number indicating the thread id to plan stepping on.132//133// No immediate response. A cpu.stepping event will be sent once complete.134//135// Note: any thread can wake the cpu when it hits the next instruction currently.136void WebSocketSteppingState::Over(DebuggerRequest &req) {137if (!currentDebugMIPS->isAlive())138return req.Fail("CPU not started");139if (!Core_IsStepping())140return req.Fail("CPU currently running (cpu.stepping first)");141142uint32_t threadID;143auto cpuDebug = CPUFromRequest(req, &threadID);144if (!cpuDebug)145return;146147MipsOpcodeInfo info = GetOpcodeInfo(cpuDebug, cpuDebug->GetPC());148uint32_t breakpointAddress = GetNextAddress(cpuDebug);149if (info.isBranch) {150if (info.isConditional && !info.isLinkedBranch) {151if (info.conditionMet) {152breakpointAddress = info.branchTarget;153} else {154// Skip over the delay slot.155breakpointAddress += 4;156}157} else {158if (info.isLinkedBranch) {159// jal or jalr - a function call. Skip the delay slot.160breakpointAddress += 4;161} else {162// j - for absolute branches, set the breakpoint at the branch target.163breakpointAddress = info.branchTarget;164}165}166}167168PrepareResume();169// Could have advanced to the breakpoint already in PrepareResume().170cpuDebug = CPUFromRequest(req);171if (cpuDebug->GetPC() != breakpointAddress) {172CBreakPoints::AddBreakPoint(breakpointAddress, true);173if (cpuDebug != currentDebugMIPS)174AddThreadCondition(breakpointAddress, threadID);175Core_EnableStepping(false);176}177}178179// Step out of a function based on a stack walk (cpu.stepOut)180//181// Parameters:182// - thread: optional number indicating the thread id to plan stepping on.183//184// No immediate response. A cpu.stepping event will be sent once complete.185//186// Note: any thread can wake the cpu when it hits the next instruction currently.187void WebSocketSteppingState::Out(DebuggerRequest &req) {188if (!currentDebugMIPS->isAlive())189return req.Fail("CPU not started");190if (!Core_IsStepping())191return req.Fail("CPU currently running (cpu.stepping first)");192193uint32_t threadID;194auto cpuDebug = CPUFromRequest(req, &threadID);195if (!cpuDebug)196return;197198auto threads = GetThreadsInfo();199uint32_t entry = cpuDebug->GetPC();200uint32_t stackTop = 0;201for (const DebugThreadInfo &th : threads) {202if ((threadID == -1 && th.isCurrent) || th.id == threadID) {203entry = th.entrypoint;204stackTop = th.initialStack;205break;206}207}208209uint32_t ra = cpuDebug->GetRegValue(0, MIPS_REG_RA);210uint32_t sp = cpuDebug->GetRegValue(0, MIPS_REG_SP);211auto frames = MIPSStackWalk::Walk(cpuDebug->GetPC(), ra, sp, entry, stackTop);212if (frames.size() < 2) {213return req.Fail("Could not find function call to step out into");214}215216uint32_t breakpointAddress = frames[1].pc;217PrepareResume();218// Could have advanced to the breakpoint already in PrepareResume().219cpuDebug = CPUFromRequest(req);220if (cpuDebug->GetPC() != breakpointAddress) {221CBreakPoints::AddBreakPoint(breakpointAddress, true);222if (cpuDebug != currentDebugMIPS)223AddThreadCondition(breakpointAddress, threadID);224Core_EnableStepping(false);225}226}227228// Run until a certain address (cpu.runUntil)229//230// Parameters:231// - address: number parameter for destination.232//233// No immediate response. A cpu.stepping event will be sent once complete.234void WebSocketSteppingState::RunUntil(DebuggerRequest &req) {235if (!currentDebugMIPS->isAlive()) {236return req.Fail("CPU not started");237}238239uint32_t address = 0;240if (!req.ParamU32("address", &address)) {241// Error already sent.242return;243}244245bool wasAtAddress = currentMIPS->pc == address;246PrepareResume();247// We may have arrived already if PauseResume() stepped out of a delay slot.248if (currentMIPS->pc != address || wasAtAddress) {249CBreakPoints::AddBreakPoint(address, true);250Core_EnableStepping(false);251}252}253254// Jump after the next HLE call (cpu.nextHLE)255//256// No parameters.257//258// No immediate response. A cpu.stepping event will be sent once complete.259void WebSocketSteppingState::HLE(DebuggerRequest &req) {260if (!currentDebugMIPS->isAlive()) {261return req.Fail("CPU not started");262}263264PrepareResume();265hleDebugBreak();266Core_EnableStepping(false);267}268269uint32_t WebSocketSteppingState::GetNextAddress(DebugInterface *cpuDebug) {270uint32_t current = disasm_.getStartAddress(cpuDebug->GetPC());271return disasm_.getNthNextAddress(current, 1);272}273274int WebSocketSteppingState::GetNextInstructionCount(DebugInterface *cpuDebug) {275return (GetNextAddress(cpuDebug) - cpuDebug->GetPC()) / 4;276}277278void WebSocketSteppingState::PrepareResume() {279if (currentMIPS->inDelaySlot) {280Core_DoSingleStep();281} else {282// If the current PC is on a breakpoint, the user doesn't want to do nothing.283CBreakPoints::SetSkipFirst(currentMIPS->pc);284}285}286287void WebSocketSteppingState::AddThreadCondition(uint32_t breakpointAddress, uint32_t threadID) {288BreakPointCond cond;289cond.debug = currentDebugMIPS;290cond.expressionString = StringFromFormat("threadid == 0x%08x", threadID);291if (currentDebugMIPS->initExpression(cond.expressionString.c_str(), cond.expression))292CBreakPoints::ChangeBreakPointAddCond(breakpointAddress, cond);293}294295296