Skip to content

Commit 6e74436

Browse files
authored
Merge pull request #1914 from nicolasnoble/crash-handler
Adds a crash handler to psyqo.
2 parents 8cc3aa2 + 5fa392a commit 6e74436

File tree

4 files changed

+318
-12
lines changed

4 files changed

+318
-12
lines changed

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
/**

src/mips/psyqo/src/crash-handler.cpp

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
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+
{"r0", 0, 0}, {"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}, {"t3", 0x128, 11},
106+
{"t4", 0x12c, 12}, {"t5", 0x130, 13}, {"t6", 0x134, 14}, {"t7", 0x138, 15}, {"s0", 0x154, 16}, {"s1", 0x158, 17},
107+
{"s2", 0x15c, 18}, {"s3", 0x160, 19}, {"s4", 0x164, 20}, {"s5", 0x168, 21}, {"s6", 0x16c, 22}, {"s7", 0x170, 23},
108+
{"t8", 0x140, 24}, {"t9", 0x144, 25}, {"gp", 0x150, 28}, {"sp", 0x148, 29}, {"fp", 0x174, 30}, {"ra", 0x14c, 31}};
109+
110+
static void printRegister(unsigned number, psyqo::Prim::Sprite &sprite, const uint8_t baseV, psyqo::Vertex &location,
111+
uint32_t *kernelRegisters) {
112+
const RegisterInfo &info = s_registers[number];
113+
char buffer[32];
114+
auto x = location.x;
115+
uint32_t value = 0;
116+
if (number != 0) {
117+
if (kernelRegisters) {
118+
value = kernelRegisters[info.offsetDefault];
119+
} else {
120+
value = *(uint32_t *)(info.offsetCustom);
121+
}
122+
}
123+
int len = snprintf(buffer, sizeof(buffer), "%s: 0x%08x", info.name, value);
124+
printString(buffer, sprite, baseV, location);
125+
if (number & 1) {
126+
location.x = x;
127+
location.y += 16;
128+
} else {
129+
location.x = x + 128;
130+
}
131+
}
132+
133+
static inline uint32_t getCop0BadVAddr() {
134+
uint32_t r;
135+
asm("mfc0 %0, $8 ; nop" : "=r"(r));
136+
return r;
137+
}
138+
139+
static inline uint32_t getCop0EPC() {
140+
uint32_t r;
141+
asm("mfc0 %0, $14 ; nop" : "=r"(r));
142+
return r;
143+
}
144+
145+
[[noreturn]] void psyqo::Kernel::Internal::crashHandler(uint32_t exceptionCode, uint32_t *kernelRegisters) {
146+
IMASK = 0;
147+
IREG = 0;
148+
const bool isPAL = (*((char *)0xbfc7ff52) == 'E');
149+
GPU_STATUS = 0x00000000; // reset GPU
150+
DisplayModeConfig config = {
151+
.hResolution = HR_640,
152+
.vResolution = VR_480,
153+
.videoMode = isPAL ? VM_PAL : VM_NTSC,
154+
.colorDepth = CD_15BITS,
155+
.videoInterlace = VI_ON,
156+
.hResolutionExtended = HRE_NORMAL,
157+
};
158+
setDisplayMode(&config);
159+
setHorizontalRange(0, 0xa00);
160+
setVerticalRange(16, 255);
161+
setDisplayArea(0, 2);
162+
setDrawingArea(0, 0, 640, 480);
163+
setDrawingOffset(0, 0);
164+
165+
const Vertex location = {{.x = 960, .y = 464}};
166+
Prim::VRAMUpload vramUpload;
167+
vramUpload.region.pos = location;
168+
vramUpload.region.size = {{.w = 2, .h = 1}};
169+
sendPrimitive(vramUpload);
170+
GPU_DATA = 0x7fff0000;
171+
Prim::FlushCache flushCache;
172+
sendPrimitive(flushCache);
173+
Prim::TPage tpage;
174+
tpage.attr.setPageX(location.x >> 6)
175+
.setPageY(location.y >> 8)
176+
.set(Prim::TPageAttr::Tex4Bits)
177+
.setDithering(false)
178+
.enableDisplayArea();
179+
sendPrimitive(tpage);
180+
Prim::Sprite s;
181+
s.setColor({{.r = 0x80, .g = 0x80, .b = 0x80}});
182+
s.size = {{.w = 8, .h = 16}};
183+
s.texInfo.clut = location;
184+
const uint8_t baseV = location.y & 0xff;
185+
enableDisplay();
186+
187+
IMASK = IRQ_VBLANK;
188+
while (true) {
189+
while ((IREG & IRQ_VBLANK) == 0);
190+
IREG &= ~IRQ_VBLANK;
191+
FastFill ff = {
192+
.c = {0, 0, 0},
193+
.x = 0,
194+
.y = 0,
195+
.w = 640,
196+
.h = 480,
197+
};
198+
fastFill(&ff);
199+
Vertex p = {{.x = 16, .y = 16}};
200+
printString("Crash handler: ", s, baseV, p);
201+
printString(s_exceptionNames[exceptionCode], s, baseV, p);
202+
p.x = 16;
203+
p.y += 32;
204+
for (unsigned i = 0; i < 30; i++) {
205+
if ((i & 1) == 0) {
206+
p.x = 16;
207+
}
208+
printRegister(i, s, baseV, p, kernelRegisters);
209+
}
210+
211+
p.x = 300;
212+
p.y = 48;
213+
214+
uint32_t badVAddr = getCop0BadVAddr();
215+
uint32_t epc = getCop0EPC();
216+
char buffer[32];
217+
snprintf(buffer, sizeof(buffer), "BadVAddr: 0x%08x", badVAddr);
218+
printString(buffer, s, baseV, p);
219+
p.x = 300;
220+
p.y += 16;
221+
snprintf(buffer, sizeof(buffer), "EPC : 0x%08x", epc);
222+
printString(buffer, s, baseV, p);
223+
p.x = 300;
224+
p.y += 16;
225+
226+
if ((exceptionCode == 8) || (exceptionCode == 9)) {
227+
uint32_t code = *(uint32_t *)(epc & ~3) >> 6;
228+
uint32_t category = code >> 10;
229+
code &= 0x3ff;
230+
snprintf(buffer, sizeof(buffer), "Code : %d", code);
231+
printString(buffer, s, baseV, p);
232+
p.x = 300;
233+
p.y += 16;
234+
snprintf(buffer, sizeof(buffer), "Category: %d", category);
235+
printString(buffer, s, baseV, p);
236+
if (category == 7) {
237+
p.x = 300;
238+
p.y += 32;
239+
switch (code) {
240+
case 0:
241+
printString("Division by zero", s, baseV, p);
242+
break;
243+
}
244+
}
245+
}
246+
}
247+
__builtin_unreachable();
248+
}

src/mips/psyqo/src/kernel.cpp

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ bool handleBreak(uint32_t code) {
9999
return false;
100100
}
101101

102+
[[noreturn]] void (*s_crashHandlerPtr)(uint32_t, uint32_t*) = nullptr;
102103
} // namespace
103104

104105
void psyqo::Kernel::setBreakHandler(unsigned category, eastl::function<bool(uint32_t)>&& handler) {
@@ -126,10 +127,14 @@ void psyqoExceptionHandler(uint32_t ireg) {
126127
}
127128
void psyqoBreakHandler(uint32_t code) {
128129
if (handleBreak(code)) return;
130+
if (s_crashHandlerPtr) {
131+
s_crashHandlerPtr(9, nullptr);
132+
}
129133
ramsyscall_printf("Unhandled break: %08x\n", code);
130134
psyqo::Kernel::abort("Unhandled break");
131135
}
132136
void psyqoAssemblyExceptionHandler();
137+
extern uint32_t psyqoExceptionHandlerStop[];
133138
}
134139

135140
void psyqo::Kernel::takeOverKernel() {
@@ -165,6 +170,18 @@ void psyqo::Kernel::takeOverKernel() {
165170
});
166171
}
167172

173+
void psyqo::Kernel::installCrashHandler() {
174+
if (s_tookOverKernel) {
175+
uintptr_t crashHandlerAddr = reinterpret_cast<uintptr_t>(Internal::crashHandler);
176+
uint16_t hi = crashHandlerAddr >> 16;
177+
uint16_t lo = crashHandlerAddr & 0xffff;
178+
psyqoExceptionHandlerStop[0] = Mips::Encoder::lui(Mips::Encoder::Reg::V1, hi);
179+
psyqoExceptionHandlerStop[1] = Mips::Encoder::ori(Mips::Encoder::Reg::V1, lo);
180+
flushCache();
181+
}
182+
s_crashHandlerPtr = Internal::crashHandler;
183+
}
184+
168185
void psyqo::Kernel::queueIRQHandler(IRQ irq, eastl::function<void()>&& lambda) {
169186
auto& handlers = *s_irqHandlers;
170187
size_t index = static_cast<size_t>(irq);
@@ -337,12 +354,14 @@ void psyqo::Kernel::Internal::prepare(Application& application) {
337354
Process* processes = *reinterpret_cast<Process**>(0x108);
338355
Thread* currentThread = processes[0].thread;
339356
unsigned exCode = currentThread->registers.Cause & 0x3c;
340-
if (exCode != 0x24) return;
341-
unsigned code = *reinterpret_cast<uint32_t*>(currentThread->registers.returnPC) >> 6;
342-
if (handleBreak(code)) {
343-
currentThread->registers.returnPC += 4;
344-
syscall_returnFromException();
357+
if (exCode == 0x24) {
358+
unsigned code = *reinterpret_cast<uint32_t*>(currentThread->registers.returnPC) >> 6;
359+
if (handleBreak(code)) {
360+
currentThread->registers.returnPC += 4;
361+
syscall_returnFromException();
362+
}
345363
}
364+
if (s_crashHandlerPtr) s_crashHandlerPtr(exCode >> 2, &currentThread->registers.GPR.r[0]);
346365
});
347366
syscall_enableEvent(event);
348367
} else {

0 commit comments

Comments
 (0)