Files
UniXNU/scripts/xnu-panic-screen.c

303 lines
9.1 KiB
C

/* Draw a kernel panic message, with its arguments, on the frame buffer, then stop.
*
* Built freestanding by patch-xnu-entry-probe.py and placed in unused padding
* of a COPY of the kernel; panic() and its common panic funnel are redirected
* here. It runs on the kernel's
* own mapping of the frame buffer. With the board observer it uses the retained
* GOP mapping prefix; otherwise it uses vinfo.v_baseaddr.
*
* panic(fmt, ...) is variadic and the Darwin arm64 ABI passes every variadic
* argument in an 8-byte stack slot, starting at the stack pointer on entry.
* A small printf walks the format string over those slots (%s %x %p, with
* 0-flag, width, precision and l/ll/z). Kernel strings are checked with an
* active hardware translation query before reading each byte;
* unmapped/device/user pointers print as pointers. VINFO names that mapping prefix.
*/
typedef unsigned long u64;
typedef unsigned int u32;
typedef unsigned char u8;
struct vc_info {
u32 v_height, v_width, v_depth, v_rowbytes;
u64 v_baseaddr;
};
#define SCALE 3
extern const u64 gFont[];
/* All state lives on the stack: this code sits in the kernel's read-only text,
* so a writable global would fault on its first store. */
struct ctx {
u8 *base;
u32 rb, w, h, x, y;
u8 *log; /* kernel buffer that also receives the formatted message (PANIC_TEXT), 0 = none */
u32 logn;
};
#ifndef PANIC_TEXT
#define PANIC_TEXT 0UL
#endif
#define PANIC_TEXT_BYTES 2048 /* boot_progress_panic_text: a data abort's message with every register, then the frames */
__attribute__((noreturn)) static void park (void)
{
for (;;) {
__asm__ volatile ("wfe");
}
}
static void put (struct ctx *g, u8 c)
{
const u32 cell = 6 * SCALE, line = 14 * SCALE;
u32 row, col, dy, dx;
if (g->log && g->logn < PANIC_TEXT_BYTES - 1 && c >= 9 && c <= 126) {
g->log[g->logn++] = (c < 32) ? ' ' : c;
g->log[g->logn] = 0;
}
if (c == '\n') {
g->x = 0;
g->y += line;
return;
}
if (c == '\t') {
g->x += 4 * cell;
return;
}
if (c < 32 || c > 126) {
return;
}
if (g->x + cell > g->w) {
g->x = 0;
g->y += line;
}
if (g->y + line > g->h) {
return;
}
u64 glyph = gFont[c - 32];
u32 top = (u32)(glyph >> 32), bottom = (u32)glyph;
for (row = 0; row < 12; row++) {
u32 data = (row < 6) ? (top >> (row * 5)) : (bottom >> ((row - 6) * 5));
for (col = 0; col < 5; col++) {
if ((data >> col) & 1) {
for (dy = 0; dy < SCALE; dy++) {
for (dx = 0; dx < SCALE; dx++) {
*(volatile u32 *)(g->base + (u64)(g->y + row * SCALE + dy) * g->rb + (u64)(g->x + col * SCALE + dx) * 4) = 0xFFFFFFFFu;
}
}
}
}
}
g->x += cell;
}
static void number (struct ctx *g, u64 value, u32 base, u32 width, int zero)
{
u8 digits[24];
u32 n = 0, i;
do {
u32 d = (u32)(value % base);
digits[n++] = (u8)(d < 10 ? '0' + d : 'a' + d - 10);
value /= base;
} while (value && n < 24);
for (i = n; i < width; i++) {
put (g, zero ? '0' : ' ');
}
while (n) {
put (g, digits[--n]);
}
}
/* Probe each string byte before dereferencing it. Heap strings (process name
* and exit reason) live outside the linked image. AT performs a hardware
* translation query only: no table edits, mappings, or exception bypass.
* Refuse user pointers, translation faults and device-memory attributes. */
static int readable (const char *s, const u64 *args)
{
u64 a = (u64)s, par;
(void)args;
if (a < 0xffff000000000000UL) return 0;
__asm__ volatile ("at s1e1r, %1; isb; mrs %0, par_el1" : "=r" (par) : "r" (a) : "memory");
return !(par & 1) && ((par >> 60) != 0);
}
static void format (struct ctx *g, const char *fmt, const u64 *args)
{
u32 i, used = 0;
for (i = 0; i < 2000 && fmt[i]; i++) {
u8 c = (u8)fmt[i];
if (c != '%') {
put (g, c);
continue;
}
u32 width = 0;
int zero = 0, longs = 0;
c = (u8)fmt[++i];
if (c == '0') {
zero = 1;
c = (u8)fmt[++i];
}
while (c >= '0' && c <= '9') {
width = width * 10 + (c - '0');
c = (u8)fmt[++i];
}
u32 precision = 4000;
if (c == '.') {
precision = 0;
c = (u8)fmt[++i];
if (c == '*') {
precision = (u32)args[used++];
c = (u8)fmt[++i];
} else {
while (c >= '0' && c <= '9') {
if (precision < 4000) precision = precision * 10 + (c - '0');
c = (u8)fmt[++i];
}
}
if (precision > 4000) precision = 4000;
}
if (width > 256) width = 256;
while (c == 'l' || c == 'z' || c == 'h') {
longs += (c != 'h');
c = (u8)fmt[++i];
}
if (c == '%') {
put (g, '%');
continue;
}
if (c == 0) {
break;
}
u64 v = args[used++];
if (c == 's') {
const char *s = (const char *)v;
u32 k;
for (k = 0; k < precision; k++) {
if (!readable (s + k, args)) {
put (g, '<');
number (g, (u64)(s + k), 16, 0, 0);
put (g, '>');
break;
}
u8 ch = (u8)s[k];
if (!ch) break;
put (g, ch);
}
} else if (c == 'x' || c == 'X') {
number (g, longs ? v : (v & 0xFFFFFFFFu), 16, width, zero);
} else if (c == 'u' || c == 'd') {
number (g, longs ? v : (v & 0xFFFFFFFFu), 10, width, zero);
} else if (c == 'p') {
put (g, '0');
put (g, 'x');
number (g, v, 16, width, zero);
}
}
}
static void text (struct ctx *g, const char *s)
{
while (*s) {
put (g, (u8)*s++);
}
}
static void hex (struct ctx *g, const char *label, u64 v)
{
text (g, label);
put (g, '0');
put (g, 'x');
number (g, v, 16, 16, 1);
}
/* x0 = fmt as for panic(); x1 is set by the stub to the entry stack pointer. */
__attribute__((noreturn, used, section(".text.b_main"))) void panic_screen_main (const char *fmt, const u64 *args)
{
volatile struct vc_info *v = (volatile struct vc_info *)VINFO;
struct ctx g;
u32 row, col;
__asm__ volatile ("msr daifset, #15");
g.base = (u8 *)v->v_baseaddr;
g.rb = v->v_rowbytes;
g.w = v->v_width;
g.h = v->v_height;
if (g.base == 0 || v->v_depth != 32 || fmt == 0) {
park ();
}
/* First panic wins: a marker in the last row's first two pixels means a panic already drew
* the screen; a later panic (another thread or exception) must not overwrite it. */
volatile u32 *mark = (volatile u32 *)(g.base + (u64)(g.h - 1) * g.rb);
if (mark[0] == 0xC0FFEE11u && mark[1] == 0x5CEEDA7Au) {
park ();
}
g.x = 0;
g.y = g.h / 8;
g.log = 0;
g.logn = 0;
for (row = g.y; row < g.h; row++) {
for (col = 0; col < g.w; col++) {
*(volatile u32 *)(g.base + (u64)row * g.rb + (u64)col * 4) = 0;
}
}
g.y += 8;
/* The board's live banner keeps showing this text after the next swap paints over the screen (BootProgress.c,
* boot_progress_panic_text). The first panic's text is kept: a buffer that already holds one is left alone. */
if (PANIC_TEXT && *(volatile u8 *)PANIC_TEXT == 0) {
g.log = (u8 *)PANIC_TEXT;
}
format (&g, fmt, args);
/* The return addresses up the frame pointer chain: the exception entry links the panic's frames to the code that faulted, so this
* names the callers the message leaves out. Stops at a pointer outside the kernel's range, a misaligned one or one that does not
* move up the stack. */
{
const u64 *fp = (const u64 *)__builtin_frame_address (0);
text (&g, "\nframes");
for (unsigned i = 0; i < 24; i++) {
const u64 at = (u64)fp;
if (at < 0xfffffff000000000UL || (at & 15)) break;
const u64 next = fp[0], lr = fp[1] & 0x0000007fffffffffUL; /* the return address without its pointer authentication bits */
put (&g, ' ');
number (&g, lr | 0xffffff8000000000UL, 16, 16, 1);
if (next <= at) break;
fp = (const u64 *)next;
}
}
g.log = 0;
__asm__ volatile ("dsb sy");
/* Resolved from this candidate's symbols by the patcher, never fixed addresses.
* Values are sampled at panic time and need not form an atomic VM snapshot. */
(void)MAX_MEM;
hex (&g, "\npages=", *(volatile const u32 *)PAGE_TOTAL);
hex (&g, "\nfree=", *(volatile const u32 *)PAGE_FREE);
/* The raw argument slots are not dumped: the formatted message above already carries them (room for the PANIC_TEXT copy). */
mark[0] = 0xC0FFEE11u;
mark[1] = 0x5CEEDA7Au;
__asm__ volatile ("dsb sy");
park ();
}
/* Entry from panic(): keep the stack pointer, which addresses the variadic arguments. */
__attribute__((naked, noreturn, section(".text.a_entry"))) void panic_screen (const char *fmt)
{
__asm__ volatile ("mov x1, sp\n\tb panic_screen_main");
}
/* The real panic_trap_to_debugger signature takes (fmt, va_list *, ...).
* Darwin ARM64 va_list is a stack pointer; its address is passed in x1.
* The wrapper has already established its frame, so SP is NOT the argument
* list here. Follow exactly the one pointer supplied by the real wrapper.
*/
__attribute__((naked, noreturn, section(".text.a_common"))) void panic_screen_common (void)
{
__asm__ volatile ("ldr x1, [x1]\n\tb panic_screen_main");
}
#include "Font.h"
__attribute__((section(".text.z_font"))) const u64 gFont[] = GLYPH_FONT;