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