mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 17:09:57 -04:00
The review of the shared failure guard found that a thread parked in dispatch.c rt_ending_enter ended the process with 71 after 30 s even when the first report was a FATAL (70, or 72 for a lost device), so a fatal error whose report hung read as a crash to the launcher. rt_ending_enter now takes the code its caller ends with: fatal_end passes its own, main.c crash_handler 71. The first caller stores it next to ending_tid, and a waiting thread that gives up after 30 s ends the process with it. Before the store the value is 71, as before. Reproduced before the change with a one-off build of tests/fatal_exit_check.c given a fatal note that never returns and a fault on another thread: the process ended with 71 after 30.5 s; with this change it ends with 70 after 30.5 s. That case takes 30 s, so the matrix gets a static check instead: tests/fatal_exit_check.py holds rt_ending_enter to storing the first caller's code and ending with it, fatal_end to passing its code, and crash_handler to its first line `if (!rt_ending_enter(71)) plat_exit(71);`. fatal_exit_check and the compile check of every runtime file (a header changed) pass, with no new -Wall warnings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
72 lines
4.1 KiB
C
72 lines
4.1 KiB
C
// Guest address -> host function lookup for indirect branches (rt_dispatch, declared in cpu.h).
|
|
//
|
|
// One flat table covers every module's code range: slot (pc - lo) >> 2 holds the host function for a guest
|
|
// function start, as a 32-bit offset from an anchor address. Lookup is one range check and one load. The table is
|
|
// built once by rt_dispatch_init and never written again, so threads read it without locks.
|
|
#pragma once
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
|
|
#include "cpu.h"
|
|
|
|
// A lifted module: its place in the guest layout and the generator's sorted function table.
|
|
typedef struct RtModule {
|
|
const char *name;
|
|
uint64_t base, size; // guest image (layout.h MOD_<NAME>_BASE / _SIZE)
|
|
const uint64_t *addrs; // <mod>_fn_addrs: guest function starts, strictly ascending
|
|
const GuestFn *fns; // <mod>_fn_ptrs: the lifted function for each start
|
|
uint64_t count; // <mod>_fn_count
|
|
} RtModule;
|
|
|
|
// The module glue that tools/build_game.py writes for each build (build/<version>/modules.c), from layout.json and
|
|
// layout.h: every module of the layout, and the modules linked into this executable.
|
|
typedef struct RtLayoutEntry { const char *name; uint64_t base, size; } RtLayoutEntry;
|
|
extern const RtLayoutEntry rt_layout[];
|
|
extern const int rt_layout_count;
|
|
int rt_linked_modules(RtModule *out, int max);
|
|
|
|
// Register a host function at a guest address outside every module (HLE entry points). Allowed only before
|
|
// rt_dispatch_init; the registry is fixed afterwards.
|
|
void rt_register_host(uint64_t addr, GuestFn fn);
|
|
|
|
// Build the table from the modules (kept by pointer; they must outlive the process). Call it once, before any guest
|
|
// thread starts: thread creation publishes the table to the threads. Fatal on a malformed table:
|
|
// unsorted or duplicate starts, a start outside its module, overlapping modules, host code spread over 4 GiB.
|
|
void rt_dispatch_init(const RtModule *mods, int count);
|
|
|
|
// The host function for a guest address, or NULL when nothing is there. Does not run guest code.
|
|
GuestFn rt_dispatch_lookup(uint64_t pc);
|
|
|
|
// "0x7000001234 (main+0x1234)" for a guest address inside a module, else the bare address. Returns buf.
|
|
const char *rt_guest_name(uint64_t pc, char *buf, int size);
|
|
|
|
typedef struct RtDispatchStats {
|
|
uint64_t lo, hi; // guest range the table covers
|
|
uint64_t slots; // (hi - lo) / 4
|
|
uint64_t table_bytes; // reserved for the table
|
|
uint64_t functions; // filled slots
|
|
uint64_t host_fns; // rt_register_host entries
|
|
} RtDispatchStats;
|
|
RtDispatchStats rt_dispatch_stats(void);
|
|
|
|
// Runtime fatal error: message to stderr, then exit. Used by the dispatcher and the stubs.
|
|
void rt_fatal(const char *fmt, ...) __attribute__((noreturn, format(printf, 1, 2)));
|
|
// The same end with an exit code of its own, for a failure that a script or the launcher tells from the others by it
|
|
// (vk_device.c vk_device_lost_check ends with RT_LOST_EXIT). rt_fatal ends with RT_FATAL_EXIT; 71 is the crash report's
|
|
// (main.c crash_handler).
|
|
#define RT_FATAL_EXIT 70
|
|
#define RT_LOST_EXIT 72
|
|
void rt_fatal_code(int code, const char *fmt, ...) __attribute__((noreturn, format(printf, 2, 3)));
|
|
// Lines of context for a fatal error's report, which rt_fatal and the crash report (main.c crash_handler) write after
|
|
// their own: fn logs one with rt_log. It runs on the failing thread, which may hold any lock, so it reads only what
|
|
// needs none. At most four; adding one twice keeps one. rt_fatal_notes runs them, once per process.
|
|
void rt_fatal_note_add(void (*fn)(void));
|
|
void rt_fatal_notes(void);
|
|
// The one end of a failed run: the fatal error (rt_fatal) and the crash report (main.c crash_handler) call it first,
|
|
// with the code they end the process with. 1 for the first caller, which reports and ends the process; 0 for that
|
|
// thread again (a failure inside its own report), which ends at once with its code; any other thread waits, logging
|
|
// nothing, until the process ends, and after 30 s ends it with the first caller's code.
|
|
int rt_ending_enter(int code);
|
|
// HD rumble stopped before a failed run ends, on a thread of its own that the caller waits for 300 ms at most.
|
|
void rt_rumble_stop_bounded(void);
|