2026-08-30 11:27:54 +02:00
|
|
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
|
|
|
|
|
|
package verify
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"runtime"
|
|
|
|
|
"testing"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// requireHost skips the test unless the test binary runs on the named
|
|
|
|
|
// architecture: the JIT executes native code, so a kernel assembled for
|
|
|
|
|
// arm64 only runs on an arm64 host.
|
|
|
|
|
func requireHost(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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestABIArm64(t *testing.T) {
|
|
|
|
|
requireHost(t, "arm64")
|
|
|
|
|
|
|
|
|
|
k, err := Load("../testdata/verify/abi_arm64.s")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Load: %v", err)
|
|
|
|
|
}
|
|
|
|
|
t.Cleanup(k.Close)
|
|
|
|
|
|
|
|
|
|
// cleanAdd preserves everything and computes correctly.
|
|
|
|
|
args := make([]byte, 24)
|
|
|
|
|
PutUint64(args, 0, 3)
|
|
|
|
|
PutUint64(args, 8, 4)
|
|
|
|
|
out, report, err := k.CallFuncChecked("cleanAdd", args)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("CallFuncChecked: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if got := int64(GetUint64(out, 16)); got != 7 {
|
|
|
|
|
t.Errorf("cleanAdd(3, 4) = %d, want 7", got)
|
|
|
|
|
}
|
|
|
|
|
if !report.OK() {
|
|
|
|
|
t.Errorf("cleanAdd: %s", report)
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-20 00:57:02 +02:00
|
|
|
// dirtyFP clobbers the frame pointer (R29). The kernel passes its
|
|
|
|
|
// argument through, so the argument must carry the expected value the
|
|
|
|
|
// way the amd64 twin test seeds it.
|
|
|
|
|
args = make([]byte, 16)
|
|
|
|
|
PutUint64(args, 0, 0x1234)
|
|
|
|
|
out, report, err = k.CallFuncChecked("dirtyFP", args)
|
2026-08-30 11:27:54 +02:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("CallFuncChecked: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if got := int64(GetUint64(out, 8)); got != 0x1234 {
|
|
|
|
|
t.Errorf("dirtyFP returned %d, want %d", got, int64(0x1234))
|
|
|
|
|
}
|
|
|
|
|
if !report.FPClobbered {
|
|
|
|
|
t.Error("dirtyFP: expected frame pointer clobbered, but report says clean")
|
|
|
|
|
}
|
|
|
|
|
if report.GClobbered {
|
|
|
|
|
t.Errorf("dirtyFP: only R29 should be clobbered: %s", report)
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-20 00:57:02 +02:00
|
|
|
// dirtyG clobbers the goroutine pointer (R28) and still returns its
|
|
|
|
|
// argument.
|
|
|
|
|
args = make([]byte, 16)
|
|
|
|
|
PutUint64(args, 0, 0x5678)
|
|
|
|
|
out, report, err = k.CallFuncChecked("dirtyG", args)
|
2026-08-30 11:27:54 +02:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("CallFuncChecked: %v", err)
|
|
|
|
|
}
|
2026-09-20 00:57:02 +02:00
|
|
|
if got := int64(GetUint64(out, 8)); got != 0x5678 {
|
|
|
|
|
t.Errorf("dirtyG returned %d, want %d", got, int64(0x5678))
|
|
|
|
|
}
|
2026-08-30 11:27:54 +02:00
|
|
|
if !report.GClobbered {
|
|
|
|
|
t.Error("dirtyG: expected g clobbered, but report says clean")
|
|
|
|
|
}
|
|
|
|
|
if report.FPClobbered {
|
|
|
|
|
t.Errorf("dirtyG: only R28 should be clobbered: %s", report)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestABIRiscv64(t *testing.T) {
|
|
|
|
|
requireHost(t, "riscv64")
|
|
|
|
|
|
|
|
|
|
k, err := Load("../testdata/verify/abi_riscv64.s")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Load: %v", err)
|
|
|
|
|
}
|
|
|
|
|
t.Cleanup(k.Close)
|
|
|
|
|
|
|
|
|
|
// cleanAdd preserves g and computes correctly.
|
|
|
|
|
args := make([]byte, 24)
|
|
|
|
|
PutUint64(args, 0, 3)
|
|
|
|
|
PutUint64(args, 8, 4)
|
|
|
|
|
out, report, err := k.CallFuncChecked("cleanAdd", args)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("CallFuncChecked: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if got := int64(GetUint64(out, 16)); got != 7 {
|
|
|
|
|
t.Errorf("cleanAdd(3, 4) = %d, want 7", got)
|
|
|
|
|
}
|
|
|
|
|
if !report.OK() {
|
|
|
|
|
t.Errorf("cleanAdd: %s", report)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// dirtyG clobbers the goroutine pointer (X27).
|
|
|
|
|
_, report, err = k.CallFuncChecked("dirtyG", make([]byte, 16))
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("CallFuncChecked: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if !report.GClobbered {
|
|
|
|
|
t.Error("dirtyG: expected g clobbered, but report says clean")
|
|
|
|
|
}
|
|
|
|
|
if report.FPClobbered || report.RedZoneHit {
|
|
|
|
|
t.Errorf("dirtyG: unexpected additional violations: %s", report)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestABILoong64(t *testing.T) {
|
|
|
|
|
requireHost(t, "loong64")
|
|
|
|
|
|
|
|
|
|
k, err := Load("../testdata/verify/abi_loong64.s")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Load: %v", err)
|
|
|
|
|
}
|
|
|
|
|
t.Cleanup(k.Close)
|
|
|
|
|
|
|
|
|
|
// cleanAdd preserves g and computes correctly.
|
|
|
|
|
args := make([]byte, 24)
|
|
|
|
|
PutUint64(args, 0, 3)
|
|
|
|
|
PutUint64(args, 8, 4)
|
|
|
|
|
out, report, err := k.CallFuncChecked("cleanAdd", args)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("CallFuncChecked: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if got := int64(GetUint64(out, 16)); got != 7 {
|
|
|
|
|
t.Errorf("cleanAdd(3, 4) = %d, want 7", got)
|
|
|
|
|
}
|
|
|
|
|
if !report.OK() {
|
|
|
|
|
t.Errorf("cleanAdd: %s", report)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// dirtyG clobbers the goroutine pointer (R22).
|
|
|
|
|
_, report, err = k.CallFuncChecked("dirtyG", make([]byte, 16))
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("CallFuncChecked: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if !report.GClobbered {
|
|
|
|
|
t.Error("dirtyG: expected g clobbered, but report says clean")
|
|
|
|
|
}
|
|
|
|
|
if report.FPClobbered || report.RedZoneHit {
|
|
|
|
|
t.Errorf("dirtyG: unexpected additional violations: %s", report)
|
|
|
|
|
}
|
|
|
|
|
}
|