116 lines
3.8 KiB
C
116 lines
3.8 KiB
C
// Phase 6.2 UBSan-min smoke probe.
|
|
//
|
|
// Three UB cases (one each from the spec):
|
|
// kind 0 (sentinel 0xC0DE): signed-overflow add (i16 INT_MAX + 1)
|
|
// kind 1 (sentinel 0xC0DF): shift-out-of-bounds (1 << 17 on a u16)
|
|
// kind 2 (sentinel 0xC0E0): divide-by-zero (n / 0)
|
|
//
|
|
// The probe overrides the three relevant `__ubsan_handle_*_minimal`
|
|
// recovering handlers with strong definitions that record their
|
|
// firing in a static state byte. After each UB, the probe writes
|
|
// 0xC0DE + kind to $025000 to prove (a) the instrumentation fired and
|
|
// (b) execution recovered cleanly past the UB. The recover handler
|
|
// returning normally is the whole point of -fsanitize-minimal-runtime
|
|
// + -fsanitize-recover; this probe is what proves the round-trip.
|
|
//
|
|
// To verify all three at once we cascade the sentinel writes through a
|
|
// staircase of $025000 / $025002 / $025004 word stores so the smoke
|
|
// harness can read three independent 16-bit values back from MAME.
|
|
//
|
|
// Compile with -fsanitize=undefined -fsanitize-minimal-runtime.
|
|
|
|
#include <stdint.h>
|
|
|
|
|
|
// Bank-2 BSS at $025000-$025006 — outside the SHR shadow and outside
|
|
// $C000-$CFFF IO window. link816 places .bss at the user-specified
|
|
// --bss-base (we pass 0xA000) so these constant addresses are
|
|
// independent of BSS layout.
|
|
#define MARK_ADD_OVF ((volatile uint16_t *)0x025000UL)
|
|
#define MARK_SHIFT_OOB ((volatile uint16_t *)0x025002UL)
|
|
#define MARK_DIV_ZERO ((volatile uint16_t *)0x025004UL)
|
|
#define DONE_SENTINEL ((volatile uint16_t *)0x025006UL)
|
|
|
|
|
|
// Strong overrides win over runtime/ubsan.o's weak-by-link defaults.
|
|
// Each fires once per kind and records that the corresponding UB
|
|
// instrumentation reached us. Recovering handlers MUST return so the
|
|
// probe continues executing past the UB site.
|
|
static volatile uint8_t handlerFiredAdd = 0;
|
|
static volatile uint8_t handlerFiredShift = 0;
|
|
static volatile uint8_t handlerFiredDiv = 0;
|
|
|
|
|
|
void __ubsan_handle_add_overflow_minimal(void) {
|
|
handlerFiredAdd = 1;
|
|
}
|
|
|
|
|
|
void __ubsan_handle_shift_out_of_bounds_minimal(void) {
|
|
handlerFiredShift = 1;
|
|
}
|
|
|
|
|
|
void __ubsan_handle_divrem_overflow_minimal(void) {
|
|
handlerFiredDiv = 1;
|
|
}
|
|
|
|
|
|
// Each UB site goes through a noinline wrapper so the optimizer
|
|
// cannot constant-fold the operation away. __attribute__((noinline))
|
|
// + volatile inputs blocks the obvious folding paths; we also wrap
|
|
// the inputs with `volatile` reads so the LLVM mid-end has no
|
|
// known-value to work with.
|
|
__attribute__((noinline))
|
|
static int16_t triggerAddOverflow(int16_t a, int16_t b) {
|
|
return a + b;
|
|
}
|
|
|
|
|
|
__attribute__((noinline))
|
|
static uint16_t triggerShiftOob(uint16_t a, uint16_t s) {
|
|
return a << s;
|
|
}
|
|
|
|
|
|
__attribute__((noinline))
|
|
static int16_t triggerDivByZero(int16_t a, int16_t b) {
|
|
return a / b;
|
|
}
|
|
|
|
|
|
int main(void) {
|
|
// --- case 0: signed-overflow add (INT16_MAX + 1) ---
|
|
volatile int16_t aMax = 0x7FFF;
|
|
volatile int16_t aOne = 1;
|
|
(void)triggerAddOverflow(aMax, aOne);
|
|
if (handlerFiredAdd) {
|
|
*MARK_ADD_OVF = 0xC0DE;
|
|
}
|
|
|
|
// --- case 1: shift-out-of-bounds (1 << 17 on a u16) ---
|
|
volatile uint16_t base = 1;
|
|
volatile uint16_t shf = 17;
|
|
(void)triggerShiftOob(base, shf);
|
|
if (handlerFiredShift) {
|
|
*MARK_SHIFT_OOB = 0xC0DF;
|
|
}
|
|
|
|
// --- case 2: divide-by-zero (signed) ---
|
|
volatile int16_t num = 42;
|
|
volatile int16_t den = 0;
|
|
(void)triggerDivByZero(num, den);
|
|
if (handlerFiredDiv) {
|
|
*MARK_DIV_ZERO = 0xC0E0;
|
|
}
|
|
|
|
// Final liveness sentinel — only written if we got past all three
|
|
// UB sites without the runtime aborting (which would have spun on
|
|
// a BRK_pseudo at $70 instead of reaching here).
|
|
*DONE_SENTINEL = 0xC0DA;
|
|
|
|
// Halt — crt0's return-from-main path hits a BRK that headless
|
|
// MAME wild-jumps from, so spin-wait instead.
|
|
while (1) {
|
|
}
|
|
}
|