303 lines
9.1 KiB
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;
|