Skip to content

Commit c270ca5

Browse files
committed
Adds a crash handler to psyqo.
1 parent 8cc3aa2 commit c270ca5

4 files changed

Lines changed: 283 additions & 2 deletions

File tree

src/mips/psyqo/kernel.hh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,24 @@ void takeOverKernel();
159159
*/
160160
bool isKernelTakenOver();
161161

162+
/**
163+
* @brief Installs a crash handler for the application.
164+
*
165+
* @details This function installs a crash handler for the application.
166+
* The crash handler will be called when the application crashes, such
167+
* when an unhandled exception occurs. It will display a message on the screen
168+
* with the crash information, including the exception type, the exception
169+
* address, and the value of all the registers at the time of the crash.
170+
* The crash handler requires the system font to be uploaded to VRAM, at
171+
* the default location (960, 464). If the system font is not available,
172+
* the crash handler will not be able to display the message properly.
173+
*
174+
* As usual, this function should be called from `main`, before handing
175+
* over control to the application, it should only be called once, and
176+
* its associated cost will only be added to the binary if it is called.
177+
*/
178+
void installCrashHandler();
179+
162180
/**
163181
* @brief Queues an IRQ handler to be called from the exception handler.
164182
*
@@ -292,6 +310,7 @@ void prepare(Application&);
292310
void addInitializer(eastl::function<void(Application&)>&& lambda);
293311
void addOnFrame(eastl::function<void()>&& lambda);
294312
void beginFrame();
313+
[[noreturn]] void crashHandler(uint32_t exceptionCode, uint32_t* kernelRegisters);
295314
} // namespace Internal
296315

297316
/**
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
/*
2+
3+
MIT License
4+
5+
Copyright (c) 2025 PCSX-Redux authors
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy
8+
of this software and associated documentation files (the "Software"), to deal
9+
in the Software without restriction, including without limitation the rights
10+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
copies of the Software, and to permit persons to whom the Software is
12+
furnished to do so, subject to the following conditions:
13+
14+
The above copyright notice and this permission notice shall be included in all
15+
copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23+
SOFTWARE.
24+
25+
*/
26+
27+
#include <stdint.h>
28+
29+
#include "common/hardware/gpu.h"
30+
#include "common/hardware/irq.h"
31+
#include "common/kernel/threads.h"
32+
#include "psyqo/kernel.hh"
33+
#include "psyqo/primitives/control.hh"
34+
#include "psyqo/primitives/misc.hh"
35+
#include "psyqo/primitives/sprites.hh"
36+
#include "psyqo/xprintf.h"
37+
38+
static const char *const s_exceptionNames[] = {
39+
"Interrupt",
40+
"TLB Mod",
41+
"TLB Load",
42+
"TLB Store",
43+
"Address Load",
44+
"Address Store",
45+
"Bus Error Load",
46+
"Bus Error Store",
47+
"Syscall",
48+
"Breakpoint",
49+
"Reserved Instruction",
50+
"Coprocessor Unusable",
51+
"Arithmetic Overflow",
52+
"Trap",
53+
"Floating Point Exception",
54+
"Watchpoint",
55+
};
56+
57+
namespace {
58+
59+
template <typename Prim>
60+
void sendPrimitive(const Prim &primitive) {
61+
waitGPU();
62+
const uint32_t *ptr = reinterpret_cast<const uint32_t *>(&primitive);
63+
constexpr size_t size = sizeof(Prim) / sizeof(uint32_t);
64+
for (int i = 0; i < size; i++) {
65+
GPU_DATA = *ptr++;
66+
}
67+
}
68+
69+
} // namespace
70+
71+
struct RegisterInfo {
72+
const char *name;
73+
uint32_t offsetCustom;
74+
uint32_t offsetDefault;
75+
};
76+
77+
static void printString(const char *str, psyqo::Prim::Sprite &sprite, const uint8_t baseV, psyqo::Vertex &location) {
78+
for (const char *p = str; *p != '\0'; p++) {
79+
char c = *p;
80+
if (c < 32 || c > 127) {
81+
c = '?';
82+
}
83+
if (c == ' ') {
84+
location.x += 8;
85+
continue;
86+
}
87+
if (c <= '?') {
88+
sprite.texInfo.u = (c - ' ') * 8;
89+
sprite.texInfo.v = baseV;
90+
} else if (c <= '_') {
91+
sprite.texInfo.u = (c - '@') * 8;
92+
sprite.texInfo.v = baseV + 16;
93+
} else {
94+
sprite.texInfo.u = (c - '`') * 8;
95+
sprite.texInfo.v = baseV + 32;
96+
}
97+
sprite.position = location;
98+
sendPrimitive(sprite);
99+
location.x += 8;
100+
}
101+
}
102+
103+
static const RegisterInfo s_registers[] = {
104+
{"at", 0x100, 1}, {"v0", 0x104, 2}, {"v1", 0x108, 3}, {"a0", 0x10c, 4}, {"a1", 0x110, 5},
105+
{"a2", 0x114, 6}, {"a3", 0x118, 7}, {"t0", 0x11c, 8}, {"t1", 0x120, 9}, {"t2", 0x124, 10},
106+
{"t3", 0x128, 11}, {"t4", 0x12c, 12}, {"t5", 0x130, 13}, {"t6", 0x134, 14}, {"t7", 0x138, 15},
107+
{"t8", 0x140, 24}, {"t9", 0x144, 25}, {"sp", 0x148, 29}, {"ra", 0x14c, 31}, {"gp", 0x150, 28},
108+
{"s0", 0x154, 16}, {"s1", 0x158, 17}, {"s2", 0x15c, 18}, {"s3", 0x160, 19}, {"s4", 0x164, 20},
109+
{"s5", 0x168, 21}, {"s6", 0x16c, 22}, {"s7", 0x170, 23}, {"fp", 0x174, 30}};
110+
111+
static void printRegister(unsigned number, psyqo::Prim::Sprite &sprite, const uint8_t baseV, psyqo::Vertex &location,
112+
uint32_t *kernelRegisters) {
113+
const RegisterInfo &info = s_registers[number];
114+
char buffer[32];
115+
auto x = location.x;
116+
uint32_t value;
117+
if (kernelRegisters) {
118+
value = kernelRegisters[info.offsetDefault];
119+
} else {
120+
value = *(uint32_t *)(info.offsetCustom);
121+
}
122+
int len = snprintf(buffer, sizeof(buffer), "%s: 0x%08x", info.name, value);
123+
printString(buffer, sprite, baseV, location);
124+
if (number & 1) {
125+
location.x = x;
126+
location.y += 16;
127+
} else {
128+
location.x = x + 128;
129+
}
130+
}
131+
132+
static inline uint32_t getCop0BadVAddr() {
133+
uint32_t r;
134+
asm("mfc0 %0, $8 ; nop" : "=r"(r));
135+
return r;
136+
}
137+
138+
static inline uint32_t getCop0EPC() {
139+
uint32_t r;
140+
asm("mfc0 %0, $14 ; nop" : "=r"(r));
141+
return r;
142+
}
143+
144+
[[noreturn]] void psyqo::Kernel::Internal::crashHandler(uint32_t exceptionCode, uint32_t *kernelRegisters) {
145+
IMASK = 0;
146+
IREG = 0;
147+
const bool isPAL = (*((char *)0xbfc7ff52) == 'E');
148+
GPU_STATUS = 0x00000000; // reset GPU
149+
DisplayModeConfig config = {
150+
.hResolution = HR_640,
151+
.vResolution = VR_480,
152+
.videoMode = isPAL ? VM_PAL : VM_NTSC,
153+
.colorDepth = CD_15BITS,
154+
.videoInterlace = VI_ON,
155+
.hResolutionExtended = HRE_NORMAL,
156+
};
157+
setDisplayMode(&config);
158+
setHorizontalRange(0, 0xa00);
159+
setVerticalRange(16, 255);
160+
setDisplayArea(0, 2);
161+
setDrawingArea(0, 0, 640, 480);
162+
setDrawingOffset(0, 0);
163+
164+
const Vertex location = {{.x = 960, .y = 464}};
165+
Prim::VRAMUpload vramUpload;
166+
vramUpload.region.pos = location;
167+
vramUpload.region.size = {{.w = 2, .h = 1}};
168+
sendPrimitive(vramUpload);
169+
GPU_DATA = 0x7fff0000;
170+
Prim::FlushCache flushCache;
171+
sendPrimitive(flushCache);
172+
Prim::TPage tpage;
173+
tpage.attr.setPageX(location.x >> 6)
174+
.setPageY(location.y >> 8)
175+
.set(Prim::TPageAttr::Tex4Bits)
176+
.setDithering(false)
177+
.enableDisplayArea();
178+
sendPrimitive(tpage);
179+
Prim::Sprite s;
180+
s.setColor({{.r = 0x80, .g = 0x80, .b = 0x80}});
181+
s.size = {{.w = 8, .h = 16}};
182+
s.texInfo.clut = location;
183+
const uint8_t baseV = location.y & 0xff;
184+
enableDisplay();
185+
186+
IMASK = IRQ_VBLANK;
187+
while (true) {
188+
while ((IREG & IRQ_VBLANK) == 0);
189+
IREG &= ~IRQ_VBLANK;
190+
FastFill ff = {
191+
.c = {0, 0, 0},
192+
.x = 0,
193+
.y = 0,
194+
.w = 640,
195+
.h = 480,
196+
};
197+
fastFill(&ff);
198+
Vertex p = {{.x = 16, .y = 16}};
199+
printString("Crash handler: ", s, baseV, p);
200+
printString(s_exceptionNames[exceptionCode], s, baseV, p);
201+
p.x = 16;
202+
p.y += 32;
203+
for (unsigned i = 0; i < 29; i++) {
204+
if ((i & 1) == 0) {
205+
p.x = 16;
206+
}
207+
printRegister(i, s, baseV, p, kernelRegisters);
208+
}
209+
210+
p.x = 300;
211+
p.y = 48;
212+
213+
uint32_t badVAddr = getCop0BadVAddr();
214+
uint32_t epc = getCop0EPC();
215+
char buffer[32];
216+
snprintf(buffer, sizeof(buffer), "BadVAddr: 0x%08x", badVAddr);
217+
printString(buffer, s, baseV, p);
218+
p.x = 300;
219+
p.y += 16;
220+
snprintf(buffer, sizeof(buffer), "EPC: 0x%08x", epc);
221+
printString(buffer, s, baseV, p);
222+
}
223+
__builtin_unreachable();
224+
}

