CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: hrydgard/ppsspp
Path: blob/master/GPU/Debugger/Breakpoints.cpp
Views: 1401
1
// Copyright (c) 2013- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include <functional>
19
#include <mutex>
20
#include <set>
21
#include <unordered_map>
22
#include <vector>
23
24
#include "Common/CommonFuncs.h"
25
#include "Common/Math/expression_parser.h"
26
#include "GPU/Common/GPUDebugInterface.h"
27
#include "GPU/Debugger/Breakpoints.h"
28
#include "GPU/GPUState.h"
29
30
namespace GPUBreakpoints {
31
32
static void NothingToDo(bool) {
33
}
34
35
struct BreakpointInfo {
36
bool isConditional = false;
37
PostfixExpression expression;
38
std::string expressionString;
39
};
40
41
static std::mutex breaksLock;
42
static bool breakCmds[256];
43
static BreakpointInfo breakCmdsInfo[256];
44
static std::unordered_map<u32, BreakpointInfo> breakPCs;
45
static std::set<u32> breakTextures;
46
static std::set<u32> breakRenderTargets;
47
// Small optimization to avoid a lock/lookup for the common case.
48
static size_t breakPCsCount = 0;
49
static size_t breakTexturesCount = 0;
50
static size_t breakRenderTargetsCount = 0;
51
static std::function<void(bool)> notifyBreakpoints = &NothingToDo;
52
53
// If these are set, the above are also, but they should be temporary.
54
static bool breakCmdsTemp[256];
55
static std::set<u32> breakPCsTemp;
56
static std::set<u32> breakTexturesTemp;
57
static std::set<u32> breakRenderTargetsTemp;
58
static bool textureChangeTemp = false;
59
60
static u32 lastTexture = 0xFFFFFFFF;
61
62
// These are commands we run before breaking on a texture.
63
// They are commands that affect the decoding of the texture.
64
const static u8 textureRelatedCmds[] = {
65
GE_CMD_TEXADDR0, GE_CMD_TEXADDR1, GE_CMD_TEXADDR2, GE_CMD_TEXADDR3, GE_CMD_TEXADDR4, GE_CMD_TEXADDR5, GE_CMD_TEXADDR6, GE_CMD_TEXADDR7,
66
GE_CMD_TEXBUFWIDTH0, GE_CMD_TEXBUFWIDTH1, GE_CMD_TEXBUFWIDTH2, GE_CMD_TEXBUFWIDTH3, GE_CMD_TEXBUFWIDTH4, GE_CMD_TEXBUFWIDTH5, GE_CMD_TEXBUFWIDTH6, GE_CMD_TEXBUFWIDTH7,
67
GE_CMD_TEXSIZE0, GE_CMD_TEXSIZE1, GE_CMD_TEXSIZE2, GE_CMD_TEXSIZE3, GE_CMD_TEXSIZE4, GE_CMD_TEXSIZE5, GE_CMD_TEXSIZE6, GE_CMD_TEXSIZE7,
68
69
GE_CMD_CLUTADDR, GE_CMD_CLUTADDRUPPER, GE_CMD_LOADCLUT, GE_CMD_CLUTFORMAT,
70
GE_CMD_TEXFORMAT, GE_CMD_TEXMODE, GE_CMD_TEXTUREMAPENABLE,
71
GE_CMD_TEXFILTER, GE_CMD_TEXWRAP,
72
GE_CMD_TEXLEVEL,
73
74
// Sometimes found between clut/texture params.
75
GE_CMD_TEXFLUSH, GE_CMD_TEXSYNC,
76
};
77
static std::vector<bool> nonTextureCmds;
78
79
void Init(void (*hasBreakpoints)(bool flag)) {
80
notifyBreakpoints = hasBreakpoints;
81
ClearAllBreakpoints();
82
83
nonTextureCmds.clear();
84
nonTextureCmds.resize(256, true);
85
for (size_t i = 0; i < ARRAY_SIZE(textureRelatedCmds); ++i) {
86
nonTextureCmds[textureRelatedCmds[i]] = false;
87
}
88
}
89
90
void AddNonTextureTempBreakpoints() {
91
for (int i = 0; i < 256; ++i) {
92
if (nonTextureCmds[i]) {
93
AddCmdBreakpoint(i, true);
94
}
95
}
96
}
97
98
u32 GetAdjustedTextureAddress(u32 op) {
99
const u8 cmd = op >> 24;
100
bool interesting = (cmd >= GE_CMD_TEXADDR0 && cmd <= GE_CMD_TEXADDR7);
101
interesting = interesting || (cmd >= GE_CMD_TEXBUFWIDTH0 && cmd <= GE_CMD_TEXBUFWIDTH7);
102
103
if (!interesting) {
104
return (u32)-1;
105
}
106
107
int level = cmd <= GE_CMD_TEXADDR7 ? cmd - GE_CMD_TEXADDR0 : cmd - GE_CMD_TEXBUFWIDTH0;
108
u32 addr;
109
110
// Okay, so would this op modify the low or high part?
111
if (cmd <= GE_CMD_TEXADDR7) {
112
addr = (op & 0xFFFFF0) | ((gstate.texbufwidth[level] << 8) & 0x0F000000);
113
} else {
114
addr = (gstate.texaddr[level] & 0xFFFFF0) | ((op << 8) & 0x0F000000);
115
}
116
117
return addr;
118
}
119
120
u32 GetAdjustedRenderTargetAddress(u32 op) {
121
const u8 cmd = op >> 24;
122
switch (cmd) {
123
case GE_CMD_FRAMEBUFPTR:
124
case GE_CMD_ZBUFPTR:
125
return op & 0x001FFFF0;
126
}
127
128
return (u32)-1;
129
}
130
131
// Note: this now always returns false, but still needs to be called.
132
void CheckForTextureChange(u32 op, u32 addr) {
133
if (!textureChangeTemp) {
134
return;
135
}
136
137
const u8 cmd = op >> 24;
138
bool enabled = gstate.isTextureMapEnabled();
139
140
// Only for level 0.
141
if (cmd != GE_CMD_TEXADDR0 && cmd != GE_CMD_TEXBUFWIDTH0) {
142
// But we don't break when it's not enabled.
143
if (cmd == GE_CMD_TEXTUREMAPENABLE) {
144
enabled = (op & 1) != 0;
145
} else {
146
return;
147
}
148
}
149
if (enabled && addr != lastTexture) {
150
textureChangeTemp = false;
151
lastTexture = addr;
152
153
// Silently convert to a primitive breakpoint, so we stop on use.
154
// Note: this may cause "spurious" breaks if the tex is changed and the changed back.
155
AddCmdBreakpoint(GE_CMD_PRIM, true);
156
AddCmdBreakpoint(GE_CMD_BEZIER, true);
157
AddCmdBreakpoint(GE_CMD_SPLINE, true);
158
AddCmdBreakpoint(GE_CMD_VAP, true);
159
}
160
}
161
162
bool IsTextureCmdBreakpoint(u32 op) {
163
const u32 addr = GetAdjustedTextureAddress(op);
164
if (addr != (u32)-1) {
165
CheckForTextureChange(op, addr);
166
return IsTextureBreakpoint(addr);
167
} else {
168
CheckForTextureChange(op, gstate.getTextureAddress(0));
169
return false;
170
}
171
}
172
173
bool IsRenderTargetCmdBreakpoint(u32 op) {
174
const u32 addr = GetAdjustedRenderTargetAddress(op);
175
if (addr != (u32)-1) {
176
return IsRenderTargetBreakpoint(addr);
177
}
178
return false;
179
}
180
181
static bool HitBreakpointCond(BreakpointInfo &bp, u32 op) {
182
u8 cmd = op >> 24;
183
184
// Temporarily set the value while running the breakpoint.
185
// It makes more intuitive sense for the referenced data to already be set.
186
// Note this won't perform actions, like matrix uploads.
187
u32 diff = gstate.cmdmem[cmd] ^ op;
188
gstate.cmdmem[cmd] ^= diff;
189
190
u32 result = 1;
191
if (!GPUDebugExecExpression(gpuDebug, bp.expression, result))
192
result = 0;
193
194
gstate.cmdmem[cmd] ^= diff;
195
return result != 0;
196
}
197
198
static bool HitAddressBreakpoint(u32 pc, u32 op) {
199
if (breakPCsCount == 0)
200
return false;
201
202
std::lock_guard<std::mutex> guard(breaksLock);
203
auto entry = breakPCs.find(pc);
204
if (entry == breakPCs.end())
205
return false;
206
207
if (entry->second.isConditional) {
208
return HitBreakpointCond(entry->second, op);
209
}
210
return true;
211
}
212
213
static bool HitOpBreakpoint(u32 op) {
214
u8 cmd = op >> 24;
215
if (!IsCmdBreakpoint(cmd))
216
return false;
217
218
if (breakCmdsInfo[cmd].isConditional) {
219
std::lock_guard<std::mutex> guard(breaksLock);
220
return HitBreakpointCond(breakCmdsInfo[cmd], op);
221
}
222
223
return true;
224
}
225
226
bool IsBreakpoint(u32 pc, u32 op) {
227
if (HitAddressBreakpoint(pc, op) || HitOpBreakpoint(op)) {
228
return true;
229
}
230
231
if ((breakTexturesCount != 0 || textureChangeTemp) && IsTextureCmdBreakpoint(op)) {
232
// Break on the next non-texture.
233
AddNonTextureTempBreakpoints();
234
}
235
if (breakRenderTargetsCount != 0 && IsRenderTargetCmdBreakpoint(op)) {
236
return true;
237
}
238
239
return false;
240
}
241
242
bool IsAddressBreakpoint(u32 addr, bool &temp) {
243
if (breakPCsCount == 0) {
244
temp = false;
245
return false;
246
}
247
248
std::lock_guard<std::mutex> guard(breaksLock);
249
temp = breakPCsTemp.find(addr) != breakPCsTemp.end();
250
return breakPCs.find(addr) != breakPCs.end();
251
}
252
253
bool IsAddressBreakpoint(u32 addr) {
254
if (breakPCsCount == 0) {
255
return false;
256
}
257
258
std::lock_guard<std::mutex> guard(breaksLock);
259
return breakPCs.find(addr) != breakPCs.end();
260
}
261
262
bool IsTextureBreakpoint(u32 addr, bool &temp) {
263
if (breakTexturesCount == 0) {
264
temp = false;
265
return false;
266
}
267
268
std::lock_guard<std::mutex> guard(breaksLock);
269
temp = breakTexturesTemp.find(addr) != breakTexturesTemp.end();
270
return breakTextures.find(addr) != breakTextures.end();
271
}
272
273
bool IsTextureBreakpoint(u32 addr) {
274
if (breakTexturesCount == 0) {
275
return false;
276
}
277
278
std::lock_guard<std::mutex> guard(breaksLock);
279
return breakTextures.find(addr) != breakTextures.end();
280
}
281
282
bool IsRenderTargetBreakpoint(u32 addr, bool &temp) {
283
if (breakRenderTargetsCount == 0) {
284
temp = false;
285
return false;
286
}
287
288
addr &= 0x001FFFF0;
289
290
std::lock_guard<std::mutex> guard(breaksLock);
291
temp = breakRenderTargetsTemp.find(addr) != breakRenderTargetsTemp.end();
292
return breakRenderTargets.find(addr) != breakRenderTargets.end();
293
}
294
295
bool IsRenderTargetBreakpoint(u32 addr) {
296
if (breakRenderTargetsCount == 0) {
297
return false;
298
}
299
300
addr &= 0x001FFFF0;
301
302
std::lock_guard<std::mutex> guard(breaksLock);
303
return breakRenderTargets.find(addr) != breakRenderTargets.end();
304
}
305
306
bool IsOpBreakpoint(u32 op, bool &temp) {
307
return IsCmdBreakpoint(op >> 24, temp);
308
}
309
310
bool IsOpBreakpoint(u32 op) {
311
return IsCmdBreakpoint(op >> 24);
312
}
313
314
bool IsCmdBreakpoint(u8 cmd, bool &temp) {
315
temp = breakCmdsTemp[cmd];
316
return breakCmds[cmd];
317
}
318
319
bool IsCmdBreakpoint(u8 cmd) {
320
return breakCmds[cmd];
321
}
322
323
static bool HasAnyBreakpoints() {
324
if (breakPCsCount != 0 || breakTexturesCount != 0 || breakRenderTargetsCount != 0)
325
return true;
326
if (textureChangeTemp)
327
return true;
328
329
for (int i = 0; i < 256; ++i) {
330
if (breakCmds[i] || breakCmdsTemp[i])
331
return true;
332
}
333
334
return false;
335
}
336
337
void AddAddressBreakpoint(u32 addr, bool temp) {
338
std::lock_guard<std::mutex> guard(breaksLock);
339
340
if (temp) {
341
if (breakPCs.find(addr) == breakPCs.end()) {
342
breakPCsTemp.insert(addr);
343
breakPCs[addr].isConditional = false;
344
}
345
// Already normal breakpoint, let's not make it temporary.
346
} else {
347
// Remove the temporary marking.
348
breakPCsTemp.erase(addr);
349
breakPCs.emplace(addr, BreakpointInfo{});
350
}
351
352
breakPCsCount = breakPCs.size();
353
notifyBreakpoints(true);
354
}
355
356
void AddCmdBreakpoint(u8 cmd, bool temp) {
357
if (temp) {
358
if (!breakCmds[cmd]) {
359
breakCmdsTemp[cmd] = true;
360
breakCmds[cmd] = true;
361
breakCmdsInfo[cmd].isConditional = false;
362
}
363
// Ignore adding a temp breakpoint when a normal one exists.
364
} else {
365
// This is no longer temporary.
366
breakCmdsTemp[cmd] = false;
367
if (!breakCmds[cmd]) {
368
breakCmds[cmd] = true;
369
breakCmdsInfo[cmd].isConditional = false;
370
}
371
}
372
notifyBreakpoints(true);
373
}
374
375
void AddTextureBreakpoint(u32 addr, bool temp) {
376
std::lock_guard<std::mutex> guard(breaksLock);
377
378
if (temp) {
379
if (breakTextures.find(addr) == breakTextures.end()) {
380
breakTexturesTemp.insert(addr);
381
breakTextures.insert(addr);
382
}
383
} else {
384
breakTexturesTemp.erase(addr);
385
breakTextures.insert(addr);
386
}
387
388
breakTexturesCount = breakTextures.size();
389
notifyBreakpoints(true);
390
}
391
392
void AddRenderTargetBreakpoint(u32 addr, bool temp) {
393
std::lock_guard<std::mutex> guard(breaksLock);
394
395
addr &= 0x001FFFF0;
396
397
if (temp) {
398
if (breakRenderTargets.find(addr) == breakRenderTargets.end()) {
399
breakRenderTargetsTemp.insert(addr);
400
breakRenderTargets.insert(addr);
401
}
402
} else {
403
breakRenderTargetsTemp.erase(addr);
404
breakRenderTargets.insert(addr);
405
}
406
407
breakRenderTargetsCount = breakRenderTargets.size();
408
notifyBreakpoints(true);
409
}
410
411
void AddTextureChangeTempBreakpoint() {
412
textureChangeTemp = true;
413
notifyBreakpoints(true);
414
}
415
416
void AddAnyTempBreakpoint() {
417
for (int i = 0; i < 256; ++i) {
418
AddCmdBreakpoint(i, true);
419
}
420
notifyBreakpoints(true);
421
}
422
423
void RemoveAddressBreakpoint(u32 addr) {
424
std::lock_guard<std::mutex> guard(breaksLock);
425
426
breakPCsTemp.erase(addr);
427
breakPCs.erase(addr);
428
429
breakPCsCount = breakPCs.size();
430
notifyBreakpoints(HasAnyBreakpoints());
431
}
432
433
void RemoveCmdBreakpoint(u8 cmd) {
434
std::lock_guard<std::mutex> guard(breaksLock);
435
436
breakCmdsTemp[cmd] = false;
437
breakCmds[cmd] = false;
438
notifyBreakpoints(HasAnyBreakpoints());
439
}
440
441
void RemoveTextureBreakpoint(u32 addr) {
442
std::lock_guard<std::mutex> guard(breaksLock);
443
444
breakTexturesTemp.erase(addr);
445
breakTextures.erase(addr);
446
447
breakTexturesCount = breakTextures.size();
448
notifyBreakpoints(HasAnyBreakpoints());
449
}
450
451
void RemoveRenderTargetBreakpoint(u32 addr) {
452
std::lock_guard<std::mutex> guard(breaksLock);
453
454
addr &= 0x001FFFF0;
455
456
breakRenderTargetsTemp.erase(addr);
457
breakRenderTargets.erase(addr);
458
459
breakRenderTargetsCount = breakRenderTargets.size();
460
notifyBreakpoints(HasAnyBreakpoints());
461
}
462
463
void RemoveTextureChangeTempBreakpoint() {
464
std::lock_guard<std::mutex> guard(breaksLock);
465
466
textureChangeTemp = false;
467
notifyBreakpoints(HasAnyBreakpoints());
468
}
469
470
static bool SetupCond(BreakpointInfo &bp, const std::string &expression, std::string *error) {
471
bool success = true;
472
if (expression.length() != 0) {
473
if (GPUDebugInitExpression(gpuDebug, expression.c_str(), bp.expression)) {
474
bp.isConditional = true;
475
bp.expressionString = expression;
476
} else {
477
// Don't change if it failed.
478
if (error)
479
*error = getExpressionError();
480
success = false;
481
}
482
} else {
483
bp.isConditional = false;
484
}
485
return success;
486
}
487
488
bool SetAddressBreakpointCond(u32 addr, const std::string &expression, std::string *error) {
489
// Must have one in the first place, make sure it's not temporary.
490
AddAddressBreakpoint(addr);
491
492
std::lock_guard<std::mutex> guard(breaksLock);
493
auto &bp = breakPCs[addr];
494
return SetupCond(breakPCs[addr], expression, error);
495
}
496
497
bool GetAddressBreakpointCond(u32 addr, std::string *expression) {
498
std::lock_guard<std::mutex> guard(breaksLock);
499
auto entry = breakPCs.find(addr);
500
if (entry != breakPCs.end() && entry->second.isConditional) {
501
if (expression)
502
*expression = entry->second.expressionString;
503
return true;
504
}
505
return false;
506
}
507
508
bool SetCmdBreakpointCond(u8 cmd, const std::string &expression, std::string *error) {
509
// Must have one in the first place, make sure it's not temporary.
510
AddCmdBreakpoint(cmd);
511
512
std::lock_guard<std::mutex> guard(breaksLock);
513
return SetupCond(breakCmdsInfo[cmd], expression, error);
514
}
515
516
bool GetCmdBreakpointCond(u8 cmd, std::string *expression) {
517
if (breakCmds[cmd] && breakCmdsInfo[cmd].isConditional) {
518
if (expression) {
519
std::lock_guard<std::mutex> guard(breaksLock);
520
*expression = breakCmdsInfo[cmd].expressionString;
521
}
522
return true;
523
}
524
return false;
525
}
526
527
void UpdateLastTexture(u32 addr) {
528
lastTexture = addr;
529
}
530
531
void ClearAllBreakpoints() {
532
std::lock_guard<std::mutex> guard(breaksLock);
533
534
for (int i = 0; i < 256; ++i) {
535
breakCmds[i] = false;
536
breakCmdsTemp[i] = false;
537
}
538
breakPCs.clear();
539
breakTextures.clear();
540
breakRenderTargets.clear();
541
542
breakPCsTemp.clear();
543
breakTexturesTemp.clear();
544
breakRenderTargetsTemp.clear();
545
546
breakPCsCount = breakPCs.size();
547
breakTexturesCount = breakTextures.size();
548
breakRenderTargetsCount = breakRenderTargets.size();
549
550
textureChangeTemp = false;
551
notifyBreakpoints(false);
552
}
553
554
void ClearTempBreakpoints() {
555
std::lock_guard<std::mutex> guard(breaksLock);
556
557
// Reset ones that were temporary back to non-breakpoints in the primary arrays.
558
for (int i = 0; i < 256; ++i) {
559
if (breakCmdsTemp[i]) {
560
breakCmds[i] = false;
561
breakCmdsTemp[i] = false;
562
}
563
}
564
565
for (auto it = breakPCsTemp.begin(), end = breakPCsTemp.end(); it != end; ++it) {
566
breakPCs.erase(*it);
567
}
568
breakPCsTemp.clear();
569
breakPCsCount = breakPCs.size();
570
571
for (auto it = breakTexturesTemp.begin(), end = breakTexturesTemp.end(); it != end; ++it) {
572
breakTextures.erase(*it);
573
}
574
breakTexturesTemp.clear();
575
breakTexturesCount = breakTextures.size();
576
577
for (auto it = breakRenderTargetsTemp.begin(), end = breakRenderTargetsTemp.end(); it != end; ++it) {
578
breakRenderTargets.erase(*it);
579
}
580
breakRenderTargetsTemp.clear();
581
breakRenderTargetsCount = breakRenderTargets.size();
582
583
textureChangeTemp = false;
584
notifyBreakpoints(HasAnyBreakpoints());
585
}
586
587
};
588
589