Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/bof-corpus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,10 @@ jobs:
platform: linux/riscv64
qemu_platform: riscv64
dockerfile: testdata/docker/linux-riscv64-bof.Dockerfile
- goarch: loong64
platform: linux/loong64
qemu_platform: loong64
dockerfile: testdata/docker/linux-loong64-bof.Dockerfile
defaults:
run:
shell: bash
Expand Down Expand Up @@ -281,15 +285,38 @@ jobs:
with:
version: 0.14.0

- name: Prepare pinned linux/loong64 corpus target
if: matrix.goarch == 'loong64'
working-directory: runtime/test/corpus/Situational-Awareness-BOFs
run: |
set -euo pipefail
git apply "${GITHUB_WORKSPACE}/runtime/testdata/corpus/situational-awareness-linux-loong64.patch"
manifest_tmp="$(mktemp)"
jq '
.artifacts += [
.artifacts[]
| select(.os == "linux" and .arch == "arm64")
| .os = "linux"
| .arch = "loong64"
| .path |= sub("^dist/linux/arm64/"; "dist/linux/loong64/")
]
' testdata/e2e-manifest.json > "${manifest_tmp}"
mv "${manifest_tmp}" testdata/e2e-manifest.json

- name: Build and inspect target BOF corpus
working-directory: runtime/test/corpus/Situational-Awareness-BOFs
run: ./scripts/build-target.sh linux "$GOARCH"

- name: Set up QEMU
if: matrix.goarch != 'loong64'
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
with:
platforms: ${{ matrix.qemu_platform }}

- name: Install pinned LoongArch QEMU binfmt
if: matrix.goarch == 'loong64'
run: docker run --privileged --rm ghcr.io/loong64/binfmt@sha256:abe730caa95d5495aa1b888e2d77eadc41b3ea303ef75e2d82f8dad420b7f4d9 --install loong64

- name: Set up Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3

Expand Down
68 changes: 68 additions & 0 deletions .github/workflows/test-matrix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,71 @@ jobs:
docker build --platform linux/riscv64 -f testdata/docker/linux-riscv64-bof.Dockerfile -t reflektor-linux-riscv64-bof-ci .
docker run --rm --platform linux/riscv64 reflektor-linux-riscv64-bof-ci

linux-loong64-bof:
name: linux-loong64-full-qemu
runs-on: ubuntu-24.04
timeout-minutes: 180
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- name: Check out Situational Awareness BOF corpus
uses: actions/checkout@v4
with:
repository: sliverarmory/Situational-Awareness-BOFs
ref: 693174ff525595c5ec61b08fdccb5f057ad52c94
path: test/corpus/Situational-Awareness-BOFs
fetch-depth: 1
persist-credentials: false
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- uses: mlugg/setup-zig@v2
with:
version: 0.14.0
- name: Build and inspect linux/loong64 BOF fixtures
run: |
set -euo pipefail
mkdir -p test/bof-fixtures
REFLEKTOR_BOF_FIXTURE_DIR="${GITHUB_WORKSPACE}/test/bof-fixtures" \
CGO_ENABLED=0 go test ./integration \
-run '^TestBuildBOFMatrix$/^linux-loong64$' -count=1 -v | tee linux-loong64-fixture-build-test.log
if ! grep -Fq -- '--- PASS: TestBuildBOFMatrix ' linux-loong64-fixture-build-test.log; then
echo "The linux/loong64 BOF fixture build and inspection did not pass."
exit 1
fi
test -s test/bof-fixtures/fixture_linux_loong64.o
test -s test/bof-fixtures/options_fixture_linux_loong64.o
test -s test/bof-fixtures/async_fixture_linux_loong64.o
test -s test/bof-fixtures/stop_fixture_linux_loong64.o
- name: Prepare pinned linux/loong64 BOF corpus
working-directory: test/corpus/Situational-Awareness-BOFs
run: |
set -euo pipefail
git apply "${GITHUB_WORKSPACE}/testdata/corpus/situational-awareness-linux-loong64.patch"
manifest_tmp="$(mktemp)"
jq '
.artifacts += [
.artifacts[]
| select(.os == "linux" and .arch == "arm64")
| .os = "linux"
| .arch = "loong64"
| .path |= sub("^dist/linux/arm64/"; "dist/linux/loong64/")
]
' testdata/e2e-manifest.json > "${manifest_tmp}"
mv "${manifest_tmp}" testdata/e2e-manifest.json
./scripts/build-target.sh linux loong64
- name: Install pinned LoongArch QEMU binfmt
run: docker run --privileged --rm ghcr.io/loong64/binfmt@sha256:abe730caa95d5495aa1b888e2d77eadc41b3ea303ef75e2d82f8dad420b7f4d9 --install loong64
- uses: docker/setup-buildx-action@v3
- name: Run full linux/loong64 BOF and shared-library tests under QEMU
run: |
set -euo pipefail
docker build --platform linux/loong64 -f testdata/docker/linux-loong64-bof.Dockerfile -t reflektor-linux-loong64-bof-ci .
docker run --rm --platform linux/loong64 reflektor-linux-loong64-bof-ci