src/mips/psyqo/src/kernel.cpp

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ void psyqoBreakHandler(uint32_t code) {
130130
psyqo::Kernel::abort("Unhandled break");
131131
}
132132
void psyqoAssemblyExceptionHandler();
133+
extern uint32_t psyqoExceptionHandlerStop[];
134+
135+
void (*s_crashHandlerPtr)(uint32_t, uint32_t*) = nullptr;
133136
}
134137

135138
void psyqo::Kernel::takeOverKernel() {
@@ -165,6 +168,19 @@ void psyqo::Kernel::takeOverKernel() {
165168
});
166169
}
167170

171+
void psyqo::Kernel::installCrashHandler() {
172+
if (s_tookOverKernel) {
173+
uintptr_t crashHandlerAddr = reinterpret_cast<uintptr_t>(Internal::crashHandler);
174+
uint16_t hi = crashHandlerAddr >> 16;
175+
uint16_t lo = crashHandlerAddr & 0xffff;
176+
psyqoExceptionHandlerStop[0] = Mips::Encoder::lui(Mips::Encoder::Reg::V1, hi);
177+
psyqoExceptionHandlerStop[1] = Mips::Encoder::ori(Mips::Encoder::Reg::V1, lo);
178+
flushCache();
179+
} else {
180+
s_crashHandlerPtr = Internal::crashHandler;
181+
}
182+
}
183+
168184
void psyqo::Kernel::queueIRQHandler(IRQ irq, eastl::function<void()>&& lambda) {
169185
auto& handlers = *s_irqHandlers;
170186
size_t index = static_cast<size_t>(irq);
@@ -337,7 +353,10 @@ void psyqo::Kernel::Internal::prepare(Application& application) {
337353
Process* processes = *reinterpret_cast<Process**>(0x108);
338354
Thread* currentThread = processes[0].thread;
339355
unsigned exCode = currentThread->registers.Cause & 0x3c;
340-
if (exCode != 0x24) return;
356+
if (exCode != 0x24) {
357+
if (s_crashHandlerPtr) (*s_crashHandlerPtr)(exCode >> 2, &currentThread->registers.GPR.r[0]);
358+
return;
359+
}
341360
unsigned code = *reinterpret_cast<uint32_t*>(currentThread->registers.returnPC) >> 6;
342361
if (handleBreak(code)) {
343362
currentThread->registers.returnPC += 4;

src/mips/psyqo/src/vector.s

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ SOFTWARE.
3333
.global psyqoExceptionHandler
3434
.global psyqoBreakHandler
3535
.global psyqoExceptionHandlerAdjustFrameCount
36+
.global psyqoExceptionHandlerStop
3637
.type psyqoAssemblyExceptionHandler, @function
3738

3839
/*
@@ -61,7 +62,7 @@ psyqoAssemblyExceptionHandler:
6162
andi $a0, 0x3c /* Test for what kind of exception */
6263
beq $a0, $at, .Lbreak /* (a) */
6364
li $at, 0x4a /* Prepare for cop2 test in (b) */
64-
.Lstop: /* Beyond break, psyqo will only support IRQs, aka 0 */
65+
/* Beyond break, psyqo will only support IRQs, aka 0 */
6566
bnez $a0, .Lstop /* Anything else and we just stop - $a0 available again */
6667
srl $v1, 24 /* | (b) */
6768
andi $v1, 0xfe /* |_ Test if we were in a cop2 operation */
@@ -115,6 +116,7 @@ psyqoExceptionHandlerAdjustFrameCount:
115116
sw $ra, 0x14c($0)
116117

117118
/* Call the C++ exception or break handler while adjusting the stack */
119+
li $a1, 0
118120
jalr $v1
119121
li $sp, 0x1000 - 16
120122

@@ -140,3 +142,20 @@ psyqoExceptionHandlerAdjustFrameCount:
140142
lw $ra, 0x14c($0)
141143
jr $k1
142144
rfe
145+
146+
psyqoExceptionHandlerStop:
147+
.Lstop:
148+
b .Lstop /* Infinite loop to stop execution */
149+
nop /* Replaced with self-modifying code when adding crash screen */
150+
sw $gp, 0x150($0)
151+
sw $s0, 0x154($0)
152+
sw $s1, 0x158($0)
153+
sw $s2, 0x15c($0)
154+
sw $s3, 0x160($0)
155+
sw $s4, 0x164($0)
156+
sw $s5, 0x168($0)
157+
sw $s6, 0x16c($0)
158+
sw $s7, 0x170($0)
159+
sw $fp, 0x174($0)
160+
b .LcallCPlusPlus
161+
srl $a0, $a0, 2

0 commit comments

Comments
 (0)