Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/Core.cpp
5654 views
1
// Copyright (c) 2012- 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 "ppsspp_config.h"
19
20
#include <cstdint>
21
#include <mutex>
22
#include <set>
23
#include <condition_variable>
24
25
#include "Common/System/System.h"
26
#include "Common/Profiler/Profiler.h"
27
28
#include "Common/GraphicsContext.h"
29
#include "Common/Thread/ThreadUtil.h"
30
#include "Common/Log.h"
31
#include "Core/Core.h"
32
#include "Core/Config.h"
33
#include "Core/HLE/HLE.h"
34
#include "Core/MIPS/MIPSDebugInterface.h"
35
#include "Core/SaveState.h"
36
#include "Core/System.h"
37
#include "Core/MemFault.h"
38
#include "Core/Debugger/Breakpoints.h"
39
#include "Core/MIPS/MIPS.h"
40
#include "Core/MIPS/MIPSAnalyst.h"
41
#include "Core/HLE/sceNetAdhoc.h"
42
#include "Core/MIPS/MIPSTracer.h"
43
44
#include "GPU/Debugger/Stepping.h"
45
#include "GPU/GPU.h"
46
#include "GPU/GPUCommon.h"
47
48
// Step command to execute next
49
static std::mutex g_stepMutex;
50
51
struct CPUStepCommand {
52
CPUStepType type;
53
int stepSize;
54
BreakReason reason;
55
u32 relatedAddr;
56
bool empty() const {
57
return type == CPUStepType::None;
58
}
59
void clear() {
60
type = CPUStepType::None;
61
stepSize = 0;
62
reason = BreakReason::None;
63
relatedAddr = 0;
64
}
65
};
66
67
static CPUStepCommand g_cpuStepCommand;
68
69
// This is so that external threads can wait for the CPU to become inactive.
70
static std::condition_variable m_InactiveCond;
71
static std::mutex m_hInactiveMutex;
72
73
static int steppingCounter = 0;
74
static std::set<CoreLifecycleFunc> lifecycleFuncs;
75
76
// This can be read and written from ANYWHERE.
77
volatile CoreState coreState = CORE_POWERDOWN;
78
CoreState preGeCoreState = CORE_POWERDOWN;
79
// If true, core state has been changed, but JIT has probably not noticed yet.
80
volatile bool coreStatePending = false;
81
82
static bool powerSaving = false;
83
static bool g_breakAfterFrame = false;
84
static BreakReason g_breakReason = BreakReason::None;
85
86
static MIPSExceptionInfo g_exceptionInfo;
87
88
// This is called on EmuThread before RunLoop.
89
static bool Core_ProcessStepping(MIPSDebugInterface *cpu);
90
91
BreakReason Core_BreakReason() {
92
return g_breakReason;
93
}
94
95
const char *CoreStateToString(CoreState state) {
96
switch (state) {
97
case CORE_RUNNING_CPU: return "RUNNING_CPU";
98
case CORE_NEXTFRAME: return "NEXTFRAME";
99
case CORE_STEPPING_CPU: return "STEPPING_CPU";
100
case CORE_POWERDOWN: return "POWERDOWN";
101
case CORE_RUNTIME_ERROR: return "RUNTIME_ERROR";
102
case CORE_STEPPING_GE: return "STEPPING_GE";
103
case CORE_RUNNING_GE: return "RUNNING_GE";
104
default: return "N/A";
105
}
106
}
107
108
const char *BreakReasonToString(BreakReason reason) {
109
switch (reason) {
110
case BreakReason::None: return "None";
111
case BreakReason::AssertChoice: return "cpu.assert";
112
case BreakReason::DebugBreak: return "cpu.debugbreak";
113
case BreakReason::DebugStep: return "cpu.stepping";
114
case BreakReason::DebugStepInto: return "cpu.stepInto";
115
case BreakReason::UIFocus: return "ui.lost_focus";
116
case BreakReason::AfterFrame: return "frame.after";
117
case BreakReason::MemoryException: return "memory.exception";
118
case BreakReason::CpuException: return "cpu.exception";
119
case BreakReason::BreakInstruction: return "cpu.breakInstruction";
120
case BreakReason::SavestateLoad: return "savestate.load";
121
case BreakReason::SavestateSave: return "savestate.save";
122
case BreakReason::SavestateRewind: return "savestate.rewind";
123
case BreakReason::SavestateCrash: return "savestate.crash";
124
case BreakReason::MemoryBreakpoint: return "memory.breakpoint";
125
case BreakReason::CpuBreakpoint: return "cpu.breakpoint";
126
case BreakReason::MemoryAccess: return "memory.access"; // ???
127
case BreakReason::JitBranchDebug: return "jit.branchdebug";
128
case BreakReason::RABreak: return "ra.break";
129
case BreakReason::BreakOnBoot: return "ui.boot";
130
case BreakReason::AddBreakpoint: return "cpu.breakpoint.add";
131
case BreakReason::FrameAdvance: return "ui.frameAdvance";
132
case BreakReason::UIPause: return "ui.pause";
133
case BreakReason::HLEDebugBreak: return "hle.step";
134
default: return "Unknown";
135
}
136
}
137
138
void Core_SetGraphicsContext(GraphicsContext *ctx) {
139
PSP_CoreParameter().graphicsContext = ctx;
140
}
141
142
void Core_ListenLifecycle(CoreLifecycleFunc func) {
143
lifecycleFuncs.insert(func);
144
}
145
146
void Core_NotifyLifecycle(CoreLifecycle stage) {
147
if (stage == CoreLifecycle::STARTING) {
148
Core_ResetException();
149
}
150
151
for (auto func : lifecycleFuncs) {
152
func(stage);
153
}
154
}
155
156
void Core_Stop() {
157
Core_ResetException();
158
Core_UpdateState(CORE_POWERDOWN);
159
}
160
161
void Core_UpdateState(CoreState newState) {
162
const CoreState state = coreState;
163
if ((state == CORE_RUNNING_CPU || state == CORE_NEXTFRAME) && newState != CORE_RUNNING_CPU)
164
coreStatePending = true;
165
coreState = newState;
166
}
167
168
bool Core_IsStepping() {
169
const CoreState state = coreState;
170
return state == CORE_STEPPING_CPU || state == CORE_STEPPING_GE || state == CORE_POWERDOWN;
171
}
172
173
bool Core_IsActive() {
174
const CoreState state = coreState;
175
return state == CORE_RUNNING_CPU || state == CORE_NEXTFRAME || coreStatePending;
176
}
177
178
bool Core_IsInactive() {
179
const CoreState state = coreState;
180
return state != CORE_RUNNING_CPU && state != CORE_NEXTFRAME && !coreStatePending;
181
}
182
183
void Core_StateProcessed() {
184
if (coreStatePending) {
185
std::lock_guard<std::mutex> guard(m_hInactiveMutex);
186
coreStatePending = false;
187
m_InactiveCond.notify_all();
188
}
189
}
190
191
void Core_WaitInactive() {
192
while (Core_IsActive() && !GPUStepping::IsStepping()) {
193
std::unique_lock<std::mutex> guard(m_hInactiveMutex);
194
m_InactiveCond.wait_for(guard, std::chrono::seconds(1));
195
}
196
}
197
198
void Core_SetPowerSaving(bool mode) {
199
powerSaving = mode;
200
}
201
202
bool Core_GetPowerSaving() {
203
return powerSaving;
204
}
205
206
void Core_RunLoopUntil(u64 globalticks) {
207
while (true) {
208
switch (coreState) {
209
case CORE_POWERDOWN:
210
case CORE_RUNTIME_ERROR:
211
case CORE_NEXTFRAME:
212
return;
213
case CORE_STEPPING_CPU:
214
case CORE_STEPPING_GE:
215
if (Core_ProcessStepping(currentDebugMIPS)) {
216
return;
217
}
218
break;
219
case CORE_RUNNING_CPU:
220
mipsr4k.RunLoopUntil(globalticks);
221
if (g_breakAfterFrame && coreState == CORE_NEXTFRAME) {
222
g_breakAfterFrame = false;
223
g_breakReason = BreakReason::AfterFrame;
224
coreState = CORE_STEPPING_CPU;
225
}
226
break; // Will loop around to go to RUNNING_GE or NEXTFRAME, which will exit.
227
case CORE_RUNNING_GE:
228
switch (gpu->ProcessDLQueue()) {
229
case DLResult::DebugBreak:
230
GPUStepping::EnterStepping(coreState);
231
break;
232
233
case DLResult::Error: // We should elegantly report the error somehow, or I guess ignore it.
234
case DLResult::Done: // Done executing for now
235
hleFinishSyscallAfterGe();
236
coreState = preGeCoreState;
237
break;
238
239
default:
240
// Not a valid return value.
241
_dbg_assert_(false);
242
break;
243
}
244
break;
245
}
246
}
247
}
248
249
// Should only be called from GPUCommon functions (called from sceGe functions).
250
void Core_SwitchToGe() {
251
// TODO: This should be an atomic exchange. Or we add bitflags into coreState.
252
preGeCoreState = coreState;
253
coreState = CORE_RUNNING_GE;
254
}
255
256
bool Core_RequestCPUStep(CPUStepType type, int stepSize) {
257
std::lock_guard<std::mutex> guard(g_stepMutex);
258
if (g_cpuStepCommand.type != CPUStepType::None) {
259
ERROR_LOG(Log::CPU, "Can't submit two steps in one host frame");
260
return false;
261
}
262
// Some step types don't need a size.
263
switch (type) {
264
case CPUStepType::Out:
265
case CPUStepType::Frame:
266
break;
267
default:
268
_dbg_assert_(stepSize != 0);
269
break;
270
}
271
g_cpuStepCommand = { type, stepSize };
272
return true;
273
}
274
275
// Handles more advanced step types (used by the debugger).
276
// stepSize is to support stepping through compound instructions like fused lui+ladd (li).
277
// Yes, our disassembler does support those.
278
// Doesn't return the new address, as that's just mips->getPC().
279
// Internal use.
280
static void Core_PerformCPUStep(MIPSDebugInterface *cpu, CPUStepType stepType, int stepSize) {
281
switch (stepType) {
282
case CPUStepType::Into:
283
{
284
u32 currentPc = cpu->GetPC();
285
u32 newAddress = currentPc + stepSize;
286
// If the current PC is on a breakpoint, the user still wants the step to happen.
287
g_breakpoints.SetSkipFirst(currentPc);
288
for (int i = 0; i < (int)(newAddress - currentPc) / 4; i++) {
289
currentMIPS->SingleStep();
290
}
291
break;
292
}
293
case CPUStepType::Over:
294
{
295
u32 currentPc = cpu->GetPC();
296
u32 breakpointAddress = currentPc + stepSize;
297
298
g_breakpoints.SetSkipFirst(currentPc);
299
MIPSAnalyst::MipsOpcodeInfo info = MIPSAnalyst::GetOpcodeInfo(cpu, cpu->GetPC());
300
301
// TODO: Doing a step over in a delay slot is a bit .. unclear. Maybe just do a single step.
302
303
if (info.isBranch) {
304
if (info.isConditional == false) {
305
if (info.isLinkedBranch) { // jal, jalr
306
// it's a function call with a delay slot - skip that too
307
breakpointAddress += cpu->getInstructionSize(0);
308
} else { // j, ...
309
// in case of absolute branches, set the breakpoint at the branch target
310
breakpointAddress = info.branchTarget;
311
}
312
} else { // beq, ...
313
if (info.conditionMet) {
314
breakpointAddress = info.branchTarget;
315
} else {
316
breakpointAddress = currentPc + 2 * cpu->getInstructionSize(0);
317
}
318
}
319
g_breakpoints.AddBreakPoint(breakpointAddress, true);
320
Core_Resume();
321
} else {
322
// If not a branch, just do a simple single-step, no point in involving the breakpoint machinery.
323
for (int i = 0; i < (int)(breakpointAddress - currentPc) / 4; i++) {
324
currentMIPS->SingleStep();
325
}
326
}
327
break;
328
}
329
case CPUStepType::Out:
330
{
331
u32 entry = cpu->GetPC();
332
u32 stackTop = 0;
333
334
auto threads = GetThreadsInfo();
335
for (size_t i = 0; i < threads.size(); i++) {
336
if (threads[i].isCurrent) {
337
entry = threads[i].entrypoint;
338
stackTop = threads[i].initialStack;
339
break;
340
}
341
}
342
343
auto frames = MIPSStackWalk::Walk(cpu->GetPC(), cpu->GetRegValue(0, 31), cpu->GetRegValue(0, 29), entry, stackTop);
344
if (frames.size() < 2) {
345
// Failure. PC not moving.
346
return;
347
}
348
349
u32 breakpointAddress = frames[1].pc;
350
351
g_breakpoints.AddBreakPoint(breakpointAddress, true);
352
Core_Resume();
353
break;
354
}
355
case CPUStepType::Frame:
356
{
357
g_breakAfterFrame = true;
358
Core_Resume();
359
break;
360
}
361
default:
362
// Not yet implemented
363
break;
364
}
365
}
366
367
static bool Core_ProcessStepping(MIPSDebugInterface *cpu) {
368
Core_StateProcessed();
369
370
// Check if there's any pending save state actions.
371
SaveState::Process();
372
373
switch (coreState) {
374
case CORE_STEPPING_CPU:
375
case CORE_STEPPING_GE:
376
case CORE_RUNNING_GE:
377
// All good
378
break;
379
default:
380
// Nothing to do.
381
return true;
382
}
383
384
// Or any GPU actions.
385
// Legacy stepping code.
386
GPUStepping::ProcessStepping();
387
388
if (coreState == CORE_RUNNING_GE) {
389
// Retry, to get it done this frame.
390
return false;
391
}
392
393
// We're not inside jit now, so it's safe to clear the breakpoints.
394
static int lastSteppingCounter = -1;
395
if (lastSteppingCounter != steppingCounter) {
396
g_breakpoints.ClearTemporaryBreakPoints();
397
System_Notify(SystemNotification::DISASSEMBLY_AFTERSTEP);
398
System_Notify(SystemNotification::MEM_VIEW);
399
lastSteppingCounter = steppingCounter;
400
}
401
402
// Need to check inside the lock to avoid races.
403
std::lock_guard<std::mutex> guard(g_stepMutex);
404
405
if (coreState != CORE_STEPPING_CPU || g_cpuStepCommand.empty()) {
406
return true;
407
}
408
409
Core_ResetException();
410
411
if (!g_cpuStepCommand.empty()) {
412
Core_PerformCPUStep(cpu, g_cpuStepCommand.type, g_cpuStepCommand.stepSize);
413
if (g_cpuStepCommand.type == CPUStepType::Into) {
414
// We're already done. The other step types will resume the CPU.
415
System_Notify(SystemNotification::DISASSEMBLY_AFTERSTEP);
416
}
417
g_cpuStepCommand.clear();
418
steppingCounter++;
419
}
420
421
// Update disasm dialog.
422
System_Notify(SystemNotification::MEM_VIEW);
423
return true;
424
}
425
426
// Free-threaded (hm, possibly except tracing).
427
void Core_Break(BreakReason reason, u32 relatedAddress) {
428
const CoreState state = coreState;
429
if (state != CORE_RUNNING_CPU) {
430
if (state == CORE_STEPPING_CPU) {
431
// Already stepping.
432
INFO_LOG(Log::CPU, "Core_Break(%s), already in break mode", BreakReasonToString(reason));
433
return;
434
}
435
WARN_LOG(Log::CPU, "Core_Break(%s) only works in the CORE_RUNNING_CPU state (was in state %s)", BreakReasonToString(reason), CoreStateToString(state));
436
return;
437
}
438
439
{
440
std::lock_guard<std::mutex> lock(g_stepMutex);
441
if (!g_cpuStepCommand.empty() && Core_IsStepping()) {
442
// If we're in a failed step that uses a temp breakpoint, we need to be able to override it here.
443
switch (g_cpuStepCommand.type) {
444
case CPUStepType::Over:
445
case CPUStepType::Out:
446
// Allow overwriting the command.
447
break;
448
default:
449
ERROR_LOG(Log::CPU, "Core_Break(%s) called with a step-command already in progress", BreakReasonToString(g_cpuStepCommand.reason));
450
return;
451
}
452
}
453
454
// Stop the tracer
455
mipsTracer.stop_tracing();
456
457
g_breakReason = reason;
458
g_cpuStepCommand.type = CPUStepType::None;
459
g_cpuStepCommand.reason = reason;
460
g_cpuStepCommand.relatedAddr = relatedAddress;
461
steppingCounter++;
462
_assert_msg_(reason != BreakReason::None, "No reason specified for break");
463
Core_UpdateState(CORE_STEPPING_CPU);
464
}
465
System_Notify(SystemNotification::DEBUG_MODE_CHANGE);
466
}
467
468
// Free-threaded (or at least should be)
469
void Core_Resume() {
470
// If the current PC is on a breakpoint, the user doesn't want to do nothing.
471
if (currentMIPS) {
472
g_breakpoints.SetSkipFirst(currentMIPS->pc);
473
}
474
475
// Handle resuming from GE.
476
if (coreState == CORE_STEPPING_GE) {
477
coreState = CORE_RUNNING_GE;
478
return;
479
}
480
481
// Clear the exception if we resume.
482
Core_ResetException();
483
coreState = CORE_RUNNING_CPU;
484
g_breakReason = BreakReason::None;
485
System_Notify(SystemNotification::DEBUG_MODE_CHANGE);
486
}
487
488
// Should be called from the EmuThread.
489
bool Core_NextFrame() {
490
CoreState coreState = ::coreState;
491
492
_dbg_assert_(coreState != CORE_STEPPING_GE && coreState != CORE_RUNNING_GE);
493
494
if (coreState == CORE_RUNNING_CPU) {
495
::coreState = CORE_NEXTFRAME;
496
return true;
497
} else if (coreState == CORE_STEPPING_CPU) {
498
// All good, just stepping through so no need to switch to the NextFrame coreState though, that'd
499
// just lose our stepping state.
500
INFO_LOG(Log::System, "Reached end-of-frame while stepping the CPU (this is ok)");
501
return true;
502
} else {
503
ERROR_LOG(Log::System, "Core_NextFrame called with wrong core state %s", CoreStateToString(coreState));
504
return false;
505
}
506
}
507
508
int Core_GetSteppingCounter() {
509
return steppingCounter;
510
}
511
512
SteppingReason Core_GetSteppingReason() {
513
SteppingReason r;
514
std::lock_guard<std::mutex> lock(g_stepMutex);
515
if (!g_cpuStepCommand.empty()) {
516
r.reason = g_cpuStepCommand.reason;
517
r.relatedAddress = g_cpuStepCommand.relatedAddr;
518
}
519
return r;
520
}
521
522
const char *ExceptionTypeAsString(MIPSExceptionType type) {
523
switch (type) {
524
case MIPSExceptionType::MEMORY: return "Invalid Memory Access";
525
case MIPSExceptionType::BREAK: return "Break";
526
case MIPSExceptionType::BAD_EXEC_ADDR: return "Bad Execution Address";
527
default: return "N/A";
528
}
529
}
530
531
const char *MemoryExceptionTypeAsString(MemoryExceptionType type) {
532
switch (type) {
533
case MemoryExceptionType::UNKNOWN: return "Unknown";
534
case MemoryExceptionType::READ_WORD: return "Read Word";
535
case MemoryExceptionType::WRITE_WORD: return "Write Word";
536
case MemoryExceptionType::READ_BLOCK: return "Read Block";
537
case MemoryExceptionType::WRITE_BLOCK: return "Read/Write Block";
538
case MemoryExceptionType::ALIGNMENT: return "Alignment";
539
default:
540
return "N/A";
541
}
542
}
543
544
const char *ExecExceptionTypeAsString(ExecExceptionType type) {
545
switch (type) {
546
case ExecExceptionType::JUMP: return "CPU Jump";
547
case ExecExceptionType::THREAD: return "Thread switch";
548
default:
549
return "N/A";
550
}
551
}
552
553
void Core_MemoryException(u32 address, u32 accessSize, u32 pc, MemoryExceptionType type) {
554
const char *desc = MemoryExceptionTypeAsString(type);
555
// In jit, we only flush PC when bIgnoreBadMemAccess is off.
556
if ((g_Config.iCpuCore == (int)CPUCore::JIT || g_Config.iCpuCore == (int)CPUCore::JIT_IR) && g_Config.bIgnoreBadMemAccess) {
557
WARN_LOG(Log::MemMap, "%s: Invalid access at %08x (size %08x)", desc, address, accessSize);
558
} else {
559
WARN_LOG(Log::MemMap, "%s: Invalid access at %08x (size %08x) PC %08x LR %08x", desc, address, accessSize, currentMIPS->pc, currentMIPS->r[MIPS_REG_RA]);
560
}
561
562
if (!g_Config.bIgnoreBadMemAccess) {
563
// Try to fetch a call stack, to start with.
564
std::vector<MIPSStackWalk::StackFrame> stackFrames = WalkCurrentStack(-1);
565
std::string stackTrace = FormatStackTrace(stackFrames);
566
WARN_LOG(Log::MemMap, "\n%s", stackTrace.c_str());
567
568
MIPSExceptionInfo &e = g_exceptionInfo;
569
e = {};
570
e.type = MIPSExceptionType::MEMORY;
571
e.info.clear();
572
e.memory_type = type;
573
e.address = address;
574
e.accessSize = accessSize;
575
e.stackTrace = stackTrace;
576
e.pc = pc;
577
Core_Break(BreakReason::MemoryException, address);
578
}
579
}
580
581
void Core_MemoryExceptionInfo(u32 address, u32 accessSize, u32 pc, MemoryExceptionType type, std::string_view additionalInfo, bool forceReport) {
582
const char *desc = MemoryExceptionTypeAsString(type);
583
// In jit, we only flush PC when bIgnoreBadMemAccess is off.
584
if ((g_Config.iCpuCore == (int)CPUCore::JIT || g_Config.iCpuCore == (int)CPUCore::JIT_IR) && g_Config.bIgnoreBadMemAccess) {
585
WARN_LOG(Log::MemMap, "%s: Invalid access at %08x (size %08x). %.*s", desc, address, accessSize, (int)additionalInfo.length(), additionalInfo.data());
586
} else {
587
WARN_LOG(Log::MemMap, "%s: Invalid access at %08x (size %08x) PC %08x LR %08x %.*s", desc, address, accessSize, currentMIPS->pc, currentMIPS->r[MIPS_REG_RA], (int)additionalInfo.length(), additionalInfo.data());
588
}
589
590
if (!g_Config.bIgnoreBadMemAccess || forceReport) {
591
// Try to fetch a call stack, to start with.
592
std::vector<MIPSStackWalk::StackFrame> stackFrames = WalkCurrentStack(-1);
593
std::string stackTrace = FormatStackTrace(stackFrames);
594
WARN_LOG(Log::MemMap, "\n%s", stackTrace.c_str());
595
596
MIPSExceptionInfo &e = g_exceptionInfo;
597
e = {};
598
e.type = MIPSExceptionType::MEMORY;
599
e.info = additionalInfo;
600
e.memory_type = type;
601
e.address = address;
602
e.accessSize = accessSize;
603
e.stackTrace = stackTrace;
604
e.pc = pc;
605
Core_Break(BreakReason::MemoryException, address);
606
}
607
}
608
609
// Can't be ignored
610
void Core_ExecException(u32 address, u32 pc, ExecExceptionType type) {
611
const char *desc = ExecExceptionTypeAsString(type);
612
WARN_LOG(Log::MemMap, "%s: Invalid exec address %08x pc=%08x ra=%08x", desc, address, pc, currentMIPS->r[MIPS_REG_RA]);
613
614
MIPSExceptionInfo &e = g_exceptionInfo;
615
e = {};
616
e.type = MIPSExceptionType::BAD_EXEC_ADDR;
617
e.info.clear();
618
e.exec_type = type;
619
e.address = address;
620
e.accessSize = 4; // size of an instruction
621
e.pc = pc;
622
// This just records the closest value that could be useful as reference.
623
e.ra = currentMIPS->r[MIPS_REG_RA];
624
Core_Break(BreakReason::CpuException, address);
625
}
626
627
void Core_BreakException(u32 pc) {
628
ERROR_LOG(Log::CPU, "BREAK!");
629
630
MIPSExceptionInfo &e = g_exceptionInfo;
631
e = {};
632
e.type = MIPSExceptionType::BREAK;
633
e.info.clear();
634
e.pc = pc;
635
636
if (!g_Config.bIgnoreBadMemAccess) {
637
Core_Break(BreakReason::BreakInstruction, currentMIPS->pc);
638
}
639
}
640
641
void Core_ResetException() {
642
g_exceptionInfo.type = MIPSExceptionType::NONE;
643
}
644
645
const MIPSExceptionInfo &Core_GetExceptionInfo() {
646
return g_exceptionInfo;
647
}
648
649