linux-ppc64le-bof:
name: linux-ppc64le-full-qemu
runs-on: ubuntu-24.04
Expand Down Expand Up @@ -875,6 +940,7 @@ jobs:
linux amd64
linux arm
linux arm64
linux loong64
linux ppc64le
linux riscv64
windows 386
Expand Down Expand Up @@ -918,6 +984,7 @@ jobs:
linux amd64
linux arm
linux arm64
linux loong64
linux ppc64le
linux riscv64
windows 386
Expand Down Expand Up @@ -946,6 +1013,7 @@ jobs:
linux amd64
linux arm
linux arm64
linux loong64
linux ppc64le
linux riscv64
windows 386
Expand Down
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ separately because it can cover a host without implying shared-library parity.
| Windows | `386`, `amd64`, `arm64` | PE (`.dll`) | Supported | In-memory PE loader |
| Darwin | `amd64`, `arm64` | Mach-O (`.dylib`, bundle) | Supported | Dyld4 root-image loader with system dependencies registered through public dyld; supports no-cgo builds and avoids temp-file legacy NS APIs. |
| FreeBSD | `amd64`, `arm64` | ELF (`.so`) | Supported | Pure Go in-memory ELF loader with FreeBSD rtld symbol resolution and real FreeBSD 15.1 QEMU runtime proof. C and Rust loading support both CGO modes; Go `c-shared` is supported on `amd64` and unavailable in the pinned Go toolchain on `arm64`. |
| Linux | `386`, `amd64`, `arm64`, `ppc64le`, `riscv64`; ARMv7 hard-float (`GOARM=7`) | ELF (`.so`) | Supported | Pure Go in-memory ELF loader (maps PT_LOAD segments, applies relocations, resolves externals from runtime modules/`dlsym`); no `memfd`, no `/dev/shm`, no temp-file disk writes. ARMv7, ppc64le, and riscv64 receive QEMU runtime proof. |
| Linux | `386`, `amd64`, `arm64`, `loong64`, `ppc64le`, `riscv64`; ARMv7 hard-float (`GOARM=7`) | ELF (`.so`) | Supported | Pure Go in-memory ELF loader (maps PT_LOAD segments, applies relocations, resolves externals from runtime modules/`dlsym`); no `memfd`, no `/dev/shm`, no temp-file disk writes. ARMv7, loong64, ppc64le, and riscv64 receive QEMU runtime proof. |
| NetBSD | `amd64`, `arm64` | ELF (`.so`) | Supported | In-memory ELF loader with NetBSD rtld symbol resolution and NetBSD 10.1 QEMU runtime proof on both architectures. C and Rust loading support both CGO modes; Go `c-shared` is unavailable in Go 1.26.6. Externally linked CGO executables require the per-binary PaX MPROTECT opt-out described below. |
| Other | - | - | Unsupported | Returns an explicit unsupported-platform error. |

Expand Down Expand Up @@ -117,7 +117,7 @@ subpackage without pulling in the root package's `memmod` shared-library
backend.

