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/HLESubscriber.cpp
Views: 1401
1
// Copyright (c) 2018- 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 "Common/StringUtils.h"
19
#include "Core/Config.h"
20
#include "Core/Core.h"
21
#include "Core/Debugger/DisassemblyManager.h"
22
#include "Core/Debugger/SymbolMap.h"
23
#include "Core/Debugger/WebSocket/HLESubscriber.h"
24
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
25
#include "Core/MemMap.h"
26
#include "Core/MIPS/MIPSAnalyst.h"
27
#include "Core/MIPS/MIPSDebugInterface.h"
28
#include "Core/MIPS/MIPSStackWalk.h"
29
#include "Core/HLE/sceKernelThread.h"
30
#include "Core/Reporting.h"
31
32
DebuggerSubscriber *WebSocketHLEInit(DebuggerEventHandlerMap &map) {
33
map["hle.thread.list"] = &WebSocketHLEThreadList;
34
map["hle.thread.wake"] = &WebSocketHLEThreadWake;
35
map["hle.thread.stop"] = &WebSocketHLEThreadStop;
36
map["hle.func.list"] = &WebSocketHLEFuncList;
37
map["hle.func.add"] = &WebSocketHLEFuncAdd;
38
map["hle.func.remove"] = &WebSocketHLEFuncRemove;
39
map["hle.func.removeRange"] = &WebSocketHLEFuncRemoveRange;
40
map["hle.func.rename"] = &WebSocketHLEFuncRename;
41
map["hle.func.scan"] = &WebSocketHLEFuncScan;
42
map["hle.module.list"] = &WebSocketHLEModuleList;
43
map["hle.backtrace"] = &WebSocketHLEBacktrace;
44
45
return nullptr;
46
}
47
48
// List all current HLE threads (hle.thread.list)
49
//
50
// No parameters.
51
//
52
// Response (same event name):
53
// - threads: array of objects, each with properties:
54
// - id: unsigned integer unique id of thread.
55
// - name: name given to thread when created.
56
// - status: numeric status flags of thread.
57
// - statuses: array of string status names, e.g. 'running'. Typically only one set.
58
// - pc: unsigned integer address of next instruction on thread.
59
// - entry: unsigned integer address thread execution started at.
60
// - initialStackSize: unsigned integer, size of initial stack.
61
// - currentStackSize: unsigned integer, size of stack (e.g. if resized.)
62
// - priority: numeric priority level, lower values are better priority.
63
// - waitType: numeric wait type, if the thread is waiting, or 0 if not waiting.
64
// - isCurrent: boolean, true for the currently executing thread.
65
void WebSocketHLEThreadList(DebuggerRequest &req) {
66
// Will just return none of the CPU isn't ready yet.
67
auto threads = GetThreadsInfo();
68
69
JsonWriter &json = req.Respond();
70
json.pushArray("threads");
71
for (const auto &th : threads) {
72
json.pushDict();
73
json.writeUint("id", th.id);
74
json.writeString("name", th.name);
75
json.writeInt("status", th.status);
76
json.pushArray("statuses");
77
if (th.status & THREADSTATUS_RUNNING)
78
json.writeString("running");
79
if (th.status & THREADSTATUS_READY)
80
json.writeString("ready");
81
if (th.status & THREADSTATUS_WAIT)
82
json.writeString("wait");
83
if (th.status & THREADSTATUS_SUSPEND)
84
json.writeString("suspend");
85
if (th.status & THREADSTATUS_DORMANT)
86
json.writeString("dormant");
87
if (th.status & THREADSTATUS_DEAD)
88
json.writeString("dead");
89
json.pop();
90
json.writeUint("pc", th.curPC);
91
json.writeUint("entry", th.entrypoint);
92
json.writeUint("initialStackSize", th.initialStack);
93
json.writeUint("currentStackSize", th.stackSize);
94
json.writeInt("priority", th.priority);
95
json.writeInt("waitType", (int)th.waitType);
96
json.writeBool("isCurrent", th.isCurrent);
97
json.pop();
98
}
99
json.pop();
100
}
101
102
static bool ThreadInfoForStatus(DebuggerRequest &req, DebugThreadInfo *result) {
103
if (!PSP_IsInited()) {
104
req.Fail("CPU not active");
105
return false;
106
}
107
if (!Core_IsStepping()) {
108
req.Fail("CPU currently running (cpu.stepping first)");
109
return false;
110
}
111
112
uint32_t threadID;
113
if (!req.ParamU32("thread", &threadID))
114
return false;
115
116
auto threads = GetThreadsInfo();
117
for (const auto &t : threads) {
118
if (t.id == threadID) {
119
*result = t;
120
return true;
121
}
122
}
123
124
req.Fail("Thread could not be found");
125
return false;
126
}
127
128
// Force resume a thread (hle.thread.wake)
129
//
130
// Parameters:
131
// - thread: number indicating the thread id to resume.
132
//
133
// Response (same event name):
134
// - thread: id repeated back.
135
// - status: string 'ready'.
136
void WebSocketHLEThreadWake(DebuggerRequest &req) {
137
DebugThreadInfo threadInfo{ -1 };
138
if (!ThreadInfoForStatus(req, &threadInfo))
139
return;
140
141
switch (threadInfo.status) {
142
case THREADSTATUS_SUSPEND:
143
case THREADSTATUS_WAIT:
144
case THREADSTATUS_WAITSUSPEND:
145
if (__KernelResumeThreadFromWait(threadInfo.id, 0) != 0)
146
return req.Fail("Failed to resume thread");
147
break;
148
149
default:
150
return req.Fail("Cannot force run thread based on current status");
151
}
152
153
Reporting::NotifyDebugger();
154
155
JsonWriter &json = req.Respond();
156
json.writeUint("thread", threadInfo.id);
157
json.writeString("status", "ready");
158
}
159
160
// Force stop a thread (hle.thread.stop)
161
//
162
// Parameters:
163
// - thread: number indicating the thread id to stop.
164
//
165
// Response (same event name):
166
// - thread: id repeated back.
167
// - status: string 'dormant'.
168
void WebSocketHLEThreadStop(DebuggerRequest &req) {
169
DebugThreadInfo threadInfo{ -1 };
170
if (!ThreadInfoForStatus(req, &threadInfo))
171
return;
172
173
switch (threadInfo.status) {
174
case THREADSTATUS_SUSPEND:
175
case THREADSTATUS_WAIT:
176
case THREADSTATUS_WAITSUSPEND:
177
case THREADSTATUS_READY:
178
__KernelStopThread(threadInfo.id, 0, "stopped from debugger");
179
break;
180
181
default:
182
return req.Fail("Cannot force run thread based on current status");
183
}
184
185
// Get it again to verify.
186
if (!ThreadInfoForStatus(req, &threadInfo))
187
return;
188
if ((threadInfo.status & THREADSTATUS_DORMANT) == 0)
189
return req.Fail("Failed to stop thread");
190
191
Reporting::NotifyDebugger();
192
193
JsonWriter &json = req.Respond();
194
json.writeUint("thread", threadInfo.id);
195
json.writeString("status", "dormant");
196
}
197
198
// List all current known function symbols (hle.func.list)
199
//
200
// No parameters.
201
//
202
// Response (same event name):
203
// - functions: array of objects, each with properties:
204
// - name: current name of function.
205
// - address: unsigned integer start address of function.
206
// - size: unsigned integer size in bytes.
207
void WebSocketHLEFuncList(DebuggerRequest &req) {
208
if (!g_symbolMap)
209
return req.Fail("CPU not active");
210
211
auto functions = g_symbolMap->GetAllSymbols(ST_FUNCTION);
212
213
JsonWriter &json = req.Respond();
214
json.pushArray("functions");
215
for (auto f : functions) {
216
json.pushDict();
217
json.writeString("name", f.name);
218
json.writeUint("address", f.address);
219
json.writeUint("size", f.size);
220
json.pop();
221
}
222
json.pop();
223
}
224
225
// Add a new function symbols (hle.func.add)
226
//
227
// Parameters:
228
// - address: unsigned integer address for the start of the function.
229
// - size: unsigned integer size in bytes, optional. If 'address' is inside a function,
230
// defaults to that function's end, otherwise 4 bytes.
231
// - name: string to name the function, optional and defaults to an auto-generated name.
232
//
233
// Response (same event name):
234
// - address: the start address, repeated back.
235
// - size: the size of the function, whether autodetected or not.
236
// - name: name of the new function.
237
//
238
// Note: will fail if a function starts at that location already, or if size spans multiple
239
// existing functions. Remove those functions first if necessary.
240
void WebSocketHLEFuncAdd(DebuggerRequest &req) {
241
if (!g_symbolMap)
242
return req.Fail("CPU not active");
243
if (!Core_IsStepping())
244
return req.Fail("CPU currently running (cpu.stepping first)");
245
246
u32 addr;
247
if (!req.ParamU32("address", &addr))
248
return;
249
u32 size = -1;
250
if (!req.ParamU32("size", &size, false, DebuggerParamType::OPTIONAL))
251
return;
252
if (size == 0)
253
size = -1;
254
255
std::string name;
256
if (!req.ParamString("name", &name, DebuggerParamType::OPTIONAL))
257
return;
258
if (name.empty())
259
name = StringFromFormat("z_un_%08x", addr);
260
261
u32 prevBegin = g_symbolMap->GetFunctionStart(addr);
262
u32 endBegin = size == -1 ? prevBegin : g_symbolMap->GetFunctionStart(addr + size - 1);
263
if (prevBegin == addr) {
264
return req.Fail("Function already exists at 'address'");
265
} else if (endBegin != prevBegin) {
266
return req.Fail("Function already exists between 'address' and 'address' + 'size'");
267
} else if (prevBegin != -1) {
268
std::string prevName = g_symbolMap->GetLabelString(prevBegin);
269
u32 prevSize = g_symbolMap->GetFunctionSize(prevBegin);
270
u32 newPrevSize = addr - prevBegin;
271
272
// The new function will be the remainder, unless otherwise specified.
273
if (size == -1)
274
size = prevSize - newPrevSize;
275
276
// Make sure we register the new length for replacements too.
277
MIPSAnalyst::ForgetFunctions(prevBegin, prevBegin + newPrevSize - 1);
278
g_symbolMap->SetFunctionSize(prevBegin, newPrevSize);
279
MIPSAnalyst::RegisterFunction(prevBegin, newPrevSize, prevName.c_str());
280
} else {
281
// There was no function there, so hopefully they specified a size.
282
if (size == -1)
283
size = 4;
284
}
285
286
// To ensure we restore replacements.
287
MIPSAnalyst::ForgetFunctions(addr, addr + size - 1);
288
g_symbolMap->AddFunction(name.c_str(), addr, size);
289
g_symbolMap->SortSymbols();
290
MIPSAnalyst::RegisterFunction(addr, size, name.c_str());
291
292
MIPSAnalyst::UpdateHashMap();
293
MIPSAnalyst::ApplyHashMap();
294
295
if (g_Config.bFuncReplacements) {
296
MIPSAnalyst::ReplaceFunctions();
297
}
298
299
// Clear cache for branch lines and such.
300
DisassemblyManager manager;
301
manager.clear();
302
303
JsonWriter &json = req.Respond();
304
json.writeUint("address", addr);
305
json.writeUint("size", size);
306
json.writeString("name", name);
307
}
308
309
// Remove a function symbol (hle.func.remove)
310
//
311
// Parameters:
312
// - address: unsigned integer address within function to remove.
313
//
314
// Response (same event name):
315
// - address: the start address of the removed function.
316
// - size: the size in bytes of the removed function.
317
//
318
// Note: will expand any previous function automatically.
319
void WebSocketHLEFuncRemove(DebuggerRequest &req) {
320
if (!g_symbolMap)
321
return req.Fail("CPU not active");
322
if (!Core_IsStepping())
323
return req.Fail("CPU currently running (cpu.stepping first)");
324
325
u32 addr;
326
if (!req.ParamU32("address", &addr))
327
return;
328
329
u32 funcBegin = g_symbolMap->GetFunctionStart(addr);
330
if (funcBegin == -1)
331
return req.Fail("No function found at 'address'");
332
u32 funcSize = g_symbolMap->GetFunctionSize(funcBegin);
333
334
// Expand the previous function.
335
u32 prevBegin = g_symbolMap->GetFunctionStart(funcBegin - 1);
336
if (prevBegin != -1) {
337
std::string prevName = g_symbolMap->GetLabelString(prevBegin);
338
u32 expandedSize = g_symbolMap->GetFunctionSize(prevBegin) + funcSize;
339
g_symbolMap->SetFunctionSize(prevBegin, expandedSize);
340
MIPSAnalyst::ForgetFunctions(prevBegin, prevBegin + expandedSize - 1);
341
MIPSAnalyst::RegisterFunction(prevBegin, expandedSize, prevName.c_str());
342
} else {
343
MIPSAnalyst::ForgetFunctions(funcBegin, funcBegin + funcSize - 1);
344
}
345
346
g_symbolMap->RemoveFunction(funcBegin, true);
347
g_symbolMap->SortSymbols();
348
349
MIPSAnalyst::UpdateHashMap();
350
MIPSAnalyst::ApplyHashMap();
351
352
if (g_Config.bFuncReplacements) {
353
MIPSAnalyst::ReplaceFunctions();
354
}
355
356
// Clear cache for branch lines and such.
357
DisassemblyManager manager;
358
manager.clear();
359
360
JsonWriter &json = req.Respond();
361
json.writeUint("address", funcBegin);
362
json.writeUint("size", funcSize);
363
}
364
365
// This function removes function symbols that intersect or lie inside the range
366
// (Note: this makes no checks whether the range is valid)
367
// Returns the number of removed functions
368
static u32 RemoveFuncSymbolsInRange(u32 addr, u32 size) {
369
u32 func_address = g_symbolMap->GetFunctionStart(addr);
370
if (func_address == SymbolMap::INVALID_ADDRESS) {
371
func_address = g_symbolMap->GetNextSymbolAddress(addr, SymbolType::ST_FUNCTION);
372
}
373
374
u32 counter = 0;
375
while (func_address < addr + size && func_address != SymbolMap::INVALID_ADDRESS) {
376
g_symbolMap->RemoveFunction(func_address, true);
377
++counter;
378
func_address = g_symbolMap->GetNextSymbolAddress(addr, SymbolType::ST_FUNCTION);
379
}
380
381
if (counter) {
382
MIPSAnalyst::ForgetFunctions(addr, addr + size - 1);
383
384
// The following was copied from hle.func.remove:
385
g_symbolMap->SortSymbols();
386
387
MIPSAnalyst::UpdateHashMap();
388
MIPSAnalyst::ApplyHashMap();
389
390
if (g_Config.bFuncReplacements) {
391
MIPSAnalyst::ReplaceFunctions();
392
}
393
394
// Clear cache for branch lines and such.
395
DisassemblyManager manager;
396
manager.clear();
397
}
398
return counter;
399
}
400
401
// Remove function symbols in range (hle.func.removeRange)
402
//
403
// Parameters:
404
// - address: unsigned integer address for the start of the range.
405
// - size: unsigned integer size in bytes for removal
406
//
407
// Response (same event name):
408
// - count: number of removed functions
409
void WebSocketHLEFuncRemoveRange(DebuggerRequest &req) {
410
if (!g_symbolMap)
411
return req.Fail("CPU not active");
412
if (!Core_IsStepping())
413
return req.Fail("CPU currently running (cpu.stepping first)");
414
415
u32 addr;
416
if (!req.ParamU32("address", &addr))
417
return;
418
u32 size;
419
if (!req.ParamU32("size", &size))
420
return;
421
422
if (!Memory::IsValidRange(addr, size))
423
return req.Fail("Address or size outside valid memory");
424
425
u32 count = RemoveFuncSymbolsInRange(addr, size);
426
427
JsonWriter &json = req.Respond();
428
json.writeUint("count", count);
429
}
430
431
// Rename a function symbol (hle.func.rename)
432
//
433
// Parameters:
434
// - address: unsigned integer address within function to rename.
435
// - name: string, new name for the function.
436
//
437
// Response (same event name):
438
// - address: the start address of the renamed function.
439
// - size: the size in bytes of the renamed function.
440
// - name: string, new name repeated back.
441
void WebSocketHLEFuncRename(DebuggerRequest &req) {
442
if (!g_symbolMap)
443
return req.Fail("CPU not active");
444
if (!Core_IsStepping())
445
return req.Fail("CPU currently running (cpu.stepping first)");
446
447
u32 addr;
448
if (!req.ParamU32("address", &addr))
449
return;
450
std::string name;
451
if (!req.ParamString("name", &name))
452
return;
453
454
u32 funcBegin = g_symbolMap->GetFunctionStart(addr);
455
if (funcBegin == -1)
456
return req.Fail("No function found at 'address'");
457
u32 funcSize = g_symbolMap->GetFunctionSize(funcBegin);
458
459
g_symbolMap->SetLabelName(name.c_str(), funcBegin);
460
// To ensure we reapply replacements (in case we check name there.)
461
MIPSAnalyst::ForgetFunctions(funcBegin, funcBegin + funcSize - 1);
462
MIPSAnalyst::RegisterFunction(funcBegin, funcSize, name.c_str());
463
MIPSAnalyst::UpdateHashMap();
464
MIPSAnalyst::ApplyHashMap();
465
if (g_Config.bFuncReplacements) {
466
MIPSAnalyst::ReplaceFunctions();
467
}
468
469
JsonWriter &json = req.Respond();
470
json.writeUint("address", funcBegin);
471
json.writeUint("size", funcSize);
472
json.writeString("name", name);
473
}
474
475
// Auto-detect functions in a memory range (hle.func.scan)
476
//
477
// Parameters:
478
// - address: unsigned integer address for the start of the range.
479
// - size: unsigned integer size in bytes for scan.
480
// - remove: optional bool indicating whether functions that intersect or inside lie inside the range must be removed before scanning
481
//
482
// Response (same event name) with no extra data.
483
void WebSocketHLEFuncScan(DebuggerRequest &req) {
484
if (!g_symbolMap)
485
return req.Fail("CPU not active");
486
if (!Core_IsStepping())
487
return req.Fail("CPU currently running (cpu.stepping first)");
488
489
u32 addr;
490
if (!req.ParamU32("address", &addr))
491
return;
492
u32 size;
493
if (!req.ParamU32("size", &size))
494
return;
495
496
bool remove = false;
497
if (!req.ParamBool("remove", &remove, DebuggerParamType::OPTIONAL))
498
return;
499
500
if (!Memory::IsValidRange(addr, size))
501
return req.Fail("Address or size outside valid memory");
502
503
if (remove) {
504
RemoveFuncSymbolsInRange(addr, size);
505
}
506
507
bool insertSymbols = MIPSAnalyst::ScanForFunctions(addr, addr + size - 1, true);
508
MIPSAnalyst::FinalizeScan(insertSymbols);
509
510
req.Respond();
511
}
512
513
// List all known user modules (hle.module.list)
514
//
515
// No parameters.
516
//
517
// Response (same event name):
518
// - modules: array of objects, each with properties:
519
// - name: name of module when loaded.
520
// - address: unsigned integer start address.
521
// - size: unsigned integer size in bytes.
522
// - isActive: boolean, true if this module is active.
523
void WebSocketHLEModuleList(DebuggerRequest &req) {
524
if (!g_symbolMap)
525
return req.Fail("CPU not active");
526
527
auto modules = g_symbolMap->getAllModules();
528
529
JsonWriter &json = req.Respond();
530
json.pushArray("modules");
531
for (auto m : modules) {
532
json.pushDict();
533
json.writeString("name", m.name);
534
json.writeUint("address", m.address);
535
json.writeUint("size", m.size);
536
json.writeBool("isActive", m.active);
537
json.pop();
538
}
539
json.pop();
540
}
541
542
// Walk the stack and list stack frames (hle.backtrace)
543
//
544
// Parameters:
545
// - thread: optional number indicating the thread id to backtrace, default current.
546
//
547
// Response (same event name):
548
// - frames: array of objects, each with properties:
549
// - entry: unsigned integer address of function start (may be estimated.)
550
// - pc: unsigned integer next execution address.
551
// - sp: unsigned integer stack address in this func (beware of alloca().)
552
// - stackSize: integer size of stack frame.
553
// - code: string disassembly of pc.
554
void WebSocketHLEBacktrace(DebuggerRequest &req) {
555
if (!g_symbolMap)
556
return req.Fail("CPU not active");
557
if (!Core_IsStepping())
558
return req.Fail("CPU currently running (cpu.stepping first)");
559
560
uint32_t threadID = -1;
561
DebugInterface *cpuDebug = currentDebugMIPS;
562
if (req.HasParam("thread")) {
563
if (!req.ParamU32("thread", &threadID))
564
return;
565
566
cpuDebug = KernelDebugThread((SceUID)threadID);
567
if (!cpuDebug)
568
return req.Fail("Thread could not be found");
569
}
570
571
auto threads = GetThreadsInfo();
572
uint32_t entry = cpuDebug->GetPC();
573
uint32_t stackTop = 0;
574
for (const DebugThreadInfo &th : threads) {
575
if ((threadID == -1 && th.isCurrent) || th.id == threadID) {
576
entry = th.entrypoint;
577
stackTop = th.initialStack;
578
break;
579
}
580
}
581
582
uint32_t ra = cpuDebug->GetRegValue(0, MIPS_REG_RA);
583
uint32_t sp = cpuDebug->GetRegValue(0, MIPS_REG_SP);
584
auto frames = MIPSStackWalk::Walk(cpuDebug->GetPC(), ra, sp, entry, stackTop);
585
586
JsonWriter &json = req.Respond();
587
json.pushArray("frames");
588
for (auto f : frames) {
589
json.pushDict();
590
json.writeUint("entry", f.entry);
591
json.writeUint("pc", f.pc);
592
json.writeUint("sp", f.sp);
593
json.writeUint("stackSize", f.stackSize);
594
595
DisassemblyManager manager;
596
DisassemblyLineInfo line;
597
manager.getLine(manager.getStartAddress(f.pc), true, line, cpuDebug);
598
json.writeString("code", line.name + " " + line.params);
599
600
json.pop();
601
}
602
json.pop();
603
}
604
605