68 lines
2.6 KiB
ArmAsm
68 lines
2.6 KiB
ArmAsm
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
#include "textflag.h"
|
|
|
|
// ABI-checking trampoline. Sets sentinel values in the callee-saved
|
|
// registers (BP, R14) before entering the JIT function and checks whether
|
|
// they survived on return.
|
|
//
|
|
// The return trampoline (leaveJITCheckedRaw) is a raw TEXT symbol with no
|
|
// Go function declaration, so the toolchain does NOT interpose an
|
|
// ABIInternal wrapper; the JIT function RETs directly into the check code,
|
|
// which sees the registers exactly as the function left them.
|
|
//
|
|
// Go ABI0 on amd64 guarantees:
|
|
// - BP is callee-saved (NOSPLIT frame=0 functions must not touch it).
|
|
// - R14 holds the goroutine pointer and must survive across any call.
|
|
|
|
// Sentinel values chosen to be unlikely in normal execution.
|
|
#define SENTINEL_BP 0xDEADBEEFCAFEF00D
|
|
#define SENTINEL_R14 0x0BADF00DDEADBEEF
|
|
|
|
// GLOBL holding the raw address of the leave trampoline, read by Go.
|
|
GLOBL ·leaveCheckedPtr(SB), NOPTR, $8
|
|
DATA ·leaveCheckedPtr(SB)/8, $·leaveJITCheckedRaw(SB)
|
|
|
|
// func enterJITChecked(fn uintptr, stack uintptr)
|
|
// Sets sentinels in BP and R14, switches to the prepared stack and jumps
|
|
// to fn. The prepared stack's return address must be leaveJITCheckedRaw
|
|
// (read from leaveCheckedPtr).
|
|
TEXT ·enterJITChecked(SB), NOSPLIT, $0-16
|
|
MOVQ fn+0(FP), AX // target (before SP switch)
|
|
MOVQ SP, ·savedSP(SB) // preserve Go stack
|
|
MOVQ BP, ·savedBP(SB) // preserve frame pointer (vet requires save before clobber)
|
|
MOVQ R14, ·savedR14(SB) // preserve the goroutine pointer
|
|
MOVQ $SENTINEL_BP, BP // sentinel in BP
|
|
MOVQ $SENTINEL_R14, R14 // sentinel in R14
|
|
MOVQ stack+8(FP), SP // switch to prepared stack
|
|
JMP AX
|
|
|
|
// leaveJITCheckedRaw is the raw return trampoline. It has NO Go function
|
|
// declaration, so no ABIInternal wrapper is generated; the JIT function's
|
|
// RET lands here directly, seeing BP and R14 exactly as the function left
|
|
// them. It checks the sentinels, records violations in abiResult, then
|
|
// restores the Go stack and returns.
|
|
TEXT ·leaveJITCheckedRaw(SB), NOSPLIT, $0-0
|
|
// Check BP against the sentinel.
|
|
MOVQ $SENTINEL_BP, CX
|
|
CMPQ BP, CX
|
|
JEQ bp_ok
|
|
ORQ $1, ·abiResult(SB)
|
|
|
|
bp_ok:
|
|
// Check R14 against the sentinel.
|
|
MOVQ $SENTINEL_R14, CX
|
|
CMPQ R14, CX
|
|
JEQ r14_ok
|
|
ORQ $2, ·abiResult(SB)
|
|
|
|
r14_ok:
|
|
MOVQ ·savedR14(SB), R14 // restore the goroutine pointer: the runtime
|
|
// needs it the moment Go code resumes, whether
|
|
// or not the kernel violated it (the violation
|
|
// is already recorded in abiResult)
|
|
MOVQ ·savedBP(SB), BP // restore the frame pointer
|
|
MOVQ ·savedSP(SB), SP
|
|
RET
|