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/Breakpoints.cpp
Views: 1401
// Copyright (c) 2012- 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 <cstdio>18#include <atomic>19#include <mutex>2021#include "Common/System/System.h"22#include "Common/Log.h"23#include "Core/Core.h"24#include "Core/Debugger/Breakpoints.h"25#include "Core/Debugger/MemBlockInfo.h"26#include "Core/Debugger/SymbolMap.h"27#include "Core/MemMap.h"28#include "Core/MIPS/MIPSAnalyst.h"29#include "Core/MIPS/MIPSDebugInterface.h"30#include "Core/MIPS/JitCommon/JitCommon.h"31#include "Core/CoreTiming.h"3233std::atomic<bool> anyBreakPoints_(false);34std::atomic<bool> anyMemChecks_(false);3536static std::mutex breakPointsMutex_;37std::vector<BreakPoint> CBreakPoints::breakPoints_;38u32 CBreakPoints::breakSkipFirstAt_ = 0;39u64 CBreakPoints::breakSkipFirstTicks_ = 0;40static std::mutex memCheckMutex_;41std::vector<MemCheck> CBreakPoints::memChecks_;42std::vector<MemCheck> CBreakPoints::memCheckRangesRead_;43std::vector<MemCheck> CBreakPoints::memCheckRangesWrite_;4445void MemCheck::Log(u32 addr, bool write, int size, u32 pc, const char *reason) {46if (result & BREAK_ACTION_LOG) {47const char *type = write ? "Write" : "Read";48if (logFormat.empty()) {49NOTICE_LOG(Log::MemMap, "CHK %s%i(%s) at %08x (%s), PC=%08x (%s)", type, size * 8, reason, addr, g_symbolMap->GetDescription(addr).c_str(), pc, g_symbolMap->GetDescription(pc).c_str());50} else {51std::string formatted;52CBreakPoints::EvaluateLogFormat(currentDebugMIPS, logFormat, formatted);53NOTICE_LOG(Log::MemMap, "CHK %s%i(%s) at %08x: %s", type, size * 8, reason, addr, formatted.c_str());54}55}56}5758BreakAction MemCheck::Apply(u32 addr, bool write, int size, u32 pc) {59int mask = write ? MEMCHECK_WRITE : MEMCHECK_READ;60if (cond & mask) {61if (hasCondition) {62if (!condition.Evaluate())63return BREAK_ACTION_IGNORE;64}6566++numHits;67return result;68}6970return BREAK_ACTION_IGNORE;71}7273BreakAction MemCheck::Action(u32 addr, bool write, int size, u32 pc, const char *reason) {74// Conditions have always already been checked if we get here.75Log(addr, write, size, pc, reason);76if ((result & BREAK_ACTION_PAUSE) && coreState != CORE_POWERUP) {77Core_EnableStepping(true, "memory.breakpoint", start);78}7980return result;81}8283// Note: must lock while calling this.84size_t CBreakPoints::FindBreakpoint(u32 addr, bool matchTemp, bool temp)85{86size_t found = INVALID_BREAKPOINT;87for (size_t i = 0; i < breakPoints_.size(); ++i)88{89const auto &bp = breakPoints_[i];90if (bp.addr == addr && (!matchTemp || bp.temporary == temp))91{92if (bp.IsEnabled())93return i;94// Hold out until the first enabled one.95if (found == INVALID_BREAKPOINT)96found = i;97}98}99100return found;101}102103size_t CBreakPoints::FindMemCheck(u32 start, u32 end)104{105for (size_t i = 0; i < memChecks_.size(); ++i)106{107if (memChecks_[i].start == start && memChecks_[i].end == end)108return i;109}110111return INVALID_MEMCHECK;112}113114bool CBreakPoints::IsAddressBreakPoint(u32 addr)115{116if (!anyBreakPoints_)117return false;118std::lock_guard<std::mutex> guard(breakPointsMutex_);119size_t bp = FindBreakpoint(addr);120return bp != INVALID_BREAKPOINT && breakPoints_[bp].result != BREAK_ACTION_IGNORE;121}122123bool CBreakPoints::IsAddressBreakPoint(u32 addr, bool* enabled)124{125if (!anyBreakPoints_)126return false;127std::lock_guard<std::mutex> guard(breakPointsMutex_);128size_t bp = FindBreakpoint(addr);129if (bp == INVALID_BREAKPOINT) return false;130if (enabled != nullptr)131*enabled = breakPoints_[bp].IsEnabled();132return true;133}134135bool CBreakPoints::IsTempBreakPoint(u32 addr)136{137std::lock_guard<std::mutex> guard(breakPointsMutex_);138size_t bp = FindBreakpoint(addr, true, true);139return bp != INVALID_BREAKPOINT;140}141142bool CBreakPoints::RangeContainsBreakPoint(u32 addr, u32 size)143{144if (!anyBreakPoints_)145return false;146std::lock_guard<std::mutex> guard(breakPointsMutex_);147const u32 end = addr + size;148for (const auto &bp : breakPoints_)149{150if (bp.addr >= addr && bp.addr < end)151return true;152}153154return false;155}156157void CBreakPoints::AddBreakPoint(u32 addr, bool temp)158{159std::unique_lock<std::mutex> guard(breakPointsMutex_);160size_t bp = FindBreakpoint(addr, true, temp);161if (bp == INVALID_BREAKPOINT)162{163BreakPoint pt;164pt.result |= BREAK_ACTION_PAUSE;165pt.temporary = temp;166pt.addr = addr;167168breakPoints_.push_back(pt);169anyBreakPoints_ = true;170guard.unlock();171Update(addr);172}173else if (!breakPoints_[bp].IsEnabled())174{175breakPoints_[bp].result |= BREAK_ACTION_PAUSE;176breakPoints_[bp].hasCond = false;177guard.unlock();178Update(addr);179}180}181182void CBreakPoints::RemoveBreakPoint(u32 addr)183{184std::unique_lock<std::mutex> guard(breakPointsMutex_);185size_t bp = FindBreakpoint(addr);186if (bp != INVALID_BREAKPOINT)187{188breakPoints_.erase(breakPoints_.begin() + bp);189190// Check again, there might've been an overlapping temp breakpoint.191bp = FindBreakpoint(addr);192if (bp != INVALID_BREAKPOINT)193breakPoints_.erase(breakPoints_.begin() + bp);194195anyBreakPoints_ = !breakPoints_.empty();196guard.unlock();197Update(addr);198}199}200201void CBreakPoints::ChangeBreakPoint(u32 addr, bool status)202{203std::unique_lock<std::mutex> guard(breakPointsMutex_);204size_t bp = FindBreakpoint(addr);205if (bp != INVALID_BREAKPOINT)206{207if (status)208breakPoints_[bp].result |= BREAK_ACTION_PAUSE;209else210breakPoints_[bp].result = BreakAction(breakPoints_[bp].result & ~BREAK_ACTION_PAUSE);211212guard.unlock();213Update(addr);214}215}216217void CBreakPoints::ChangeBreakPoint(u32 addr, BreakAction result)218{219std::unique_lock<std::mutex> guard(breakPointsMutex_);220size_t bp = FindBreakpoint(addr);221if (bp != INVALID_BREAKPOINT)222{223breakPoints_[bp].result = result;224guard.unlock();225Update(addr);226}227}228229void CBreakPoints::ClearAllBreakPoints()230{231if (!anyBreakPoints_)232return;233std::unique_lock<std::mutex> guard(breakPointsMutex_);234if (!breakPoints_.empty())235{236breakPoints_.clear();237guard.unlock();238Update();239}240}241242void CBreakPoints::ClearTemporaryBreakPoints()243{244if (!anyBreakPoints_)245return;246std::unique_lock<std::mutex> guard(breakPointsMutex_);247248bool update = false;249for (int i = (int)breakPoints_.size()-1; i >= 0; --i)250{251if (breakPoints_[i].temporary)252{253breakPoints_.erase(breakPoints_.begin() + i);254update = true;255}256}257258guard.unlock();259if (update)260Update();261}262263void CBreakPoints::ChangeBreakPointAddCond(u32 addr, const BreakPointCond &cond)264{265std::unique_lock<std::mutex> guard(breakPointsMutex_);266size_t bp = FindBreakpoint(addr);267if (bp != INVALID_BREAKPOINT)268{269breakPoints_[bp].hasCond = true;270breakPoints_[bp].cond = cond;271guard.unlock();272Update(addr);273}274}275276void CBreakPoints::ChangeBreakPointRemoveCond(u32 addr)277{278std::unique_lock<std::mutex> guard(breakPointsMutex_);279size_t bp = FindBreakpoint(addr);280if (bp != INVALID_BREAKPOINT)281{282breakPoints_[bp].hasCond = false;283guard.unlock();284Update(addr);285}286}287288BreakPointCond *CBreakPoints::GetBreakPointCondition(u32 addr)289{290std::lock_guard<std::mutex> guard(breakPointsMutex_);291size_t bp = FindBreakpoint(addr);292if (bp != INVALID_BREAKPOINT && breakPoints_[bp].hasCond)293return &breakPoints_[bp].cond;294return NULL;295}296297void CBreakPoints::ChangeBreakPointLogFormat(u32 addr, const std::string &fmt) {298std::unique_lock<std::mutex> guard(breakPointsMutex_);299size_t bp = FindBreakpoint(addr, true, false);300if (bp != INVALID_BREAKPOINT) {301breakPoints_[bp].logFormat = fmt;302guard.unlock();303Update(addr);304}305}306307BreakAction CBreakPoints::ExecBreakPoint(u32 addr) {308if (!anyBreakPoints_)309return BREAK_ACTION_IGNORE;310std::unique_lock<std::mutex> guard(breakPointsMutex_);311size_t bp = FindBreakpoint(addr, false);312if (bp != INVALID_BREAKPOINT) {313const BreakPoint &info = breakPoints_[bp];314guard.unlock();315316if (info.hasCond) {317// Evaluate the breakpoint and abort if necessary.318auto cond = CBreakPoints::GetBreakPointCondition(currentMIPS->pc);319if (cond && !cond->Evaluate())320return BREAK_ACTION_IGNORE;321}322323if (info.result & BREAK_ACTION_LOG) {324if (info.logFormat.empty()) {325NOTICE_LOG(Log::JIT, "BKP PC=%08x (%s)", addr, g_symbolMap->GetDescription(addr).c_str());326} else {327std::string formatted;328CBreakPoints::EvaluateLogFormat(currentDebugMIPS, info.logFormat, formatted);329NOTICE_LOG(Log::JIT, "BKP PC=%08x: %s", addr, formatted.c_str());330}331}332if ((info.result & BREAK_ACTION_PAUSE) && coreState != CORE_POWERUP) {333Core_EnableStepping(true, "cpu.breakpoint", info.addr);334}335336return info.result;337}338339return BREAK_ACTION_IGNORE;340}341342void CBreakPoints::AddMemCheck(u32 start, u32 end, MemCheckCondition cond, BreakAction result)343{344std::unique_lock<std::mutex> guard(memCheckMutex_);345346size_t mc = FindMemCheck(start, end);347if (mc == INVALID_MEMCHECK)348{349MemCheck check;350check.start = start;351check.end = end;352check.cond = cond;353check.result = result;354355memChecks_.push_back(check);356bool hadAny = anyMemChecks_.exchange(true);357if (!hadAny)358MemBlockOverrideDetailed();359guard.unlock();360Update();361}362else363{364memChecks_[mc].cond = (MemCheckCondition)(memChecks_[mc].cond | cond);365memChecks_[mc].result = (BreakAction)(memChecks_[mc].result | result);366bool hadAny = anyMemChecks_.exchange(true);367if (!hadAny)368MemBlockOverrideDetailed();369guard.unlock();370Update();371}372}373374void CBreakPoints::RemoveMemCheck(u32 start, u32 end)375{376std::unique_lock<std::mutex> guard(memCheckMutex_);377378size_t mc = FindMemCheck(start, end);379if (mc != INVALID_MEMCHECK)380{381memChecks_.erase(memChecks_.begin() + mc);382bool hadAny = anyMemChecks_.exchange(!memChecks_.empty());383if (hadAny)384MemBlockReleaseDetailed();385guard.unlock();386Update();387}388}389390void CBreakPoints::ChangeMemCheck(u32 start, u32 end, MemCheckCondition cond, BreakAction result)391{392std::unique_lock<std::mutex> guard(memCheckMutex_);393size_t mc = FindMemCheck(start, end);394if (mc != INVALID_MEMCHECK)395{396memChecks_[mc].cond = cond;397memChecks_[mc].result = result;398guard.unlock();399Update();400}401}402403void CBreakPoints::ClearAllMemChecks()404{405std::unique_lock<std::mutex> guard(memCheckMutex_);406407if (!memChecks_.empty())408{409memChecks_.clear();410bool hadAny = anyMemChecks_.exchange(false);411if (hadAny)412MemBlockReleaseDetailed();413guard.unlock();414Update();415}416}417418419void CBreakPoints::ChangeMemCheckAddCond(u32 start, u32 end, const BreakPointCond &cond) {420std::unique_lock<std::mutex> guard(memCheckMutex_);421size_t mc = FindMemCheck(start, end);422if (mc != INVALID_MEMCHECK) {423memChecks_[mc].hasCondition = true;424memChecks_[mc].condition = cond;425guard.unlock();426// No need to update jit for a condition add/remove, they're not baked in.427Update(-1);428}429}430431void CBreakPoints::ChangeMemCheckRemoveCond(u32 start, u32 end) {432std::unique_lock<std::mutex> guard(memCheckMutex_);433size_t mc = FindMemCheck(start, end);434if (mc != INVALID_MEMCHECK) {435memChecks_[mc].hasCondition = false;436guard.unlock();437// No need to update jit for a condition add/remove, they're not baked in.438Update(-1);439}440}441442BreakPointCond *CBreakPoints::GetMemCheckCondition(u32 start, u32 end) {443std::unique_lock<std::mutex> guard(memCheckMutex_);444size_t mc = FindMemCheck(start, end);445if (mc != INVALID_MEMCHECK && memChecks_[mc].hasCondition)446return &memChecks_[mc].condition;447return nullptr;448}449450void CBreakPoints::ChangeMemCheckLogFormat(u32 start, u32 end, const std::string &fmt) {451std::unique_lock<std::mutex> guard(memCheckMutex_);452size_t mc = FindMemCheck(start, end);453if (mc != INVALID_MEMCHECK) {454memChecks_[mc].logFormat = fmt;455guard.unlock();456Update();457}458}459460bool CBreakPoints::GetMemCheck(u32 start, u32 end, MemCheck *check) {461std::lock_guard<std::mutex> guard(memCheckMutex_);462size_t mc = FindMemCheck(start, end);463if (mc != INVALID_MEMCHECK) {464*check = memChecks_[mc];465return true;466}467return false;468}469470static inline u32 NotCached(u32 val) {471// Remove the cached part of the address as well as any mirror.472if ((val & 0x3F800000) == 0x04000000)473return val & ~0x40600000;474return val & ~0x40000000;475}476477bool CBreakPoints::GetMemCheckInRange(u32 address, int size, MemCheck *check) {478std::lock_guard<std::mutex> guard(memCheckMutex_);479auto result = GetMemCheckLocked(address, size);480if (result)481*check = *result;482return result != nullptr;483}484485MemCheck *CBreakPoints::GetMemCheckLocked(u32 address, int size) {486std::vector<MemCheck>::iterator iter;487for (iter = memChecks_.begin(); iter != memChecks_.end(); ++iter)488{489MemCheck &check = *iter;490if (check.end != 0)491{492if (NotCached(address + size) > NotCached(check.start) && NotCached(address) < NotCached(check.end))493return ✓494}495else496{497if (NotCached(check.start) == NotCached(address))498return ✓499}500}501502//none found503return 0;504}505506BreakAction CBreakPoints::ExecMemCheck(u32 address, bool write, int size, u32 pc, const char *reason)507{508if (!anyMemChecks_)509return BREAK_ACTION_IGNORE;510std::unique_lock<std::mutex> guard(memCheckMutex_);511auto check = GetMemCheckLocked(address, size);512if (check) {513BreakAction applyAction = check->Apply(address, write, size, pc);514if (applyAction == BREAK_ACTION_IGNORE)515return applyAction;516517auto copy = *check;518guard.unlock();519return copy.Action(address, write, size, pc, reason);520}521return BREAK_ACTION_IGNORE;522}523524BreakAction CBreakPoints::ExecOpMemCheck(u32 address, u32 pc)525{526// Note: currently, we don't check "on changed" for HLE (ExecMemCheck.)527// We'd need to more carefully specify memory changes in HLE for that.528int size = MIPSAnalyst::OpMemoryAccessSize(pc);529if (size == 0 && MIPSAnalyst::OpHasDelaySlot(pc)) {530// This means that the delay slot is what tripped us.531pc += 4;532size = MIPSAnalyst::OpMemoryAccessSize(pc);533}534535bool write = MIPSAnalyst::IsOpMemoryWrite(pc);536std::unique_lock<std::mutex> guard(memCheckMutex_);537auto check = GetMemCheckLocked(address, size);538if (check) {539int mask = MEMCHECK_WRITE | MEMCHECK_WRITE_ONCHANGE;540bool apply = false;541if (write && (check->cond & mask) == mask) {542if (MIPSAnalyst::OpWouldChangeMemory(pc, address, size)) {543apply = true;544}545} else {546apply = true;547}548if (apply) {549BreakAction applyAction = check->Apply(address, write, size, pc);550if (applyAction == BREAK_ACTION_IGNORE)551return applyAction;552553// Make a copy so we can safely unlock.554auto copy = *check;555guard.unlock();556return copy.Action(address, write, size, pc, "CPU");557}558}559return BREAK_ACTION_IGNORE;560}561562void CBreakPoints::SetSkipFirst(u32 pc)563{564breakSkipFirstAt_ = pc;565breakSkipFirstTicks_ = CoreTiming::GetTicks();566}567u32 CBreakPoints::CheckSkipFirst()568{569u32 pc = breakSkipFirstAt_;570if (breakSkipFirstTicks_ == CoreTiming::GetTicks())571return pc;572return 0;573}574575static MemCheck NotCached(MemCheck mc) {576// Toggle the cached part of the address.577mc.start ^= 0x40000000;578if (mc.end != 0)579mc.end ^= 0x40000000;580return mc;581}582583static MemCheck VRAMMirror(uint8_t mirror, MemCheck mc) {584mc.start &= ~0x00600000;585mc.start += 0x00200000 * mirror;586if (mc.end != 0) {587mc.end &= ~0x00600000;588mc.end += 0x00200000 * mirror;589if (mc.end < mc.start)590mc.end += 0x00200000;591}592return mc;593}594595void CBreakPoints::UpdateCachedMemCheckRanges() {596std::lock_guard<std::mutex> guard(memCheckMutex_);597memCheckRangesRead_.clear();598memCheckRangesWrite_.clear();599600auto add = [&](bool read, bool write, const MemCheck &mc) {601if (read)602memCheckRangesRead_.push_back(mc);603if (write)604memCheckRangesWrite_.push_back(mc);605};606607for (const auto &check : memChecks_) {608bool read = (check.cond & MEMCHECK_READ) != 0;609bool write = (check.cond & MEMCHECK_WRITE) != 0;610611if (Memory::IsVRAMAddress(check.start) && (check.end == 0 || Memory::IsVRAMAddress(check.end))) {612for (uint8_t mirror = 0; mirror < 4; ++mirror) {613MemCheck copy = VRAMMirror(mirror, check);614add(read, write, copy);615add(read, write, NotCached(copy));616}617} else {618add(read, write, check);619add(read, write, NotCached(check));620}621}622}623624std::vector<MemCheck> CBreakPoints::GetMemCheckRanges(bool write) {625std::lock_guard<std::mutex> guard(memCheckMutex_);626if (write)627return memCheckRangesWrite_;628return memCheckRangesRead_;629}630631std::vector<MemCheck> CBreakPoints::GetMemChecks()632{633std::lock_guard<std::mutex> guard(memCheckMutex_);634return memChecks_;635}636637std::vector<BreakPoint> CBreakPoints::GetBreakpoints()638{639std::lock_guard<std::mutex> guard(breakPointsMutex_);640return breakPoints_;641}642643bool CBreakPoints::HasBreakPoints() {644return anyBreakPoints_;645}646647bool CBreakPoints::HasMemChecks() {648return anyMemChecks_;649}650651void CBreakPoints::Update(u32 addr) {652if (MIPSComp::jit && addr != -1) {653bool resume = false;654if (Core_IsStepping() == false) {655Core_EnableStepping(true, "cpu.breakpoint.update", addr);656Core_WaitInactive(200);657resume = true;658}659660// In case this is a delay slot, clear the previous instruction too.661if (addr != 0)662mipsr4k.InvalidateICache(addr - 4, 8);663else664mipsr4k.ClearJitCache();665666if (resume)667Core_EnableStepping(false);668}669670if (anyMemChecks_ && addr != -1)671UpdateCachedMemCheckRanges();672673// Redraw in order to show the breakpoint.674System_Notify(SystemNotification::DISASSEMBLY);675}676677bool CBreakPoints::ValidateLogFormat(DebugInterface *cpu, const std::string &fmt) {678std::string ignore;679return EvaluateLogFormat(cpu, fmt, ignore);680}681682bool CBreakPoints::EvaluateLogFormat(DebugInterface *cpu, const std::string &fmt, std::string &result) {683PostfixExpression exp;684result.clear();685686size_t pos = 0;687while (pos < fmt.size()) {688size_t next = fmt.find_first_of('{', pos);689if (next == fmt.npos) {690// End of the string.691result += fmt.substr(pos);692break;693}694if (next != pos) {695result += fmt.substr(pos, next - pos);696pos = next;697}698699size_t end = fmt.find_first_of('}', next + 1);700if (end == fmt.npos) {701// Invalid: every expression needs a { and a }.702return false;703}704705std::string expression = fmt.substr(next + 1, end - next - 1);706if (expression.empty()) {707result += "{}";708} else {709int type = 'x';710if (expression.length() > 2 && expression[expression.length() - 2] == ':') {711switch (expression[expression.length() - 1]) {712case 'd':713case 'f':714case 'p':715case 's':716case 'x':717type = expression[expression.length() - 1];718expression.resize(expression.length() - 2);719break;720721default:722// Assume a ternary.723break;724}725}726727if (!cpu->initExpression(expression.c_str(), exp)) {728return false;729}730731union {732int i;733u32 u;734float f;735} expResult;736char resultString[256];737if (!cpu->parseExpression(exp, expResult.u)) {738return false;739}740741switch (type) {742case 'd':743snprintf(resultString, sizeof(resultString), "%d", expResult.i);744break;745case 'f':746snprintf(resultString, sizeof(resultString), "%f", expResult.f);747break;748case 'p':749snprintf(resultString, sizeof(resultString), "%08x[%08x]", expResult.u, Memory::IsValidAddress(expResult.u) ? Memory::Read_U32(expResult.u) : 0);750break;751case 's':752snprintf(resultString, sizeof(resultString) - 1, "%s", Memory::IsValidAddress(expResult.u) ? Memory::GetCharPointer(expResult.u) : "(invalid)");753break;754case 'x':755snprintf(resultString, sizeof(resultString), "%08x", expResult.u);756break;757}758result += resultString;759}760761// Skip the }.762pos = end + 1;763}764765return true;766}767768769