fix(verify): gate JIT verification to amd64 until trampolines are hardened
This commit is contained in:
@@ -12,6 +12,11 @@
|
|||||||
coverage.out
|
coverage.out
|
||||||
*.test
|
*.test
|
||||||
|
|
||||||
|
# Crash dumps
|
||||||
|
core
|
||||||
|
core.*
|
||||||
|
*.core
|
||||||
|
|
||||||
# Scratch / temporary work
|
# Scratch / temporary work
|
||||||
_scratch/
|
_scratch/
|
||||||
|
|
||||||
|
|||||||
+12
-10
@@ -43,16 +43,18 @@ Unreleased changes on the `development` branch.
|
|||||||
architectures via hand-written assembly trampolines
|
architectures via hand-written assembly trampolines
|
||||||
(`trampoline_{arm64,riscv64,loong64}.s`) that save the Go stack, switch
|
(`trampoline_{arm64,riscv64,loong64}.s`) that save the Go stack, switch
|
||||||
to a prepared stack, and branch to the JIT function.
|
to a prepared stack, and branch to the JIT function.
|
||||||
- **ABI checks on all architectures.** `gasm verify -abi` and the ABI
|
- **ABI checks architecture port (partial).** The ABI-checking
|
||||||
half of `-fuzz` now work on arm64, riscv64 and loong64 via
|
machinery is architecture-neutral (`ABIReport` with `FPClobbered`,
|
||||||
per-architecture checked trampolines: sentinels planted in the
|
`GClobbered`, `RedZoneHit`; renamed from the amd64-only field names)
|
||||||
registers the Go ABI fixes across calls (amd64 `BP`/`R14`, arm64
|
and per-architecture checked trampolines exist for arm64, riscv64 and
|
||||||
`R29`/`R28`, riscv64 `X27`, loong64 `R22`) are verified on return, with
|
loong64 alongside amd64, restoring the frame pointer and the goroutine
|
||||||
the below-SP canary on every architecture. `gasm verify` now runs the
|
pointer before returning into Go code. Runtime execution of the
|
||||||
JIT checks whenever the host matches the kernel's architecture, and
|
non-amd64 JIT paths is not yet reliable (arm64 and loong64 fault on the
|
||||||
takes the ground-truth-only path only on other hosts. The `ABIReport`
|
return path and riscv64 returns a wrong result under qemu-user), so
|
||||||
fields are renamed to the architecture-neutral `FPClobbered` and
|
`gasm verify` keeps JIT execution gated to amd64 kernels on amd64
|
||||||
`GClobbered`.
|
hosts; other kernels take the toolchain-comparison path exactly as
|
||||||
|
before. A qemu-user harness and GOARCH-guarded tests are in the tree
|
||||||
|
to validate the trampolines once their return path is fixed.
|
||||||
- **Hardware watchpoints on all architectures.** arm64 uses DBGWVR/DBGWCR
|
- **Hardware watchpoints on all architectures.** arm64 uses DBGWVR/DBGWCR
|
||||||
via `PTRACE_SETREGSET` with `NT_ARM_HW_BREAK`; riscv64 and loong64 use
|
via `PTRACE_SETREGSET` with `NT_ARM_HW_BREAK`; riscv64 and loong64 use
|
||||||
`PTRACE_POKEUSER` to access trigger/debug registers.
|
`PTRACE_POKEUSER` to access trigger/debug registers.
|
||||||
|
|||||||
+5
-3
@@ -1091,9 +1091,11 @@ each entry reproduces.
|
|||||||
}
|
}
|
||||||
path := set.Arg(0)
|
path := set.Arg(0)
|
||||||
targetArch := arch.FromFilename(path)
|
targetArch := arch.FromFilename(path)
|
||||||
// JIT execution requires the host CPU to match the kernel's
|
// JIT execution is enabled for amd64 kernels on amd64 hosts. The
|
||||||
// architecture; on any other host only the toolchain comparisons run.
|
// non-amd64 execution trampolines are implemented but not yet
|
||||||
if targetArch != hostArch() {
|
// runtime-hardened, so other kernels take the toolchain-comparison
|
||||||
|
// path, which needs no execution.
|
||||||
|
if targetArch != arch.AMD64 || hostArch() != arch.AMD64 {
|
||||||
switch targetArch {
|
switch targetArch {
|
||||||
case arch.RISCV:
|
case arch.RISCV:
|
||||||
// RISC-V: ground-truth only (no JIT on non-RISC-V hosts).
|
// RISC-V: ground-truth only (no JIT on non-RISC-V hosts).
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ func fieldName(stat []byte) string {
|
|||||||
|
|
||||||
func statusDump(b []byte) string {
|
func statusDump(b []byte) string {
|
||||||
var out []string
|
var out []string
|
||||||
for _, l := range strings.Split(string(b), "\n") {
|
for l := range strings.SplitSeq(string(b), "\n") {
|
||||||
if strings.HasPrefix(l, "State") || strings.HasPrefix(l, "Pid") ||
|
if strings.HasPrefix(l, "State") || strings.HasPrefix(l, "Pid") ||
|
||||||
strings.HasPrefix(l, "PPid") || strings.HasPrefix(l, "TracerPid") ||
|
strings.HasPrefix(l, "PPid") || strings.HasPrefix(l, "TracerPid") ||
|
||||||
strings.HasPrefix(l, "Threads") || strings.HasPrefix(l, "SigPnd") ||
|
strings.HasPrefix(l, "Threads") || strings.HasPrefix(l, "SigPnd") ||
|
||||||
|
|||||||
+10
-9
@@ -364,15 +364,16 @@ and returns). A 64-byte pad below the return address accommodates the
|
|||||||
ABIInternal wrapper that the Go runtime interposes on assembly functions.
|
ABIInternal wrapper that the Go runtime interposes on assembly functions.
|
||||||
Every supported architecture carries its own hand-written trampoline pair
|
Every supported architecture carries its own hand-written trampoline pair
|
||||||
(`trampoline_amd64.s`, `trampoline_arm64.s`, `trampoline_riscv64.s`,
|
(`trampoline_amd64.s`, `trampoline_arm64.s`, `trampoline_riscv64.s`,
|
||||||
`trampoline_loong64.s`), so `Call` works wherever the toolkit runs. The
|
`trampoline_loong64.s`). The ABI-checked variant `CallChecked` exists for
|
||||||
ABI-checked variant `CallChecked` exists for every architecture too:
|
every architecture too: `enterJITChecked` plants sentinels in the registers
|
||||||
`enterJITChecked` plants sentinels in the registers the Go ABI fixes across
|
the Go ABI fixes across calls (amd64 `BP`/`R14`, arm64 `R29`/`R28`, riscv64
|
||||||
calls (amd64 `BP`/`R14`, arm64 `R29`/`R28`, riscv64 `X27`, loong64 `R22`;
|
`X27`, loong64 `R22`; the latter two keep no hardware frame pointer) and the
|
||||||
the latter two keep no hardware frame pointer) and the raw return trampoline
|
raw return trampoline `leaveJITCheckedRaw` verifies them, restoring the
|
||||||
`leaveJITCheckedRaw` verifies them, so `-abi` reports frame-pointer,
|
saved registers before Go code resumes. At present only the amd64 JIT path
|
||||||
goroutine-pointer and below-SP violations on every supported host. `gasm
|
is runtime-proven: the non-amd64 trampolines compile and their kernels are
|
||||||
verify` dispatches by host: the JIT checks run when the host matches the
|
correct, but the return into Go code still fails under emulation, so `gasm
|
||||||
kernel's architecture, and only the toolchain comparisons run elsewhere.
|
verify` gates JIT execution to amd64 kernels on amd64 hosts and runs only
|
||||||
|
the toolchain comparisons elsewhere (see docs/DECISIONS.md).
|
||||||
|
|
||||||
`Load` / `LoadSource` / `LoadAST` parse, assemble and map a `.s` file in one
|
`Load` / `LoadSource` / `LoadAST` parse, assemble and map a `.s` file in one
|
||||||
step, returning a `Kernel` whose `CallFunc` method marshals the argument block
|
step, returning a `Kernel` whose `CallFunc` method marshals the argument block
|
||||||
|
|||||||
@@ -33,6 +33,37 @@ runs `go list` as a subprocess (consistent with `toolchainObjectPreamble`
|
|||||||
which already calls `go tool asm`). All symbol data is cached per package
|
which already calls `go tool asm`). All symbol data is cached per package
|
||||||
for the lifetime of the GOOBJ emission.
|
for the lifetime of the GOOBJ emission.
|
||||||
|
|
||||||
|
## 2026-08-30 non-amd64 JIT execution trampolines
|
||||||
|
|
||||||
|
**Status:** open (blocks runtime verification on arm64, riscv64 and
|
||||||
|
loong64 hosts).
|
||||||
|
|
||||||
|
**State.** The per-architecture trampolines compile for all targets, the
|
||||||
|
kernels they execute are byte-for-byte correct against `go tool asm`, and
|
||||||
|
under `qemu-aarch64` the arm64 kernel demonstrably executes and stores its
|
||||||
|
result correctly. The failure is on the return path into Go code: arm64
|
||||||
|
and loong64 take a SIGSEGV after the kernel's RET (the Go-side unwind
|
||||||
|
through `leaveJIT` and its interposed ABIInternal wrapper is the suspect),
|
||||||
|
and riscv64 returns cleanly but with an untouched result area. amd64 is
|
||||||
|
unaffected (the checked trampoline saves and restores BP/R14 and the flow
|
||||||
|
is validated end to end).
|
||||||
|
|
||||||
|
**Evidence harness.** `verify/jit_arch_test.go` (plain call) and
|
||||||
|
`verify/abi_arch_test.go` (checked call) are GOARCH-guarded tests; build
|
||||||
|
the test binary per target (`GOARCH=arm64 go test -c -o v.test ./verify/`)
|
||||||
|
and run it under `qemu-aarch64-static` from the `verify/` directory. A
|
||||||
|
minimal reproducer pattern lives in the qemu exploration notes: verify
|
||||||
|
loads, the kernel executes, the fault follows the return.
|
||||||
|
|
||||||
|
**Fix direction.** Compare the amd64 checked trampoline (GLOBL/DATA raw
|
||||||
|
address, explicit SP/BP/R14 save-restore) against the arm64/riscv64/
|
||||||
|
loong64 `leaveJIT` unwind, in particular the interaction with the
|
||||||
|
ABIInternal wrapper that `reflect.ValueOf(leaveJIT).Pointer()` returns.
|
||||||
|
The plain-call path (no sentinels) fails the same way, so the checked
|
||||||
|
path is not the variable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2026-08-29 tooling round
|
## 2026-08-29 tooling round
|
||||||
|
|
||||||
- `lint abi0-register-args`: flags kernels whose `// func` parameters are
|
- `lint abi0-register-args`: flags kernels whose `// func` parameters are
|
||||||
|
|||||||
@@ -14,6 +14,14 @@ package verify
|
|||||||
//lint:ignore U1000 written and read by the assembly
|
//lint:ignore U1000 written and read by the assembly
|
||||||
var savedBP uintptr
|
var savedBP uintptr
|
||||||
|
|
||||||
|
// savedR14 holds the caller's goroutine pointer across the ABI-checked JIT
|
||||||
|
// call; the trampoline restores it before returning into Go code.
|
||||||
|
//
|
||||||
|
// noinspection GoUnusedGlobalVariable
|
||||||
|
//
|
||||||
|
//lint:ignore U1000 written and read by the assembly
|
||||||
|
var savedR14 uintptr
|
||||||
|
|
||||||
// leaveCheckedPtr is initialised by the linker from the GLOBL/DATA in
|
// leaveCheckedPtr is initialised by the linker from the GLOBL/DATA in
|
||||||
// abi_amd64.s: it holds the raw address of leaveJITCheckedRaw (which has
|
// abi_amd64.s: it holds the raw address of leaveJITCheckedRaw (which has
|
||||||
// no ABIInternal wrapper, so the JIT function RETs directly into it).
|
// no ABIInternal wrapper, so the JIT function RETs directly into it).
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ TEXT ·enterJITChecked(SB), NOSPLIT, $0-16
|
|||||||
MOVQ fn+0(FP), AX // target (before SP switch)
|
MOVQ fn+0(FP), AX // target (before SP switch)
|
||||||
MOVQ SP, ·savedSP(SB) // preserve Go stack
|
MOVQ SP, ·savedSP(SB) // preserve Go stack
|
||||||
MOVQ BP, ·savedBP(SB) // preserve frame pointer (vet requires save before clobber)
|
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_BP, BP // sentinel in BP
|
||||||
MOVQ $SENTINEL_R14, R14 // sentinel in R14
|
MOVQ $SENTINEL_R14, R14 // sentinel in R14
|
||||||
MOVQ stack+8(FP), SP // switch to prepared stack
|
MOVQ stack+8(FP), SP // switch to prepared stack
|
||||||
@@ -57,5 +58,10 @@ bp_ok:
|
|||||||
ORQ $2, ·abiResult(SB)
|
ORQ $2, ·abiResult(SB)
|
||||||
|
|
||||||
r14_ok:
|
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
|
MOVQ ·savedSP(SB), SP
|
||||||
RET
|
RET
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ TEXT ·enterJITChecked(SB), NOSPLIT, $0-16
|
|||||||
MOVD R30, savedLR(SB) // save link register
|
MOVD R30, savedLR(SB) // save link register
|
||||||
MOVD R3, savedSP(SB) // save Go stack pointer
|
MOVD R3, savedSP(SB) // save Go stack pointer
|
||||||
MOVD R29, savedFP(SB) // save frame pointer (vet requires save before clobber)
|
MOVD R29, savedFP(SB) // save frame pointer (vet requires save before clobber)
|
||||||
|
MOVD g, savedG(SB) // save g
|
||||||
MOVD $SENTINEL_FP, R29 // sentinel in the frame pointer
|
MOVD $SENTINEL_FP, R29 // sentinel in the frame pointer
|
||||||
MOVD $SENTINEL_G, g // sentinel in g
|
MOVD $SENTINEL_G, g // sentinel in g
|
||||||
MOVD stack+8(FP), R3 // load prepared stack pointer
|
MOVD stack+8(FP), R3 // load prepared stack pointer
|
||||||
@@ -75,6 +76,9 @@ restore:
|
|||||||
MOVD savedSP(SB), R3 // restore Go stack pointer
|
MOVD savedSP(SB), R3 // restore Go stack pointer
|
||||||
MOVD R3, RSP
|
MOVD R3, RSP
|
||||||
MOVD savedLR(SB), R30 // restore link register
|
MOVD savedLR(SB), R30 // restore link register
|
||||||
|
MOVD savedFP(SB), R29 // restore frame pointer: Go code needs it the
|
||||||
|
// moment it resumes, violation or not
|
||||||
|
MOVD savedG(SB), g // restore g
|
||||||
RET // return to Go caller
|
RET // return to Go caller
|
||||||
|
|
||||||
// Package-level storage for the saved frame pointer. Like savedSP and
|
// Package-level storage for the saved frame pointer. Like savedSP and
|
||||||
@@ -82,3 +86,4 @@ restore:
|
|||||||
// checked trampoline saves the caller's frame pointer for vet's sake and
|
// checked trampoline saves the caller's frame pointer for vet's sake and
|
||||||
// never restores it, and this file mirrors that.
|
// never restores it, and this file mirrors that.
|
||||||
GLOBL savedFP(SB), NOPTR, $8
|
GLOBL savedFP(SB), NOPTR, $8
|
||||||
|
GLOBL savedG(SB), NOPTR, $8
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ TEXT ·enterJITChecked(SB), NOSPLIT, $0-16
|
|||||||
MOVV fn+0(FP), R4 // target function address (A0)
|
MOVV fn+0(FP), R4 // target function address (A0)
|
||||||
MOVV R1, savedRA(SB) // save return address (RA)
|
MOVV R1, savedRA(SB) // save return address (RA)
|
||||||
MOVV R3, savedSP(SB) // save Go stack pointer (SP)
|
MOVV R3, savedSP(SB) // save Go stack pointer (SP)
|
||||||
|
MOVV g, savedG(SB) // save g
|
||||||
MOVV $SENTINEL_G, g // sentinel in g
|
MOVV $SENTINEL_G, g // sentinel in g
|
||||||
MOVV stack+8(FP), R5 // load prepared stack pointer (A1)
|
MOVV stack+8(FP), R5 // load prepared stack pointer (A1)
|
||||||
MOVV 0(R5), R1 // load leaveJITCheckedRaw into RA
|
MOVV 0(R5), R1 // load leaveJITCheckedRaw into RA
|
||||||
@@ -58,4 +59,8 @@ g_ok:
|
|||||||
MOVV savedSP(SB), R5 // restore Go stack pointer
|
MOVV savedSP(SB), R5 // restore Go stack pointer
|
||||||
MOVV R5, R3
|
MOVV R5, R3
|
||||||
MOVV savedRA(SB), R1 // restore return address
|
MOVV savedRA(SB), R1 // restore return address
|
||||||
|
MOVV savedG(SB), g // restore g: Go code needs it the moment it
|
||||||
|
// resumes, violation or not
|
||||||
JIRL R0, R1, 0 // return to Go caller
|
JIRL R0, R1, 0 // return to Go caller
|
||||||
|
|
||||||
|
GLOBL savedG(SB), NOPTR, $8
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ TEXT ·enterJITChecked(SB), NOSPLIT, $0-16
|
|||||||
MOV fn+0(FP), X5 // target function address (T0)
|
MOV fn+0(FP), X5 // target function address (T0)
|
||||||
MOV X1, savedRA(SB) // save return address
|
MOV X1, savedRA(SB) // save return address
|
||||||
MOV X2, savedSP(SB) // save Go stack pointer
|
MOV X2, savedSP(SB) // save Go stack pointer
|
||||||
|
MOV g, savedG(SB) // save g
|
||||||
MOV $SENTINEL_G, g // sentinel in g
|
MOV $SENTINEL_G, g // sentinel in g
|
||||||
MOV stack+8(FP), X6 // load prepared stack pointer (T1)
|
MOV stack+8(FP), X6 // load prepared stack pointer (T1)
|
||||||
LD 0(X6), X1 // load leaveJITCheckedRaw into RA
|
LD 0(X6), X1 // load leaveJITCheckedRaw into RA
|
||||||
@@ -58,4 +59,8 @@ g_ok:
|
|||||||
MOV savedSP(SB), X6 // restore Go stack pointer
|
MOV savedSP(SB), X6 // restore Go stack pointer
|
||||||
MOV X6, X2
|
MOV X6, X2
|
||||||
MOV savedRA(SB), X1 // restore return address
|
MOV savedRA(SB), X1 // restore return address
|
||||||
|
MOV savedG(SB), g // restore g: Go code needs it the moment it
|
||||||
|
// resumes, violation or not
|
||||||
JALR X0, 0(X1) // return to Go caller
|
JALR X0, 0(X1) // return to Go caller
|
||||||
|
|
||||||
|
GLOBL savedG(SB), NOPTR, $8
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ package verify
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
@@ -23,12 +22,12 @@ func enterJIT(fn uintptr, stack uintptr)
|
|||||||
//go:nosplit
|
//go:nosplit
|
||||||
func leaveJIT()
|
func leaveJIT()
|
||||||
|
|
||||||
// leaveJITAddr is the machine address of leaveJIT.
|
// leaveJITAddr is the raw ABI0 address of leaveJIT, handed over by the
|
||||||
var leaveJITAddr uintptr
|
// GLOBL/DATA in trampoline_arm64.s (reflect would return the interposed
|
||||||
|
// ABIInternal wrapper instead).
|
||||||
|
var leaveRawAddr uintptr
|
||||||
|
|
||||||
func init() {
|
var leaveJITAddr = leaveRawAddr
|
||||||
leaveJITAddr = reflect.ValueOf(leaveJIT).Pointer()
|
|
||||||
}
|
|
||||||
|
|
||||||
const stackPad = 64
|
const stackPad = 64
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package verify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// requireArchHost skips unless the test binary runs on the named GOARCH: the
|
||||||
|
// JIT executes native code, so an arm64 kernel only runs on an arm64 CPU
|
||||||
|
// (real hardware or qemu-user emulation).
|
||||||
|
func requireArchHost(t *testing.T, goarch string) {
|
||||||
|
t.Helper()
|
||||||
|
if runtime.GOARCH != goarch {
|
||||||
|
t.Skipf("runs only on %s hosts (this host is %s)", goarch, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPlainCallArm64 checks the bare JIT call path (no ABI sentinels) on
|
||||||
|
// arm64: the trampoline, the kernel and the result read-back.
|
||||||
|
func TestPlainCallArm64(t *testing.T) {
|
||||||
|
requireArchHost(t, "arm64")
|
||||||
|
|
||||||
|
k, err := Load("../testdata/verify/abi_arm64.s")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load: %v", err)
|
||||||
|
}
|
||||||
|
t.Cleanup(k.Close)
|
||||||
|
|
||||||
|
args := make([]byte, 24)
|
||||||
|
PutUint64(args, 0, 3)
|
||||||
|
PutUint64(args, 8, 4)
|
||||||
|
out, err := k.CallFunc("cleanAdd", args)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CallFunc: %v", err)
|
||||||
|
}
|
||||||
|
if got := int64(GetUint64(out, 16)); got != 7 {
|
||||||
|
t.Errorf("cleanAdd(3, 4) = %d, want 7", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPlainCallRiscv64 is TestPlainCallArm64 for riscv64.
|
||||||
|
func TestPlainCallRiscv64(t *testing.T) {
|
||||||
|
requireArchHost(t, "riscv64")
|
||||||
|
|
||||||
|
k, err := Load("../testdata/verify/abi_riscv64.s")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load: %v", err)
|
||||||
|
}
|
||||||
|
t.Cleanup(k.Close)
|
||||||
|
|
||||||
|
args := make([]byte, 24)
|
||||||
|
PutUint64(args, 0, 3)
|
||||||
|
PutUint64(args, 8, 4)
|
||||||
|
out, err := k.CallFunc("cleanAdd", args)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CallFunc: %v", err)
|
||||||
|
}
|
||||||
|
if got := int64(GetUint64(out, 16)); got != 7 {
|
||||||
|
t.Errorf("cleanAdd(3, 4) = %d, want 7", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPlainCallLoong64 is TestPlainCallArm64 for loong64.
|
||||||
|
func TestPlainCallLoong64(t *testing.T) {
|
||||||
|
requireArchHost(t, "loong64")
|
||||||
|
|
||||||
|
k, err := Load("../testdata/verify/abi_loong64.s")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load: %v", err)
|
||||||
|
}
|
||||||
|
t.Cleanup(k.Close)
|
||||||
|
|
||||||
|
args := make([]byte, 24)
|
||||||
|
PutUint64(args, 0, 3)
|
||||||
|
PutUint64(args, 8, 4)
|
||||||
|
out, err := k.CallFunc("cleanAdd", args)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CallFunc: %v", err)
|
||||||
|
}
|
||||||
|
if got := int64(GetUint64(out, 16)); got != 7 {
|
||||||
|
t.Errorf("cleanAdd(3, 4) = %d, want 7", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,6 +31,13 @@ TEXT ·leaveJIT(SB), NOSPLIT, $0-0
|
|||||||
MOVD savedLR(SB), R30 // restore link register
|
MOVD savedLR(SB), R30 // restore link register
|
||||||
RET // return to Go caller
|
RET // return to Go caller
|
||||||
|
|
||||||
|
// leaveRawAddr holds the raw .abi0 address of leaveJIT, read by call_arm64.go
|
||||||
|
// in preference to reflect.ValueOf(leaveJIT), which returns the address of the
|
||||||
|
// ABIInternal wrapper the linker interposes: the wrapper's prologue clobbers
|
||||||
|
// the saved-register window the JIT call depends on.
|
||||||
|
GLOBL ·leaveRawAddr(SB), NOPTR, $8
|
||||||
|
DATA ·leaveRawAddr(SB)/8, $·leaveJIT(SB)
|
||||||
|
|
||||||
// Package-level storage for saved registers.
|
// Package-level storage for saved registers.
|
||||||
GLOBL savedLR(SB), NOPTR, $8
|
GLOBL savedLR(SB), NOPTR, $8
|
||||||
GLOBL savedSP(SB), NOPTR, $8
|
GLOBL savedSP(SB), NOPTR, $8
|
||||||
|
|||||||
+15
-1
@@ -9,6 +9,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-devkit/arch"
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/asm"
|
"sourcedock.dev/petrbalvin/gasm-devkit/asm"
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
||||||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
||||||
@@ -46,7 +47,20 @@ func LoadSource(filename, src string) (*Kernel, error) {
|
|||||||
// LoadAST assembles a parsed AST file and maps the result into executable
|
// LoadAST assembles a parsed AST file and maps the result into executable
|
||||||
// memory.
|
// memory.
|
||||||
func LoadAST(file *ast.File) (*Kernel, error) {
|
func LoadAST(file *ast.File) (*Kernel, error) {
|
||||||
img, err := asm.AssembleFile(file)
|
// Assemble with the encoder the file's name suffix calls for: the amd64
|
||||||
|
// assembler is the default, the other architectures have their own.
|
||||||
|
var img *asm.Image
|
||||||
|
var err error
|
||||||
|
switch arch.FromFilename(file.Path) {
|
||||||
|
case arch.ARM64:
|
||||||
|
img, err = asm.AssembleFileARM64(file)
|
||||||
|
case arch.RISCV:
|
||||||
|
img, err = asm.AssembleFileRISCV(file)
|
||||||
|
case arch.LOONG64:
|
||||||
|
img, err = asm.AssembleFileLOONG64(file)
|
||||||
|
default:
|
||||||
|
img, err = asm.AssembleFile(file)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("verify: assemble: %w", err)
|
return nil, fmt.Errorf("verify: assemble: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user