// Copyright (c) 2012- PPSSPP Project. // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 2.0 or later versions. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 2.0 for more details. // A copy of the GPL 2.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include #include #include #include "Common/System/System.h" #include "Common/Log.h" #include "Core/Core.h" #include "Core/Debugger/Breakpoints.h" #include "Core/Debugger/MemBlockInfo.h" #include "Core/Debugger/SymbolMap.h" #include "Core/MemMap.h" #include "Core/MIPS/MIPSAnalyst.h" #include "Core/MIPS/MIPSDebugInterface.h" #include "Core/MIPS/JitCommon/JitCommon.h" #include "Core/CoreTiming.h" std::atomic anyBreakPoints_(false); std::atomic anyMemChecks_(false); static std::mutex breakPointsMutex_; std::vector CBreakPoints::breakPoints_; u32 CBreakPoints::breakSkipFirstAt_ = 0; u64 CBreakPoints::breakSkipFirstTicks_ = 0; static std::mutex memCheckMutex_; std::vector CBreakPoints::memChecks_; std::vector CBreakPoints::memCheckRangesRead_; std::vector CBreakPoints::memCheckRangesWrite_; void MemCheck::Log(u32 addr, bool write, int size, u32 pc, const char *reason) { if (result & BREAK_ACTION_LOG) { const char *type = write ? "Write" : "Read"; if (logFormat.empty()) { NOTICE_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()); } else { std::string formatted; CBreakPoints::EvaluateLogFormat(currentDebugMIPS, logFormat, formatted); NOTICE_LOG(MEMMAP, "CHK %s%i(%s) at %08x: %s", type, size * 8, reason, addr, formatted.c_str()); } } } BreakAction MemCheck::Apply(u32 addr, bool write, int size, u32 pc) { int mask = write ? MEMCHECK_WRITE : MEMCHECK_READ; if (cond & mask) { if (hasCondition) { if (!condition.Evaluate()) return BREAK_ACTION_IGNORE; } ++numHits; return result; } return BREAK_ACTION_IGNORE; } BreakAction MemCheck::Action(u32 addr, bool write, int size, u32 pc, const char *reason) { // Conditions have always already been checked if we get here. Log(addr, write, size, pc, reason); if ((result & BREAK_ACTION_PAUSE) && coreState != CORE_POWERUP) { Core_EnableStepping(true, "memory.breakpoint", start); } return result; } // Note: must lock while calling this. size_t CBreakPoints::FindBreakpoint(u32 addr, bool matchTemp, bool temp) { size_t found = INVALID_BREAKPOINT; for (size_t i = 0; i < breakPoints_.size(); ++i) { const auto &bp = breakPoints_[i]; if (bp.addr == addr && (!matchTemp || bp.temporary == temp)) { if (bp.IsEnabled()) return i; // Hold out until the first enabled one. if (found == INVALID_BREAKPOINT) found = i; } } return found; } size_t CBreakPoints::FindMemCheck(u32 start, u32 end) { for (size_t i = 0; i < memChecks_.size(); ++i) { if (memChecks_[i].start == start && memChecks_[i].end == end) return i; } return INVALID_MEMCHECK; } bool CBreakPoints::IsAddressBreakPoint(u32 addr) { if (!anyBreakPoints_) return false; std::lock_guard guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr); return bp != INVALID_BREAKPOINT && breakPoints_[bp].result != BREAK_ACTION_IGNORE; } bool CBreakPoints::IsAddressBreakPoint(u32 addr, bool* enabled) { if (!anyBreakPoints_) return false; std::lock_guard guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr); if (bp == INVALID_BREAKPOINT) return false; if (enabled != nullptr) *enabled = breakPoints_[bp].IsEnabled(); return true; } bool CBreakPoints::IsTempBreakPoint(u32 addr) { std::lock_guard guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr, true, true); return bp != INVALID_BREAKPOINT; } bool CBreakPoints::RangeContainsBreakPoint(u32 addr, u32 size) { if (!anyBreakPoints_) return false; std::lock_guard guard(breakPointsMutex_); const u32 end = addr + size; for (const auto &bp : breakPoints_) { if (bp.addr >= addr && bp.addr < end) return true; } return false; } void CBreakPoints::AddBreakPoint(u32 addr, bool temp) { std::unique_lock guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr, true, temp); if (bp == INVALID_BREAKPOINT) { BreakPoint pt; pt.result |= BREAK_ACTION_PAUSE; pt.temporary = temp; pt.addr = addr; breakPoints_.push_back(pt); anyBreakPoints_ = true; guard.unlock(); Update(addr); } else if (!breakPoints_[bp].IsEnabled()) { breakPoints_[bp].result |= BREAK_ACTION_PAUSE; breakPoints_[bp].hasCond = false; guard.unlock(); Update(addr); } } void CBreakPoints::RemoveBreakPoint(u32 addr) { std::unique_lock guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr); if (bp != INVALID_BREAKPOINT) { breakPoints_.erase(breakPoints_.begin() + bp); // Check again, there might've been an overlapping temp breakpoint. bp = FindBreakpoint(addr); if (bp != INVALID_BREAKPOINT) breakPoints_.erase(breakPoints_.begin() + bp); anyBreakPoints_ = !breakPoints_.empty(); guard.unlock(); Update(addr); } } void CBreakPoints::ChangeBreakPoint(u32 addr, bool status) { std::unique_lock guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr); if (bp != INVALID_BREAKPOINT) { if (status) breakPoints_[bp].result |= BREAK_ACTION_PAUSE; else breakPoints_[bp].result = BreakAction(breakPoints_[bp].result & ~BREAK_ACTION_PAUSE); guard.unlock(); Update(addr); } } void CBreakPoints::ChangeBreakPoint(u32 addr, BreakAction result) { std::unique_lock guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr); if (bp != INVALID_BREAKPOINT) { breakPoints_[bp].result = result; guard.unlock(); Update(addr); } } void CBreakPoints::ClearAllBreakPoints() { if (!anyBreakPoints_) return; std::unique_lock guard(breakPointsMutex_); if (!breakPoints_.empty()) { breakPoints_.clear(); guard.unlock(); Update(); } } void CBreakPoints::ClearTemporaryBreakPoints() { if (!anyBreakPoints_) return; std::unique_lock guard(breakPointsMutex_); bool update = false; for (int i = (int)breakPoints_.size()-1; i >= 0; --i) { if (breakPoints_[i].temporary) { breakPoints_.erase(breakPoints_.begin() + i); update = true; } } guard.unlock(); if (update) Update(); } void CBreakPoints::ChangeBreakPointAddCond(u32 addr, const BreakPointCond &cond) { std::unique_lock guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr); if (bp != INVALID_BREAKPOINT) { breakPoints_[bp].hasCond = true; breakPoints_[bp].cond = cond; guard.unlock(); Update(addr); } } void CBreakPoints::ChangeBreakPointRemoveCond(u32 addr) { std::unique_lock guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr); if (bp != INVALID_BREAKPOINT) { breakPoints_[bp].hasCond = false; guard.unlock(); Update(addr); } } BreakPointCond *CBreakPoints::GetBreakPointCondition(u32 addr) { std::lock_guard guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr); if (bp != INVALID_BREAKPOINT && breakPoints_[bp].hasCond) return &breakPoints_[bp].cond; return NULL; } void CBreakPoints::ChangeBreakPointLogFormat(u32 addr, const std::string &fmt) { std::unique_lock guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr, true, false); if (bp != INVALID_BREAKPOINT) { breakPoints_[bp].logFormat = fmt; guard.unlock(); Update(addr); } } BreakAction CBreakPoints::ExecBreakPoint(u32 addr) { if (!anyBreakPoints_) return BREAK_ACTION_IGNORE; std::unique_lock guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr, false); if (bp != INVALID_BREAKPOINT) { const BreakPoint &info = breakPoints_[bp]; guard.unlock(); if (info.hasCond) { // Evaluate the breakpoint and abort if necessary. auto cond = CBreakPoints::GetBreakPointCondition(currentMIPS->pc); if (cond && !cond->Evaluate()) return BREAK_ACTION_IGNORE; } if (info.result & BREAK_ACTION_LOG) { if (info.logFormat.empty()) { NOTICE_LOG(JIT, "BKP PC=%08x (%s)", addr, g_symbolMap->GetDescription(addr).c_str()); } else { std::string formatted; CBreakPoints::EvaluateLogFormat(currentDebugMIPS, info.logFormat, formatted); NOTICE_LOG(JIT, "BKP PC=%08x: %s", addr, formatted.c_str()); } } if ((info.result & BREAK_ACTION_PAUSE) && coreState != CORE_POWERUP) { Core_EnableStepping(true, "cpu.breakpoint", info.addr); } return info.result; } return BREAK_ACTION_IGNORE; } void CBreakPoints::AddMemCheck(u32 start, u32 end, MemCheckCondition cond, BreakAction result) { std::unique_lock guard(memCheckMutex_); size_t mc = FindMemCheck(start, end); if (mc == INVALID_MEMCHECK) { MemCheck check; check.start = start; check.end = end; check.cond = cond; check.result = result; memChecks_.push_back(check); bool hadAny = anyMemChecks_.exchange(true); if (!hadAny) MemBlockOverrideDetailed(); guard.unlock(); Update(); } else { memChecks_[mc].cond = (MemCheckCondition)(memChecks_[mc].cond | cond); memChecks_[mc].result = (BreakAction)(memChecks_[mc].result | result); bool hadAny = anyMemChecks_.exchange(true); if (!hadAny) MemBlockOverrideDetailed(); guard.unlock(); Update(); } } void CBreakPoints::RemoveMemCheck(u32 start, u32 end) { std::unique_lock guard(memCheckMutex_); size_t mc = FindMemCheck(start, end); if (mc != INVALID_MEMCHECK) { memChecks_.erase(memChecks_.begin() + mc); bool hadAny = anyMemChecks_.exchange(!memChecks_.empty()); if (hadAny) MemBlockReleaseDetailed(); guard.unlock(); Update(); } } void CBreakPoints::ChangeMemCheck(u32 start, u32 end, MemCheckCondition cond, BreakAction result) { std::unique_lock guard(memCheckMutex_); size_t mc = FindMemCheck(start, end); if (mc != INVALID_MEMCHECK) { memChecks_[mc].cond = cond; memChecks_[mc].result = result; guard.unlock(); Update(); } } void CBreakPoints::ClearAllMemChecks() { std::unique_lock guard(memCheckMutex_); if (!memChecks_.empty()) { memChecks_.clear(); bool hadAny = anyMemChecks_.exchange(false); if (hadAny) MemBlockReleaseDetailed(); guard.unlock(); Update(); } } void CBreakPoints::ChangeMemCheckAddCond(u32 start, u32 end, const BreakPointCond &cond) { std::unique_lock guard(memCheckMutex_); size_t mc = FindMemCheck(start, end); if (mc != INVALID_MEMCHECK) { memChecks_[mc].hasCondition = true; memChecks_[mc].condition = cond; guard.unlock(); // No need to update jit for a condition add/remove, they're not baked in. Update(-1); } } void CBreakPoints::ChangeMemCheckRemoveCond(u32 start, u32 end) { std::unique_lock guard(memCheckMutex_); size_t mc = FindMemCheck(start, end); if (mc != INVALID_MEMCHECK) { memChecks_[mc].hasCondition = false; guard.unlock(); // No need to update jit for a condition add/remove, they're not baked in. Update(-1); } } BreakPointCond *CBreakPoints::GetMemCheckCondition(u32 start, u32 end) { std::unique_lock guard(memCheckMutex_); size_t mc = FindMemCheck(start, end); if (mc != INVALID_MEMCHECK && memChecks_[mc].hasCondition) return &memChecks_[mc].condition; return nullptr; } void CBreakPoints::ChangeMemCheckLogFormat(u32 start, u32 end, const std::string &fmt) { std::unique_lock guard(memCheckMutex_); size_t mc = FindMemCheck(start, end); if (mc != INVALID_MEMCHECK) { memChecks_[mc].logFormat = fmt; guard.unlock(); Update(); } } bool CBreakPoints::GetMemCheck(u32 start, u32 end, MemCheck *check) { std::lock_guard guard(memCheckMutex_); size_t mc = FindMemCheck(start, end); if (mc != INVALID_MEMCHECK) { *check = memChecks_[mc]; return true; } return false; } static inline u32 NotCached(u32 val) { // Remove the cached part of the address as well as any mirror. if ((val & 0x3F800000) == 0x04000000) return val & ~0x40600000; return val & ~0x40000000; } bool CBreakPoints::GetMemCheckInRange(u32 address, int size, MemCheck *check) { std::lock_guard guard(memCheckMutex_); auto result = GetMemCheckLocked(address, size); if (result) *check = *result; return result != nullptr; } MemCheck *CBreakPoints::GetMemCheckLocked(u32 address, int size) { std::vector::iterator iter; for (iter = memChecks_.begin(); iter != memChecks_.end(); ++iter) { MemCheck &check = *iter; if (check.end != 0) { if (NotCached(address + size) > NotCached(check.start) && NotCached(address) < NotCached(check.end)) return ✓ } else { if (NotCached(check.start) == NotCached(address)) return ✓ } } //none found return 0; } BreakAction CBreakPoints::ExecMemCheck(u32 address, bool write, int size, u32 pc, const char *reason) { if (!anyMemChecks_) return BREAK_ACTION_IGNORE; std::unique_lock guard(memCheckMutex_); auto check = GetMemCheckLocked(address, size); if (check) { BreakAction applyAction = check->Apply(address, write, size, pc); if (applyAction == BREAK_ACTION_IGNORE) return applyAction; auto copy = *check; guard.unlock(); return copy.Action(address, write, size, pc, reason); } return BREAK_ACTION_IGNORE; } BreakAction CBreakPoints::ExecOpMemCheck(u32 address, u32 pc) { // Note: currently, we don't check "on changed" for HLE (ExecMemCheck.) // We'd need to more carefully specify memory changes in HLE for that. int size = MIPSAnalyst::OpMemoryAccessSize(pc); if (size == 0 && MIPSAnalyst::OpHasDelaySlot(pc)) { // This means that the delay slot is what tripped us. pc += 4; size = MIPSAnalyst::OpMemoryAccessSize(pc); } bool write = MIPSAnalyst::IsOpMemoryWrite(pc); std::unique_lock guard(memCheckMutex_); auto check = GetMemCheckLocked(address, size); if (check) { int mask = MEMCHECK_WRITE | MEMCHECK_WRITE_ONCHANGE; bool apply = false; if (write && (check->cond & mask) == mask) { if (MIPSAnalyst::OpWouldChangeMemory(pc, address, size)) { apply = true; } } else { apply = true; } if (apply) { BreakAction applyAction = check->Apply(address, write, size, pc); if (applyAction == BREAK_ACTION_IGNORE) return applyAction; // Make a copy so we can safely unlock. auto copy = *check; guard.unlock(); return copy.Action(address, write, size, pc, "CPU"); } } return BREAK_ACTION_IGNORE; } void CBreakPoints::SetSkipFirst(u32 pc) { breakSkipFirstAt_ = pc; breakSkipFirstTicks_ = CoreTiming::GetTicks(); } u32 CBreakPoints::CheckSkipFirst() { u32 pc = breakSkipFirstAt_; if (breakSkipFirstTicks_ == CoreTiming::GetTicks()) return pc; return 0; } static MemCheck NotCached(MemCheck mc) { // Toggle the cached part of the address. mc.start ^= 0x40000000; if (mc.end != 0) mc.end ^= 0x40000000; return mc; } static MemCheck VRAMMirror(uint8_t mirror, MemCheck mc) { mc.start &= ~0x00600000; mc.start += 0x00200000 * mirror; if (mc.end != 0) { mc.end &= ~0x00600000; mc.end += 0x00200000 * mirror; if (mc.end < mc.start) mc.end += 0x00200000; } return mc; } void CBreakPoints::UpdateCachedMemCheckRanges() { std::lock_guard guard(memCheckMutex_); memCheckRangesRead_.clear(); memCheckRangesWrite_.clear(); auto add = [&](bool read, bool write, const MemCheck &mc) { if (read) memCheckRangesRead_.push_back(mc); if (write) memCheckRangesWrite_.push_back(mc); }; for (const auto &check : memChecks_) { bool read = (check.cond & MEMCHECK_READ) != 0; bool write = (check.cond & MEMCHECK_WRITE) != 0; if (Memory::IsVRAMAddress(check.start) && (check.end == 0 || Memory::IsVRAMAddress(check.end))) { for (uint8_t mirror = 0; mirror < 4; ++mirror) { MemCheck copy = VRAMMirror(mirror, check); add(read, write, copy); add(read, write, NotCached(copy)); } } else { add(read, write, check); add(read, write, NotCached(check)); } } } const std::vector CBreakPoints::GetMemCheckRanges(bool write) { std::lock_guard guard(memCheckMutex_); if (write) return memCheckRangesWrite_; return memCheckRangesRead_; } const std::vector CBreakPoints::GetMemChecks() { std::lock_guard guard(memCheckMutex_); return memChecks_; } const std::vector CBreakPoints::GetBreakpoints() { std::lock_guard guard(breakPointsMutex_); return breakPoints_; } bool CBreakPoints::HasBreakPoints() { return anyBreakPoints_; } bool CBreakPoints::HasMemChecks() { return anyMemChecks_; } void CBreakPoints::Update(u32 addr) { if (MIPSComp::jit && addr != -1) { bool resume = false; if (Core_IsStepping() == false) { Core_EnableStepping(true, "cpu.breakpoint.update", addr); Core_WaitInactive(200); resume = true; } // In case this is a delay slot, clear the previous instruction too. if (addr != 0) mipsr4k.InvalidateICache(addr - 4, 8); else mipsr4k.ClearJitCache(); if (resume) Core_EnableStepping(false); } if (anyMemChecks_ && addr != -1) UpdateCachedMemCheckRanges(); // Redraw in order to show the breakpoint. System_Notify(SystemNotification::DISASSEMBLY); } bool CBreakPoints::ValidateLogFormat(DebugInterface *cpu, const std::string &fmt) { std::string ignore; return EvaluateLogFormat(cpu, fmt, ignore); } bool CBreakPoints::EvaluateLogFormat(DebugInterface *cpu, const std::string &fmt, std::string &result) { PostfixExpression exp; result.clear(); size_t pos = 0; while (pos < fmt.size()) { size_t next = fmt.find_first_of("{", pos); if (next == fmt.npos) { // End of the string. result += fmt.substr(pos); break; } if (next != pos) { result += fmt.substr(pos, next - pos); pos = next; } size_t end = fmt.find_first_of("}", next + 1); if (end == fmt.npos) { // Invalid: every expression needs a { and a }. return false; } std::string expression = fmt.substr(next + 1, end - next - 1); if (expression.empty()) { result += "{}"; } else { int type = 'x'; if (expression.length() > 2 && expression[expression.length() - 2] == ':') { switch (expression[expression.length() - 1]) { case 'd': case 'f': case 'p': case 's': case 'x': type = expression[expression.length() - 1]; expression.resize(expression.length() - 2); break; default: // Assume a ternary. break; } } if (!cpu->initExpression(expression.c_str(), exp)) { return false; } union { int i; u32 u; float f; } expResult; char resultString[256]; if (!cpu->parseExpression(exp, expResult.u)) { return false; } switch (type) { case 'd': snprintf(resultString, sizeof(resultString), "%d", expResult.i); break; case 'f': snprintf(resultString, sizeof(resultString), "%f", expResult.f); break; case 'p': snprintf(resultString, sizeof(resultString), "%08x[%08x]", expResult.u, Memory::IsValidAddress(expResult.u) ? Memory::Read_U32(expResult.u) : 0); break; case 's': snprintf(resultString, sizeof(resultString) - 1, "%s", Memory::IsValidAddress(expResult.u) ? Memory::GetCharPointer(expResult.u) : "(invalid)"); break; case 'x': snprintf(resultString, sizeof(resultString), "%08x", expResult.u); break; } result += resultString; } // Skip the }. pos = end + 1; } return true; }