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/GPU/Debugger/Breakpoints.cpp
Views: 1401
// Copyright (c) 2013- 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 <functional>18#include <mutex>19#include <set>20#include <unordered_map>21#include <vector>2223#include "Common/CommonFuncs.h"24#include "Common/Math/expression_parser.h"25#include "GPU/Common/GPUDebugInterface.h"26#include "GPU/Debugger/Breakpoints.h"27#include "GPU/GPUState.h"2829namespace GPUBreakpoints {3031static void NothingToDo(bool) {32}3334struct BreakpointInfo {35bool isConditional = false;36PostfixExpression expression;37std::string expressionString;38};3940static std::mutex breaksLock;41static bool breakCmds[256];42static BreakpointInfo breakCmdsInfo[256];43static std::unordered_map<u32, BreakpointInfo> breakPCs;44static std::set<u32> breakTextures;45static std::set<u32> breakRenderTargets;46// Small optimization to avoid a lock/lookup for the common case.47static size_t breakPCsCount = 0;48static size_t breakTexturesCount = 0;49static size_t breakRenderTargetsCount = 0;50static std::function<void(bool)> notifyBreakpoints = &NothingToDo;5152// If these are set, the above are also, but they should be temporary.53static bool breakCmdsTemp[256];54static std::set<u32> breakPCsTemp;55static std::set<u32> breakTexturesTemp;56static std::set<u32> breakRenderTargetsTemp;57static bool textureChangeTemp = false;5859static u32 lastTexture = 0xFFFFFFFF;6061// These are commands we run before breaking on a texture.62// They are commands that affect the decoding of the texture.63const static u8 textureRelatedCmds[] = {64GE_CMD_TEXADDR0, GE_CMD_TEXADDR1, GE_CMD_TEXADDR2, GE_CMD_TEXADDR3, GE_CMD_TEXADDR4, GE_CMD_TEXADDR5, GE_CMD_TEXADDR6, GE_CMD_TEXADDR7,65GE_CMD_TEXBUFWIDTH0, GE_CMD_TEXBUFWIDTH1, GE_CMD_TEXBUFWIDTH2, GE_CMD_TEXBUFWIDTH3, GE_CMD_TEXBUFWIDTH4, GE_CMD_TEXBUFWIDTH5, GE_CMD_TEXBUFWIDTH6, GE_CMD_TEXBUFWIDTH7,66GE_CMD_TEXSIZE0, GE_CMD_TEXSIZE1, GE_CMD_TEXSIZE2, GE_CMD_TEXSIZE3, GE_CMD_TEXSIZE4, GE_CMD_TEXSIZE5, GE_CMD_TEXSIZE6, GE_CMD_TEXSIZE7,6768GE_CMD_CLUTADDR, GE_CMD_CLUTADDRUPPER, GE_CMD_LOADCLUT, GE_CMD_CLUTFORMAT,69GE_CMD_TEXFORMAT, GE_CMD_TEXMODE, GE_CMD_TEXTUREMAPENABLE,70GE_CMD_TEXFILTER, GE_CMD_TEXWRAP,71GE_CMD_TEXLEVEL,7273// Sometimes found between clut/texture params.74GE_CMD_TEXFLUSH, GE_CMD_TEXSYNC,75};76static std::vector<bool> nonTextureCmds;7778void Init(void (*hasBreakpoints)(bool flag)) {79notifyBreakpoints = hasBreakpoints;80ClearAllBreakpoints();8182nonTextureCmds.clear();83nonTextureCmds.resize(256, true);84for (size_t i = 0; i < ARRAY_SIZE(textureRelatedCmds); ++i) {85nonTextureCmds[textureRelatedCmds[i]] = false;86}87}8889void AddNonTextureTempBreakpoints() {90for (int i = 0; i < 256; ++i) {91if (nonTextureCmds[i]) {92AddCmdBreakpoint(i, true);93}94}95}9697u32 GetAdjustedTextureAddress(u32 op) {98const u8 cmd = op >> 24;99bool interesting = (cmd >= GE_CMD_TEXADDR0 && cmd <= GE_CMD_TEXADDR7);100interesting = interesting || (cmd >= GE_CMD_TEXBUFWIDTH0 && cmd <= GE_CMD_TEXBUFWIDTH7);101102if (!interesting) {103return (u32)-1;104}105106int level = cmd <= GE_CMD_TEXADDR7 ? cmd - GE_CMD_TEXADDR0 : cmd - GE_CMD_TEXBUFWIDTH0;107u32 addr;108109// Okay, so would this op modify the low or high part?110if (cmd <= GE_CMD_TEXADDR7) {111addr = (op & 0xFFFFF0) | ((gstate.texbufwidth[level] << 8) & 0x0F000000);112} else {113addr = (gstate.texaddr[level] & 0xFFFFF0) | ((op << 8) & 0x0F000000);114}115116return addr;117}118119u32 GetAdjustedRenderTargetAddress(u32 op) {120const u8 cmd = op >> 24;121switch (cmd) {122case GE_CMD_FRAMEBUFPTR:123case GE_CMD_ZBUFPTR:124return op & 0x001FFFF0;125}126127return (u32)-1;128}129130// Note: this now always returns false, but still needs to be called.131void CheckForTextureChange(u32 op, u32 addr) {132if (!textureChangeTemp) {133return;134}135136const u8 cmd = op >> 24;137bool enabled = gstate.isTextureMapEnabled();138139// Only for level 0.140if (cmd != GE_CMD_TEXADDR0 && cmd != GE_CMD_TEXBUFWIDTH0) {141// But we don't break when it's not enabled.142if (cmd == GE_CMD_TEXTUREMAPENABLE) {143enabled = (op & 1) != 0;144} else {145return;146}147}148if (enabled && addr != lastTexture) {149textureChangeTemp = false;150lastTexture = addr;151152// Silently convert to a primitive breakpoint, so we stop on use.153// Note: this may cause "spurious" breaks if the tex is changed and the changed back.154AddCmdBreakpoint(GE_CMD_PRIM, true);155AddCmdBreakpoint(GE_CMD_BEZIER, true);156AddCmdBreakpoint(GE_CMD_SPLINE, true);157AddCmdBreakpoint(GE_CMD_VAP, true);158}159}160161bool IsTextureCmdBreakpoint(u32 op) {162const u32 addr = GetAdjustedTextureAddress(op);163if (addr != (u32)-1) {164CheckForTextureChange(op, addr);165return IsTextureBreakpoint(addr);166} else {167CheckForTextureChange(op, gstate.getTextureAddress(0));168return false;169}170}171172bool IsRenderTargetCmdBreakpoint(u32 op) {173const u32 addr = GetAdjustedRenderTargetAddress(op);174if (addr != (u32)-1) {175return IsRenderTargetBreakpoint(addr);176}177return false;178}179180static bool HitBreakpointCond(BreakpointInfo &bp, u32 op) {181u8 cmd = op >> 24;182183// Temporarily set the value while running the breakpoint.184// It makes more intuitive sense for the referenced data to already be set.185// Note this won't perform actions, like matrix uploads.186u32 diff = gstate.cmdmem[cmd] ^ op;187gstate.cmdmem[cmd] ^= diff;188189u32 result = 1;190if (!GPUDebugExecExpression(gpuDebug, bp.expression, result))191result = 0;192193gstate.cmdmem[cmd] ^= diff;194return result != 0;195}196197static bool HitAddressBreakpoint(u32 pc, u32 op) {198if (breakPCsCount == 0)199return false;200201std::lock_guard<std::mutex> guard(breaksLock);202auto entry = breakPCs.find(pc);203if (entry == breakPCs.end())204return false;205206if (entry->second.isConditional) {207return HitBreakpointCond(entry->second, op);208}209return true;210}211212static bool HitOpBreakpoint(u32 op) {213u8 cmd = op >> 24;214if (!IsCmdBreakpoint(cmd))215return false;216217if (breakCmdsInfo[cmd].isConditional) {218std::lock_guard<std::mutex> guard(breaksLock);219return HitBreakpointCond(breakCmdsInfo[cmd], op);220}221222return true;223}224225bool IsBreakpoint(u32 pc, u32 op) {226if (HitAddressBreakpoint(pc, op) || HitOpBreakpoint(op)) {227return true;228}229230if ((breakTexturesCount != 0 || textureChangeTemp) && IsTextureCmdBreakpoint(op)) {231// Break on the next non-texture.232AddNonTextureTempBreakpoints();233}234if (breakRenderTargetsCount != 0 && IsRenderTargetCmdBreakpoint(op)) {235return true;236}237238return false;239}240241bool IsAddressBreakpoint(u32 addr, bool &temp) {242if (breakPCsCount == 0) {243temp = false;244return false;245}246247std::lock_guard<std::mutex> guard(breaksLock);248temp = breakPCsTemp.find(addr) != breakPCsTemp.end();249return breakPCs.find(addr) != breakPCs.end();250}251252bool IsAddressBreakpoint(u32 addr) {253if (breakPCsCount == 0) {254return false;255}256257std::lock_guard<std::mutex> guard(breaksLock);258return breakPCs.find(addr) != breakPCs.end();259}260261bool IsTextureBreakpoint(u32 addr, bool &temp) {262if (breakTexturesCount == 0) {263temp = false;264return false;265}266267std::lock_guard<std::mutex> guard(breaksLock);268temp = breakTexturesTemp.find(addr) != breakTexturesTemp.end();269return breakTextures.find(addr) != breakTextures.end();270}271272bool IsTextureBreakpoint(u32 addr) {273if (breakTexturesCount == 0) {274return false;275}276277std::lock_guard<std::mutex> guard(breaksLock);278return breakTextures.find(addr) != breakTextures.end();279}280281bool IsRenderTargetBreakpoint(u32 addr, bool &temp) {282if (breakRenderTargetsCount == 0) {283temp = false;284return false;285}286287addr &= 0x001FFFF0;288289std::lock_guard<std::mutex> guard(breaksLock);290temp = breakRenderTargetsTemp.find(addr) != breakRenderTargetsTemp.end();291return breakRenderTargets.find(addr) != breakRenderTargets.end();292}293294bool IsRenderTargetBreakpoint(u32 addr) {295if (breakRenderTargetsCount == 0) {296return false;297}298299addr &= 0x001FFFF0;300301std::lock_guard<std::mutex> guard(breaksLock);302return breakRenderTargets.find(addr) != breakRenderTargets.end();303}304305bool IsOpBreakpoint(u32 op, bool &temp) {306return IsCmdBreakpoint(op >> 24, temp);307}308309bool IsOpBreakpoint(u32 op) {310return IsCmdBreakpoint(op >> 24);311}312313bool IsCmdBreakpoint(u8 cmd, bool &temp) {314temp = breakCmdsTemp[cmd];315return breakCmds[cmd];316}317318bool IsCmdBreakpoint(u8 cmd) {319return breakCmds[cmd];320}321322static bool HasAnyBreakpoints() {323if (breakPCsCount != 0 || breakTexturesCount != 0 || breakRenderTargetsCount != 0)324return true;325if (textureChangeTemp)326return true;327328for (int i = 0; i < 256; ++i) {329if (breakCmds[i] || breakCmdsTemp[i])330return true;331}332333return false;334}335336void AddAddressBreakpoint(u32 addr, bool temp) {337std::lock_guard<std::mutex> guard(breaksLock);338339if (temp) {340if (breakPCs.find(addr) == breakPCs.end()) {341breakPCsTemp.insert(addr);342breakPCs[addr].isConditional = false;343}344// Already normal breakpoint, let's not make it temporary.345} else {346// Remove the temporary marking.347breakPCsTemp.erase(addr);348breakPCs.emplace(addr, BreakpointInfo{});349}350351breakPCsCount = breakPCs.size();352notifyBreakpoints(true);353}354355void AddCmdBreakpoint(u8 cmd, bool temp) {356if (temp) {357if (!breakCmds[cmd]) {358breakCmdsTemp[cmd] = true;359breakCmds[cmd] = true;360breakCmdsInfo[cmd].isConditional = false;361}362// Ignore adding a temp breakpoint when a normal one exists.363} else {364// This is no longer temporary.365breakCmdsTemp[cmd] = false;366if (!breakCmds[cmd]) {367breakCmds[cmd] = true;368breakCmdsInfo[cmd].isConditional = false;369}370}371notifyBreakpoints(true);372}373374void AddTextureBreakpoint(u32 addr, bool temp) {375std::lock_guard<std::mutex> guard(breaksLock);376377if (temp) {378if (breakTextures.find(addr) == breakTextures.end()) {379breakTexturesTemp.insert(addr);380breakTextures.insert(addr);381}382} else {383breakTexturesTemp.erase(addr);384breakTextures.insert(addr);385}386387breakTexturesCount = breakTextures.size();388notifyBreakpoints(true);389}390391void AddRenderTargetBreakpoint(u32 addr, bool temp) {392std::lock_guard<std::mutex> guard(breaksLock);393394addr &= 0x001FFFF0;395396if (temp) {397if (breakRenderTargets.find(addr) == breakRenderTargets.end()) {398breakRenderTargetsTemp.insert(addr);399breakRenderTargets.insert(addr);400}401} else {402breakRenderTargetsTemp.erase(addr);403breakRenderTargets.insert(addr);404}405406breakRenderTargetsCount = breakRenderTargets.size();407notifyBreakpoints(true);408}409410void AddTextureChangeTempBreakpoint() {411textureChangeTemp = true;412notifyBreakpoints(true);413}414415void AddAnyTempBreakpoint() {416for (int i = 0; i < 256; ++i) {417AddCmdBreakpoint(i, true);418}419notifyBreakpoints(true);420}421422void RemoveAddressBreakpoint(u32 addr) {423std::lock_guard<std::mutex> guard(breaksLock);424425breakPCsTemp.erase(addr);426breakPCs.erase(addr);427428breakPCsCount = breakPCs.size();429notifyBreakpoints(HasAnyBreakpoints());430}431432void RemoveCmdBreakpoint(u8 cmd) {433std::lock_guard<std::mutex> guard(breaksLock);434435breakCmdsTemp[cmd] = false;436breakCmds[cmd] = false;437notifyBreakpoints(HasAnyBreakpoints());438}439440void RemoveTextureBreakpoint(u32 addr) {441std::lock_guard<std::mutex> guard(breaksLock);442443breakTexturesTemp.erase(addr);444breakTextures.erase(addr);445446breakTexturesCount = breakTextures.size();447notifyBreakpoints(HasAnyBreakpoints());448}449450void RemoveRenderTargetBreakpoint(u32 addr) {451std::lock_guard<std::mutex> guard(breaksLock);452453addr &= 0x001FFFF0;454455breakRenderTargetsTemp.erase(addr);456breakRenderTargets.erase(addr);457458breakRenderTargetsCount = breakRenderTargets.size();459notifyBreakpoints(HasAnyBreakpoints());460}461462void RemoveTextureChangeTempBreakpoint() {463std::lock_guard<std::mutex> guard(breaksLock);464465textureChangeTemp = false;466notifyBreakpoints(HasAnyBreakpoints());467}468469static bool SetupCond(BreakpointInfo &bp, const std::string &expression, std::string *error) {470bool success = true;471if (expression.length() != 0) {472if (GPUDebugInitExpression(gpuDebug, expression.c_str(), bp.expression)) {473bp.isConditional = true;474bp.expressionString = expression;475} else {476// Don't change if it failed.477if (error)478*error = getExpressionError();479success = false;480}481} else {482bp.isConditional = false;483}484return success;485}486487bool SetAddressBreakpointCond(u32 addr, const std::string &expression, std::string *error) {488// Must have one in the first place, make sure it's not temporary.489AddAddressBreakpoint(addr);490491std::lock_guard<std::mutex> guard(breaksLock);492auto &bp = breakPCs[addr];493return SetupCond(breakPCs[addr], expression, error);494}495496bool GetAddressBreakpointCond(u32 addr, std::string *expression) {497std::lock_guard<std::mutex> guard(breaksLock);498auto entry = breakPCs.find(addr);499if (entry != breakPCs.end() && entry->second.isConditional) {500if (expression)501*expression = entry->second.expressionString;502return true;503}504return false;505}506507bool SetCmdBreakpointCond(u8 cmd, const std::string &expression, std::string *error) {508// Must have one in the first place, make sure it's not temporary.509AddCmdBreakpoint(cmd);510511std::lock_guard<std::mutex> guard(breaksLock);512return SetupCond(breakCmdsInfo[cmd], expression, error);513}514515bool GetCmdBreakpointCond(u8 cmd, std::string *expression) {516if (breakCmds[cmd] && breakCmdsInfo[cmd].isConditional) {517if (expression) {518std::lock_guard<std::mutex> guard(breaksLock);519*expression = breakCmdsInfo[cmd].expressionString;520}521return true;522}523return false;524}525526void UpdateLastTexture(u32 addr) {527lastTexture = addr;528}529530void ClearAllBreakpoints() {531std::lock_guard<std::mutex> guard(breaksLock);532533for (int i = 0; i < 256; ++i) {534breakCmds[i] = false;535breakCmdsTemp[i] = false;536}537breakPCs.clear();538breakTextures.clear();539breakRenderTargets.clear();540541breakPCsTemp.clear();542breakTexturesTemp.clear();543breakRenderTargetsTemp.clear();544545breakPCsCount = breakPCs.size();546breakTexturesCount = breakTextures.size();547breakRenderTargetsCount = breakRenderTargets.size();548549textureChangeTemp = false;550notifyBreakpoints(false);551}552553void ClearTempBreakpoints() {554std::lock_guard<std::mutex> guard(breaksLock);555556// Reset ones that were temporary back to non-breakpoints in the primary arrays.557for (int i = 0; i < 256; ++i) {558if (breakCmdsTemp[i]) {559breakCmds[i] = false;560breakCmdsTemp[i] = false;561}562}563564for (auto it = breakPCsTemp.begin(), end = breakPCsTemp.end(); it != end; ++it) {565breakPCs.erase(*it);566}567breakPCsTemp.clear();568breakPCsCount = breakPCs.size();569570for (auto it = breakTexturesTemp.begin(), end = breakTexturesTemp.end(); it != end; ++it) {571breakTextures.erase(*it);572}573breakTexturesTemp.clear();574breakTexturesCount = breakTextures.size();575576for (auto it = breakRenderTargetsTemp.begin(), end = breakRenderTargetsTemp.end(); it != end; ++it) {577breakRenderTargets.erase(*it);578}579breakRenderTargetsTemp.clear();580breakRenderTargetsCount = breakRenderTargets.size();581582textureChangeTemp = false;583notifyBreakpoints(HasAnyBreakpoints());584}585586};587588589