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/Core/Debugger/WebSocket/MemoryInfoSubscriber.cpp
Views: 1401
1
// Copyright (c) 2021- 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 <algorithm>
19
#include "Core/MIPS/MIPS.h"
20
#include "Core/MIPS/MIPSDebugInterface.h"
21
#include "Core/Debugger/MemBlockInfo.h"
22
#include "Core/Debugger/WebSocket/MemoryInfoSubscriber.h"
23
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
24
#include "Core/MemMap.h"
25
26
class WebSocketMemoryInfoState : public DebuggerSubscriber {
27
public:
28
WebSocketMemoryInfoState() {
29
}
30
~WebSocketMemoryInfoState() {
31
UpdateOverride(false);
32
}
33
34
void Mapping(DebuggerRequest &req);
35
void Config(DebuggerRequest &req);
36
void Set(DebuggerRequest &req);
37
void List(DebuggerRequest &req);
38
void Search(DebuggerRequest &req);
39
40
protected:
41
void UpdateOverride(bool flag);
42
43
bool detailOverride_ = false;
44
};
45
46
DebuggerSubscriber *WebSocketMemoryInfoInit(DebuggerEventHandlerMap &map) {
47
auto p = new WebSocketMemoryInfoState();
48
map["memory.mapping"] = std::bind(&WebSocketMemoryInfoState::Mapping, p, std::placeholders::_1);
49
map["memory.info.config"] = std::bind(&WebSocketMemoryInfoState::Config, p, std::placeholders::_1);
50
map["memory.info.set"] = std::bind(&WebSocketMemoryInfoState::Set, p, std::placeholders::_1);
51
map["memory.info.list"] = std::bind(&WebSocketMemoryInfoState::List, p, std::placeholders::_1);
52
map["memory.info.search"] = std::bind(&WebSocketMemoryInfoState::Search, p, std::placeholders::_1);
53
54
return p;
55
}
56
57
void WebSocketMemoryInfoState::UpdateOverride(bool flag) {
58
if (detailOverride_ && !flag)
59
MemBlockReleaseDetailed();
60
if (!detailOverride_ && flag)
61
MemBlockOverrideDetailed();
62
detailOverride_ = flag;
63
}
64
65
// List memory map data (memory.mapping)
66
//
67
// No parameters.
68
//
69
// Response (same event name):
70
// - ranges: array of objects:
71
// - type: one of "ram", "vram", "sram".
72
// - subtype: "primary" or "mirror".
73
// - name: string, friendly name.
74
// - address: number, start address of range.
75
// - size: number, in bytes.
76
//
77
// Note: Even if you set false, may stay enabled if set by user or another debug session.
78
void WebSocketMemoryInfoState::Mapping(DebuggerRequest &req) {
79
struct MemRange {
80
const char *type;
81
const char *subtype;
82
const char *name;
83
const uint32_t address;
84
const uint32_t size;
85
};
86
constexpr uint32_t kernelMemorySize = PSP_GetKernelMemoryEnd() - PSP_GetKernelMemoryBase();
87
constexpr uint32_t volatileMemorySize = PSP_GetVolatileMemoryEnd() - PSP_GetVolatileMemoryStart();
88
static constexpr MemRange ranges[] = {
89
{ "sram", "primary", "Scratchpad", PSP_GetScratchpadMemoryBase(), Memory::SCRATCHPAD_SIZE },
90
{ "vram", "primary", "VRAM", PSP_GetVidMemBase(), Memory::VRAM_SIZE },
91
{ "vram", "mirror", "VRAM (Swizzled)", PSP_GetVidMemBase() + Memory::VRAM_SIZE * 1, Memory::VRAM_SIZE },
92
{ "vram", "mirror", "VRAM (Mirror)", PSP_GetVidMemBase() + Memory::VRAM_SIZE * 2, Memory::VRAM_SIZE },
93
{ "vram", "mirror", "VRAM (Swizzled + Interleaved)", PSP_GetVidMemBase() + Memory::VRAM_SIZE * 3, Memory::VRAM_SIZE },
94
{ "ram", "primary", "Kernel Memory", 0x80000000 | PSP_GetKernelMemoryBase(), kernelMemorySize },
95
{ "ram", "primary", "Volatile Memory", PSP_GetVolatileMemoryStart(), volatileMemorySize },
96
// Size is specially calculated.
97
{ "ram", "primary", "User Memory", PSP_GetUserMemoryBase(), 0 },
98
};
99
100
JsonWriter &json = req.Respond();
101
json.pushArray("ranges");
102
for (auto range : ranges) {
103
uint32_t size = range.size;
104
if (size == 0) {
105
size = Memory::g_MemorySize;
106
if (size == 0) {
107
size = Memory::RAM_NORMAL_SIZE;
108
}
109
size -= kernelMemorySize + volatileMemorySize;
110
}
111
json.pushDict();
112
json.writeString("type", range.type);
113
json.writeString("subtype", range.subtype);
114
json.writeString("name", range.name);
115
json.writeUint("address", range.address);
116
json.writeUint("size", size);
117
json.pop();
118
119
// Also write the uncached range.
120
json.pushDict();
121
json.writeString("type", range.type);
122
json.writeString("subtype", "mirror");
123
json.writeString("name", std::string("Uncached ") + range.name);
124
json.writeUint("address", 0x40000000 | range.address);
125
json.writeUint("size", size);
126
json.pop();
127
}
128
json.pop();
129
}
130
131
// Update memory info tracking config (memory.info.config)
132
//
133
// Parameters:
134
// - detailed: optional, boolean to force enable detailed tracking (perf impact.)
135
//
136
// Response (same event name):
137
// - detailed: boolean state of tracking before any changes.
138
//
139
// Note: Even if you set false, may stay enabled if set by user or another debug session.
140
void WebSocketMemoryInfoState::Config(DebuggerRequest &req) {
141
bool setDetailed = req.HasParam("detailed");
142
bool detailed = false;
143
if (!req.ParamBool("detailed", &detailed, DebuggerParamType::OPTIONAL))
144
return;
145
146
JsonWriter &json = req.Respond();
147
json.writeBool("detailed", MemBlockInfoDetailed());
148
149
if (setDetailed)
150
UpdateOverride(detailed);
151
}
152
153
static MemBlockFlags FlagFromType(const std::string &type) {
154
if (type == "write")
155
return MemBlockFlags::WRITE;
156
if (type == "texture")
157
return MemBlockFlags::TEXTURE;
158
if (type == "alloc")
159
return MemBlockFlags::ALLOC;
160
if (type == "suballoc")
161
return MemBlockFlags::SUB_ALLOC;
162
if (type == "free")
163
return MemBlockFlags::FREE;
164
if (type == "subfree")
165
return MemBlockFlags::SUB_FREE;
166
return MemBlockFlags::SKIP_MEMCHECK;
167
}
168
169
static std::string TypeFromFlag(const MemBlockFlags &flag) {
170
if (flag & MemBlockFlags::WRITE)
171
return "write";
172
else if (flag & MemBlockFlags::TEXTURE)
173
return "texture";
174
else if (flag & MemBlockFlags::ALLOC)
175
return "alloc";
176
else if (flag & MemBlockFlags::SUB_ALLOC)
177
return "suballoc";
178
return "error";
179
}
180
181
// Update memory info tagging (memory.info.set)
182
//
183
// Parameters:
184
// - address: number representing start address of the range to modify.
185
// - size: number, bytes from start address.
186
// - type: string, one of:
187
// - "write" for last modification information.
188
// - "texture" for last texture usage information.
189
// - "alloc" for allocation information.
190
// - "suballoc" for allocations within an existing allocation.
191
// - "free" to mark a previous allocation and its suballocations freed (ignores tag.)
192
// - "subfree" to mark a previous suballocation freed (ignores tag.)
193
// - tag: string label to give the memory. Optional if type if free or subfree.
194
// - pc: optional, number indicating PC address for this tag.
195
//
196
// Response (same event name) with no extra data.
197
//
198
// Note: Only one tag per type is maintained for any given memory address.
199
// Small extent info may be ignored unless detailed tracking enabled (see memory.info.config.)
200
void WebSocketMemoryInfoState::Set(DebuggerRequest &req) {
201
if (!currentDebugMIPS->isAlive() || !Memory::IsActive())
202
return req.Fail("CPU not started");
203
204
std::string type;
205
if (!req.ParamString("type", &type))
206
return;
207
std::string tag;
208
if (type != "free" && type != "subfree") {
209
if (!req.ParamString("tag", &tag))
210
return;
211
}
212
uint32_t addr;
213
if (!req.ParamU32("address", &addr))
214
return;
215
uint32_t size;
216
if (!req.ParamU32("size", &size))
217
return;
218
uint32_t pc = currentMIPS->pc;
219
if (!req.ParamU32("pc", &pc, false, DebuggerParamType::OPTIONAL))
220
return;
221
222
MemBlockFlags flags = MemBlockFlags::SKIP_MEMCHECK | FlagFromType(type);
223
if (flags == MemBlockFlags::SKIP_MEMCHECK)
224
return req.Fail("Invaid type - expecting write, texture, alloc, suballoc, free, or subfree");
225
226
if (!Memory::IsValidAddress(addr))
227
return req.Fail("Invalid address");
228
else if (!Memory::IsValidRange(addr, size))
229
return req.Fail("Invalid size");
230
231
NotifyMemInfoPC(flags, addr, size, pc, tag.c_str(), tag.size());
232
req.Respond();
233
}
234
235
// List memory info tags for address range (memory.info.list)
236
//
237
// Parameters:
238
// - address: number representing start address of the range.
239
// - size: number, bytes from start address.
240
// - type: optional string to limit information to one of:
241
// - "write" for last modification information.
242
// - "texture" for last texture usage information.
243
// - "alloc" for allocation information.
244
// - "suballoc" for allocations within an existing allocation.
245
//
246
// Response (same event name):
247
// - extents: array of objects:
248
// - type: one of the above type string values.
249
// - address: number (may be outside requested range if overlapping.)
250
// - size: number (may be outside requested range if overlapping.)
251
// - ticks: number indicating tick counter as of last tag.
252
// - pc: number address of last tag.
253
// - tag: string tag for this memory extent.
254
// - allocated: boolean, if this extent is marked as allocated (for alloc/suballoc types.)
255
void WebSocketMemoryInfoState::List(DebuggerRequest &req) {
256
if (!currentDebugMIPS->isAlive() || !Memory::IsActive())
257
return req.Fail("CPU not started");
258
259
std::string type;
260
if (!req.ParamString("type", &type, DebuggerParamType::OPTIONAL))
261
return;
262
uint32_t addr;
263
if (!req.ParamU32("address", &addr))
264
return;
265
uint32_t size;
266
if (!req.ParamU32("size", &size))
267
return;
268
269
// Allow type to be omitted.
270
MemBlockFlags flags = MemBlockFlags::SKIP_MEMCHECK | FlagFromType(type);
271
if (flags == MemBlockFlags::SKIP_MEMCHECK && req.HasParam("type"))
272
return req.Fail("Invaid type - expecting write, texture, alloc, suballoc, free, or subfree");
273
274
if (!Memory::IsValidAddress(addr))
275
return req.Fail("Invalid address");
276
else if (!Memory::IsValidRange(addr, size))
277
return req.Fail("Invalid size");
278
279
std::vector<MemBlockInfo> results;
280
if (flags == MemBlockFlags::SKIP_MEMCHECK)
281
results = FindMemInfo(addr, size);
282
else
283
results = FindMemInfoByFlag(flags, addr, size);
284
285
JsonWriter &json = req.Respond();
286
json.pushArray("extents");
287
for (const auto &result : results) {
288
json.pushDict();
289
json.writeString("type", TypeFromFlag(result.flags));
290
json.writeUint("address", result.start);
291
json.writeUint("size", result.size);
292
json.writeFloat("ticks", result.ticks);
293
json.writeUint("pc", result.pc);
294
json.writeString("tag", result.tag);
295
json.writeBool("allocated", result.allocated);
296
json.pop();
297
}
298
json.pop();
299
}
300
301
// Search memory info tags for a string (memory.info.search)
302
//
303
// Parameters:
304
// - address: optional number representing start address of the range.
305
// - end: optional end address as a number (otherwise uses start address.)
306
// - match: string to search for within tag.
307
// - type: optional string to limit information to one of:
308
// - "write" for last modification information.
309
// - "texture" for last texture usage information.
310
// - "alloc" for allocation information.
311
// - "suballoc" for allocations within an existing allocation.
312
//
313
// Response (same event name):
314
// - extent: null, or matching object containing:
315
// - type: one of the above type string values.
316
// - address: number (may be outside requested range if overlapping.)
317
// - size: number (may be outside requested range if overlapping.)
318
// - ticks: number indicating tick counter as of last tag.
319
// - pc: number address of last tag.
320
// - tag: string tag for this memory extent.
321
// - allocated: boolean, if this extent is marked as allocated (for alloc/suballoc types.)
322
//
323
// Note: may not be fast.
324
void WebSocketMemoryInfoState::Search(DebuggerRequest &req) {
325
if (!currentDebugMIPS->isAlive() || !Memory::IsActive())
326
return req.Fail("CPU not started");
327
328
uint32_t start = 0;
329
if (!req.ParamU32("address", &start, false, DebuggerParamType::OPTIONAL))
330
return;
331
uint32_t end = start;
332
if (!req.ParamU32("end", &end, false, DebuggerParamType::OPTIONAL))
333
return;
334
std::string type;
335
if (!req.ParamString("type", &type, DebuggerParamType::OPTIONAL))
336
return;
337
std::string match;
338
if (!req.ParamString("match", &match))
339
return;
340
341
// Allow type to be omitted.
342
MemBlockFlags flags = MemBlockFlags::SKIP_MEMCHECK | FlagFromType(type);
343
if (flags == MemBlockFlags::SKIP_MEMCHECK && req.HasParam("type"))
344
return req.Fail("Invaid type - expecting write, texture, alloc, suballoc, free, or subfree");
345
346
start = RoundMemAddressUp(start);
347
end = RoundMemAddressUp(end);
348
std::transform(match.begin(), match.end(), match.begin(), ::tolower);
349
350
bool found = false;
351
MemBlockInfo foundResult;
352
353
uint32_t addr = start;
354
constexpr uint32_t CHUNK_SIZE = 0x1000;
355
do {
356
uint32_t chunk_end = addr + CHUNK_SIZE;
357
if (addr < end && chunk_end >= end) {
358
chunk_end = end;
359
}
360
361
std::vector<MemBlockInfo> results;
362
if (flags == MemBlockFlags::SKIP_MEMCHECK)
363
results = FindMemInfo(addr, chunk_end - addr);
364
else
365
results = FindMemInfoByFlag(flags, addr, chunk_end - addr);
366
367
for (const auto &result : results) {
368
std::string lowercase = result.tag;
369
std::transform(lowercase.begin(), lowercase.end(), lowercase.begin(), ::tolower);
370
371
if (lowercase.find(match) != lowercase.npos) {
372
found = true;
373
foundResult = result;
374
break;
375
}
376
}
377
addr = RoundMemAddressUp(chunk_end);
378
} while (!found && addr != end);
379
380
JsonWriter &json = req.Respond();
381
if (found) {
382
json.pushDict("extent");
383
json.writeString("type", TypeFromFlag(foundResult.flags));
384
json.writeUint("address", foundResult.start);
385
json.writeUint("size", foundResult.size);
386
json.writeFloat("ticks", foundResult.ticks);
387
json.writeUint("pc", foundResult.pc);
388
json.writeString("tag", foundResult.tag);
389
json.writeBool("allocated", foundResult.allocated);
390
json.pop();
391
} else {
392
json.writeNull("extent");
393
}
394
}
395
396