`bof.Load` accepts the native relocatable-object convention used by each host.
Linux/ARMv7 hard-float (`GOARM=7`), Linux/ppc64le, and Linux/riscv64 also have
Linux/ARMv7 hard-float (`GOARM=7`), Linux/loong64, Linux/ppc64le, and Linux/riscv64 also have
full root, `native`, recursive, C, Rust, and Go `c-shared` test coverage. ARMv5,
ARMv6, soft-float, and Thumb-compiled BOFs are not included in the ARM support
claim. PowerPC64 objects must use the little-endian ELFv2 ABI; ELFv1,
Expand All @@ -128,7 +128,7 @@ big-endian, NOTOC/P9NOTOC calls, and external tail branches are rejected.
| Windows `386`, `amd64`, `arm64` | COFF (`.o`) | `go(char *, int32)` using the Windows ABI |
| FreeBSD `amd64`, `arm64` | ELF `ET_REL` (`.o`) | `go(char *, int32)` using the host ABI |
| NetBSD `amd64`, `arm64` | ELF `ET_REL` (`.o`) | `go(char *, int32)` using the host ABI |
| Linux `386`, `amd64`, `arm64`, `ppc64le`, `riscv64`; ARMv7 hard-float (`GOARM=7`, ARM-state objects) | ELF `ET_REL` (`.o`) | `go(char *, int32)` using the host ABI |
| Linux `386`, `amd64`, `arm64`, `loong64`, `ppc64le`, `riscv64`; ARMv7 hard-float (`GOARM=7`, ARM-state objects) | ELF `ET_REL` (`.o`) | `go(char *, int32)` using the host ABI |
| Darwin `amd64`, `arm64` | Mach-O `MH_OBJECT` (`.o`); legacy ELF `ET_REL` accepted for compatibility | `go(char *, int32)` using the host ABI |

Compile native Darwin objects with Zig's `x86_64-macos-none` or
Expand All @@ -144,13 +144,13 @@ calls, close behavior, and all 25 objects from the pinned external BOF corpus
without CGO.
The NetBSD `amd64` and `arm64` rows run the same BOF lifecycles and all 25
pinned corpus objects in NetBSD 10.1 QEMU guests without CGO.
The ARMv7 hard-float, ppc64le, and riscv64 rows run BOFs without CGO under QEMU,
The ARMv7 hard-float, loong64, ppc64le, and riscv64 rows run BOFs without CGO under QEMU,
including generated fixtures, load options, repeated and concurrent execution,
close behavior, and the portable external BOF corpus. The same target images
exercise C and Rust shared libraries through root, `native`, recursive, and CLI
lifecycles with both CGO modes. Go `c-shared` exercises the cgo-enabled root,
recursive, and CLI lifecycles, while `native` proves its intentional Go-image
rejection. ARM uses a `linux/arm/v7` image; ppc64le and riscv64 use native Debian
rejection. ARM uses a `linux/arm/v7` image; loong64, ppc64le, and riscv64 use native Debian
images with checksummed target Go toolchains under user-mode emulation.

