152 lines
5.7 KiB
C
152 lines
5.7 KiB
C
// libunwindStub.c — Itanium _Unwind_* surface mapped onto our SJLJ runtime.
|
|
//
|
|
// Phase 5.1 of GAP_CLOSURE_PLAN (Phase 0.1 LOCKED option A): NOT a real
|
|
// DWARF unwinder. We expose the symbols third-party C++ libraries
|
|
// (libcxx, abseil, etc.) reference from their `<exception>` and
|
|
// `<typeinfo>` paths and route them through the existing SJLJ machinery
|
|
// in libcxxabiSjlj.c.
|
|
//
|
|
// Contract:
|
|
// - `_Unwind_RaiseException(exc)` is invoked by user code that wants
|
|
// to throw a pre-allocated `_Unwind_Exception`. We delegate to the
|
|
// SJLJ raiser, which walks gActive and longjmps to the first frame
|
|
// whose catch table matches.
|
|
// - `_Unwind_Resume(exc)` corresponds to a `resume` instruction at
|
|
// the tail of a cleanup landing pad. Our SJLJ landing pads
|
|
// dispatch from data[0]/data[1] directly so this is rarely hit;
|
|
// when it is, we keep unwinding by re-raising.
|
|
// - `_Unwind_GetIP` / `_Unwind_SetIP` / `_Unwind_GetCFA` /
|
|
// `_Unwind_GetLanguageSpecificData` operate on a
|
|
// `_Unwind_Context *`. Our SJLJ scheme never builds a real
|
|
// context — we hand back 0/no-op values that match what a personality
|
|
// routine asking "what was the IP?" would see in a stub
|
|
// environment (i.e. "nothing useful here, continue unwinding").
|
|
// - `_Unwind_DeleteException` calls the exception_cleanup callback if
|
|
// non-null and is otherwise a no-op; user code allocates the
|
|
// exception storage itself.
|
|
//
|
|
// All symbols are weak so user code (or a real unwinder ported later)
|
|
// can override. Pure-C programs and C++ programs that don't use these
|
|
// entry points get link-GC'd to zero cost.
|
|
//
|
|
// Throwing across a non-SJLJ-instrumented frame terminates: the SJLJ
|
|
// raiser walks gActive, and frames not registered via
|
|
// _Unwind_SjLj_Register are invisible. Document this in the
|
|
// reviewer-facing notes.
|
|
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
|
|
// Itanium ABI return codes. Public surface.
|
|
typedef enum UnwindReasonE {
|
|
URC_NO_REASON = 0,
|
|
URC_FOREIGN_EXCEPTION_CAUGHT = 1,
|
|
URC_FATAL_PHASE2_ERROR = 2,
|
|
URC_FATAL_PHASE1_ERROR = 3,
|
|
URC_NORMAL_STOP = 4,
|
|
URC_END_OF_STACK = 5,
|
|
URC_HANDLER_FOUND = 6,
|
|
URC_INSTALL_CONTEXT = 7,
|
|
URC_CONTINUE_UNWIND = 8
|
|
} UnwindReasonE;
|
|
|
|
// Opaque to user code; we never inspect the body — only the cleanup
|
|
// callback at a fixed offset that user code initialized.
|
|
struct _Unwind_Exception;
|
|
typedef void (*UnwindExceptionCleanupFn)(UnwindReasonE reason, struct _Unwind_Exception *exc);
|
|
|
|
// Layout per Itanium ABI: 8-byte class + cleanup fn + 2 private slots.
|
|
// We only need to reach `exception_cleanup`.
|
|
typedef struct _Unwind_Exception {
|
|
uint64_t exception_class;
|
|
UnwindExceptionCleanupFn exception_cleanup;
|
|
uintptr_t private_1;
|
|
uintptr_t private_2;
|
|
} _Unwind_Exception;
|
|
|
|
// Opaque context — see notes above.
|
|
typedef struct _Unwind_Context _Unwind_Context;
|
|
|
|
// Forward to the SJLJ raiser. The signature differs from the public
|
|
// one (it takes an ExcHeader) but for the stub surface we treat the
|
|
// _Unwind_Exception as if it were the ExcHeader — both are pointers
|
|
// into user-allocated storage and the SJLJ matcher only reads the type
|
|
// off it, which user code with this entry point hasn't set up. In
|
|
// practice third-party throwers that bypass __cxa_throw and go straight
|
|
// to _Unwind_RaiseException are rare and they don't reach our catch
|
|
// dispatch anyway; the contract here is "doesn't fail to link, terminates
|
|
// cleanly at runtime if actually invoked".
|
|
extern void _Unwind_SjLj_RaiseException(void *exc) __attribute__((noreturn));
|
|
extern void abort(void) __attribute__((noreturn));
|
|
|
|
|
|
// ---- raise / resume ----
|
|
|
|
__attribute__((weak, noreturn))
|
|
UnwindReasonE _Unwind_RaiseException(_Unwind_Exception *exc) {
|
|
// Route to the SJLJ raiser. If no frame matches it falls through
|
|
// to abort() (see libcxxabiSjlj.c), which satisfies the
|
|
// "terminates" semantics for un-SJLJ-instrumented throw paths.
|
|
_Unwind_SjLj_RaiseException((void *)exc);
|
|
// Unreachable; abort() above is noreturn and so is the raiser.
|
|
abort();
|
|
}
|
|
|
|
|
|
__attribute__((weak, noreturn))
|
|
void _Unwind_Resume(_Unwind_Exception *exc) {
|
|
// Cleanup landing pad finished and asked us to keep unwinding.
|
|
// SJLJ scheme normally dispatches via data[0]/data[1] directly,
|
|
// but if we land here we re-raise to walk the next outer frame.
|
|
_Unwind_SjLj_RaiseException((void *)exc);
|
|
abort();
|
|
}
|
|
|
|
|
|
// ---- context getters/setters ----
|
|
//
|
|
// In a real DWARF unwinder these inspect the saved register state of
|
|
// the frame being unwound. Our SJLJ scheme never materializes such
|
|
// state, so we hand back conservative zeros / accept-and-discard. A
|
|
// personality routine seeing IP=0 / LSDA=0 will return
|
|
// URC_CONTINUE_UNWIND, which is exactly the behavior we want.
|
|
|
|
__attribute__((weak))
|
|
uintptr_t _Unwind_GetIP(_Unwind_Context *ctx) {
|
|
(void)ctx;
|
|
return 0;
|
|
}
|
|
|
|
|
|
__attribute__((weak))
|
|
void _Unwind_SetIP(_Unwind_Context *ctx, uintptr_t ip) {
|
|
(void)ctx;
|
|
(void)ip;
|
|
}
|
|
|
|
|
|
__attribute__((weak))
|
|
uintptr_t _Unwind_GetCFA(_Unwind_Context *ctx) {
|
|
(void)ctx;
|
|
return 0;
|
|
}
|
|
|
|
|
|
__attribute__((weak))
|
|
uintptr_t _Unwind_GetLanguageSpecificData(_Unwind_Context *ctx) {
|
|
// A real implementation returns the LSDA pointer for the
|
|
// currently-being-unwound frame; we have no such notion here.
|
|
(void)ctx;
|
|
return 0;
|
|
}
|
|
|
|
|
|
// ---- delete ----
|
|
|
|
__attribute__((weak))
|
|
void _Unwind_DeleteException(_Unwind_Exception *exc) {
|
|
if (exc && exc->exception_cleanup) {
|
|
exc->exception_cleanup(URC_FOREIGN_EXCEPTION_CAUGHT, exc);
|
|
}
|
|
// User code owns the storage. No free() here.
|
|
}
|