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