```go
Expand Down Expand Up @@ -262,7 +262,7 @@ lib, err := native.LoadLibrary(payload)
It exposes the same `CallExport`, `CallExportWithArgs`, and `Close` lifecycle
for byte-backed native images. On FreeBSD and Linux, its import graph
deliberately excludes the root loader's Go c-shared TLS reservation. FreeBSD
`amd64` and `arm64`, and Linux `amd64`, `arm64`, `ppc64le`, and `riscv64` use
`amd64` and `arm64`, and Linux `amd64`, `arm64`, `loong64`, `ppc64le`, and `riscv64` use
the PureGo call bridge in both CGO modes; ARMv7
uses its hard-float bridge, while Linux `386` uses Reflektor's integer-only
`runtime.cgocall` dispatcher. Valid Go c-shared payloads are rejected before
Expand Down Expand Up @@ -388,6 +388,8 @@ Linux cross-architecture Docker/QEMU harnesses:
`testdata/docker/run-linux-memmod-matrix.sh`
- ARMv7 BOFs and shared libraries: `testdata/docker/linux-bof.Dockerfile` and
`testdata/docker/run-linux-bof-tests.sh`
- LoongArch64 BOFs and shared libraries: `testdata/docker/linux-loong64-bof.Dockerfile` and
`testdata/docker/run-linux-loong64-bof-tests.sh`
- RISC-V BOFs and shared libraries: `testdata/docker/linux-riscv64-bof.Dockerfile` and
`testdata/docker/run-linux-riscv64-bof-tests.sh`
- PPC64LE BOFs and shared libraries: `testdata/docker/linux-ppc64le-bof.Dockerfile` and
Expand Down
2 changes: 1 addition & 1 deletion integration/bof_corpus_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ func validateBOFCorpusArguments(t *testing.T, location string, arguments []bofCo

func validBOFCorpusTarget(goos string, goarch string) bool {
switch goos + "/" + goarch {
case "darwin/amd64", "darwin/arm64", "freebsd/amd64", "freebsd/arm64", "netbsd/amd64", "netbsd/arm64", "linux/386", "linux/amd64", "linux/arm", "linux/arm64", "linux/ppc64le", "linux/riscv64", "windows/386", "windows/amd64", "windows/arm64":
case "darwin/amd64", "darwin/arm64", "freebsd/amd64", "freebsd/arm64", "netbsd/amd64", "netbsd/arm64", "linux/386", "linux/amd64", "linux/arm", "linux/arm64", "linux/loong64", "linux/ppc64le", "linux/riscv64", "windows/386", "windows/amd64", "windows/arm64":
return true
default:
return false
Expand Down
2 changes: 1 addition & 1 deletion integration/bof_options_callback_unix_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build (darwin && !ios && (amd64 || arm64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64)) || (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64))
//go:build (darwin && !ios && (amd64 || arm64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64)) || (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64))

package reflektor_test

Expand Down
13 changes: 10 additions & 3 deletions integration/bof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ var bofTargets = []bofTarget{
{goos: "linux", goarch: "arm64", zigTarget: "aarch64-linux-none", format: "elf"},
{goos: "linux", goarch: "ppc64le", zigTarget: "powerpc64le-linux-none", format: "elf"},
{goos: "linux", goarch: "riscv64", zigTarget: "riscv64-linux-none", format: "elf"},
{goos: "linux", goarch: "loong64", zigTarget: "loongarch64-linux-none", format: "elf"},
{goos: "netbsd", goarch: "amd64", zigTarget: "x86_64-netbsd-none", format: "elf"},
{goos: "netbsd", goarch: "arm64", zigTarget: "aarch64-netbsd-none", format: "elf"},
{goos: "windows", goarch: "386", zigTarget: "x86-windows-gnu", format: "coff"},
Expand Down Expand Up @@ -68,7 +69,7 @@ func TestBuildBOFMatrix(t *testing.T) {
// the primary fixture. Other targets continue to build it in their
// native LoadWithOptions execution test.
if target.goos == "freebsd" ||
(target.goos == "linux" && (target.goarch == "arm" || target.goarch == "ppc64le" || target.goarch == "riscv64")) {
(target.goos == "linux" && (target.goarch == "arm" || target.goarch == "ppc64le" || target.goarch == "riscv64" || target.goarch == "loong64")) {
optionsPath := buildBOFSource(t, outputDirectory, target, "options_fixture", "options_fixture.c")
validateBOFObject(t, optionsPath, target)
}
Expand Down Expand Up @@ -308,11 +309,11 @@ func validateBOFObject(t *testing.T, path string, target bofTarget) {
if file.Data != elf.ELFDATA2LSB {
t.Fatalf("ELF data encoding = %s, want ELFDATA2LSB", file.Data)
}
wantClass := map[string]elf.Class{"386": elf.ELFCLASS32, "amd64": elf.ELFCLASS64, "arm": elf.ELFCLASS32, "arm64": elf.ELFCLASS64, "ppc64le": elf.ELFCLASS64, "riscv64": elf.ELFCLASS64}[target.goarch]
wantClass := map[string]elf.Class{"386": elf.ELFCLASS32, "amd64": elf.ELFCLASS64, "arm": elf.ELFCLASS32, "arm64": elf.ELFCLASS64, "ppc64le": elf.ELFCLASS64, "riscv64": elf.ELFCLASS64, "loong64": elf.ELFCLASS64}[target.goarch]
if file.Class != wantClass {
t.Fatalf("ELF class = %s, want %s", file.Class, wantClass)
}
wantMachine := map[string]elf.Machine{"386": elf.EM_386, "amd64": elf.EM_X86_64, "arm": elf.EM_ARM, "arm64": elf.EM_AARCH64, "ppc64le": elf.EM_PPC64, "riscv64": elf.EM_RISCV}[target.goarch]
wantMachine := map[string]elf.Machine{"386": elf.EM_386, "amd64": elf.EM_X86_64, "arm": elf.EM_ARM, "arm64": elf.EM_AARCH64, "ppc64le": elf.EM_PPC64, "riscv64": elf.EM_RISCV, "loong64": elf.EM_LOONGARCH}[target.goarch]
if file.Machine != wantMachine {
t.Fatalf("ELF machine = %s, want %s", file.Machine, wantMachine)
}
Expand Down Expand Up @@ -344,6 +345,12 @@ func validateBOFObject(t *testing.T, path string, target bofTarget) {
t.Fatalf("ELF/riscv64 flags = %#08x, want LP64D with optional RVC and no RVE, RVTSO, or unknown flags", flags)
}
}
if target.goarch == "loong64" {
flags := binary.LittleEndian.Uint32(image[48:52])
if flags != 0x43 {
t.Fatalf("ELF/loong64 flags = %#08x, want LP64D ABI v1 (0x43)", flags)
}
}
case "coff":
file, parseErr := pe.NewFile(bytes.NewReader(image))
if parseErr != nil {
Expand Down
2 changes: 1 addition & 1 deletion integration/native_load_go_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build (darwin && (amd64 || arm64)) || (freebsd && amd64) || (linux && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (windows && (386 || amd64 || arm64))
//go:build (darwin && (amd64 || arm64)) || (freebsd && amd64) || (linux && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (windows && (386 || amd64 || arm64))

package reflektor_test

Expand Down
2 changes: 1 addition & 1 deletion integration/native_package_linux_cgo_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build linux && cgo && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)
//go:build linux && cgo && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)

package reflektor_test

Expand Down
5 changes: 3 additions & 2 deletions integration/native_package_linux_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build linux && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)
//go:build linux && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)

package reflektor_test

Expand Down Expand Up @@ -122,7 +122,7 @@ func assertNativeLinuxBridgeFiles(t *testing.T, cgoEnabled string, modulePath st
if slices.Contains(linuxBackend.GoFiles, "call_386.go") || slices.Contains(linuxBackend.GoFiles, "call_64.go") {
t.Fatalf("linux/arm selected another architecture's dispatcher: %v", linuxBackend.GoFiles)
}
case "amd64", "arm64", "riscv64":
case "amd64", "arm64", "riscv64", "loong64":
if !slices.Contains(linuxBackend.GoFiles, "call_64.go") {
t.Fatalf("linux/%s GoFiles = %v, want purego call_64.go", runtime.GOARCH, linuxBackend.GoFiles)
}
Expand All @@ -133,6 +133,7 @@ func assertNativeLinuxBridgeFiles(t *testing.T, cgoEnabled string, modulePath st
"amd64": "cache_flush_x86.go",
"arm64": "cache_flush_arm64.go",
"riscv64": "cache_flush_riscv64.go",
"loong64": "cache_flush_loong64.go",
}[runtime.GOARCH]
if !slices.Contains(linuxBackend.GoFiles, wantCacheFile) {
t.Fatalf("linux/%s GoFiles = %v, want %s", runtime.GOARCH, linuxBackend.GoFiles, wantCacheFile)
Expand Down
2 changes: 1 addition & 1 deletion integration/native_package_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build (darwin && (amd64 || arm64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64)) || (linux && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (windows && (386 || amd64 || arm64))
//go:build (darwin && (amd64 || arm64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64)) || (linux && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (windows && (386 || amd64 || arm64))

package reflektor_test

Expand Down
Loading
Loading