diff --git a/.github/workflows/bof-corpus.yml b/.github/workflows/bof-corpus.yml index ecf3ed4..99d8073 100644 --- a/.github/workflows/bof-corpus.yml +++ b/.github/workflows/bof-corpus.yml @@ -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 @@ -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 diff --git a/.github/workflows/test-matrix.yml b/.github/workflows/test-matrix.yml index dacd19a..8004668 100644 --- a/.github/workflows/test-matrix.yml +++ b/.github/workflows/test-matrix.yml @@ -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 @@ -875,6 +940,7 @@ jobs: linux amd64 linux arm linux arm64 + linux loong64 linux ppc64le linux riscv64 windows 386 @@ -918,6 +984,7 @@ jobs: linux amd64 linux arm linux arm64 + linux loong64 linux ppc64le linux riscv64 windows 386 @@ -946,6 +1013,7 @@ jobs: linux amd64 linux arm linux arm64 + linux loong64 linux ppc64le linux riscv64 windows 386 diff --git a/README.md b/README.md index 736d972..b6a5485 100644 --- a/README.md +++ b/README.md @@ -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. | @@ -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, @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/integration/bof_corpus_test.go b/integration/bof_corpus_test.go index 8f0dc09..a547502 100644 --- a/integration/bof_corpus_test.go +++ b/integration/bof_corpus_test.go @@ -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 diff --git a/integration/bof_options_callback_unix_test.go b/integration/bof_options_callback_unix_test.go index 4ed69d2..be4d253 100644 --- a/integration/bof_options_callback_unix_test.go +++ b/integration/bof_options_callback_unix_test.go @@ -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 diff --git a/integration/bof_test.go b/integration/bof_test.go index c52444f..7b3794f 100644 --- a/integration/bof_test.go +++ b/integration/bof_test.go @@ -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"}, @@ -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) } @@ -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) } @@ -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 { diff --git a/integration/native_load_go_test.go b/integration/native_load_go_test.go index 4db2dc4..0725937 100644 --- a/integration/native_load_go_test.go +++ b/integration/native_load_go_test.go @@ -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 diff --git a/integration/native_package_linux_cgo_test.go b/integration/native_package_linux_cgo_test.go index bcc2a6b..48c1a1c 100644 --- a/integration/native_package_linux_cgo_test.go +++ b/integration/native_package_linux_cgo_test.go @@ -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 diff --git a/integration/native_package_linux_test.go b/integration/native_package_linux_test.go index cdc3491..0f95197 100644 --- a/integration/native_package_linux_test.go +++ b/integration/native_package_linux_test.go @@ -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 @@ -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) } @@ -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) diff --git a/integration/native_package_test.go b/integration/native_package_test.go index 1abd09c..ec16777 100644 --- a/integration/native_package_test.go +++ b/integration/native_package_test.go @@ -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 diff --git a/integration/platform_support_test.go b/integration/platform_support_test.go index 107554d..346c55c 100644 --- a/integration/platform_support_test.go +++ b/integration/platform_support_test.go @@ -74,7 +74,7 @@ func TestPlatformSupportManifest(t *testing.T) { if !slices.Equal(manifest.BOFFormatValues, []string{"elf", "macho", "coff", "xcoff", "undefined"}) { t.Fatalf("bof_format_values = %v, want the declared native object formats", manifest.BOFFormatValues) } - if !slices.Equal(manifest.RunnerValues, []string{"github-native", "github-container", "github-qemu-freebsd-amd64", "github-qemu-freebsd-arm64", "github-qemu-arm-v7", "github-qemu-ppc64le", "github-qemu-riscv64", "github-qemu-netbsd-amd64", "github-qemu-netbsd-arm64", "none"}) { + if !slices.Equal(manifest.RunnerValues, []string{"github-native", "github-container", "github-qemu-freebsd-amd64", "github-qemu-freebsd-arm64", "github-qemu-arm-v7", "github-qemu-loong64", "github-qemu-ppc64le", "github-qemu-riscv64", "github-qemu-netbsd-amd64", "github-qemu-netbsd-arm64", "none"}) { t.Fatalf("runner_values = %v, want the checked-in CI proof types", manifest.RunnerValues) } @@ -186,6 +186,7 @@ func TestPlatformSupportManifest(t *testing.T) { }, goCSharedRuntimeTargets) sawLinuxARM := false + sawLinuxLOONG64 := false sawLinuxPPC64LE := false sawLinuxRISCV64 := false sawFreeBSDAMD64 := false @@ -247,6 +248,15 @@ func TestPlatformSupportManifest(t *testing.T) { } sawLinuxRISCV64 = true } + if target.GOOS == "linux" && target.GOARCH == "loong64" { + if target.BOF != "runtime-emulated" || !target.BOFCGOFree || + target.RootShared != "runtime-emulated" || target.NativeShared != "runtime-emulated" || + target.RecursiveShared != "runtime-emulated" || target.GoCShared != "runtime-emulated" || + !target.SharedCGOFree || target.Runner != "github-qemu-loong64" { + t.Fatalf("linux/loong64 capabilities = %#v, want full QEMU runtime support with Go c-shared and CGO-free C/Rust loading", target) + } + sawLinuxLOONG64 = true + } if target.GOOS == "linux" && target.GOARCH == "ppc64le" { if target.BOF != "runtime-emulated" || !target.BOFCGOFree || target.RootShared != "runtime-emulated" || target.NativeShared != "runtime-emulated" || @@ -263,8 +273,8 @@ func TestPlatformSupportManifest(t *testing.T) { if !sawNetBSDAMD64 || !sawNetBSDARM64 { t.Fatalf("platform support manifest emulated NetBSD targets: amd64=%v arm64=%v, want both", sawNetBSDAMD64, sawNetBSDARM64) } - if !sawLinuxARM || !sawLinuxPPC64LE || !sawLinuxRISCV64 { - t.Fatalf("platform support manifest emulated Linux targets: arm=%v ppc64le=%v riscv64=%v, want all three", sawLinuxARM, sawLinuxPPC64LE, sawLinuxRISCV64) + if !sawLinuxARM || !sawLinuxLOONG64 || !sawLinuxPPC64LE || !sawLinuxRISCV64 { + t.Fatalf("platform support manifest emulated Linux targets: arm=%v loong64=%v ppc64le=%v riscv64=%v, want all four", sawLinuxARM, sawLinuxLOONG64, sawLinuxPPC64LE, sawLinuxRISCV64) } } diff --git a/integration/sharedlib_build_go_test.go b/integration/sharedlib_build_go_test.go index 0bd15a5..ae87f8a 100644 --- a/integration/sharedlib_build_go_test.go +++ b/integration/sharedlib_build_go_test.go @@ -111,6 +111,8 @@ func zigTargetFor(goos string, goarch string) (string, bool) { return "powerpc64le-linux-gnu", true case goos == "linux" && goarch == "riscv64": return "riscv64-linux-gnu", true + case goos == "linux" && goarch == "loong64": + return "loongarch64-linux-gnu", true case goos == "windows" && goarch == "386": return "x86-windows-gnu", true case goos == "windows" && goarch == "amd64": diff --git a/integration/sharedlib_build_rust_test.go b/integration/sharedlib_build_rust_test.go index ded69d4..b019c96 100644 --- a/integration/sharedlib_build_rust_test.go +++ b/integration/sharedlib_build_rust_test.go @@ -89,6 +89,8 @@ func rustTargetFor(goos string, goarch string) (string, bool) { return "powerpc64le-unknown-linux-gnu", true case goos == "linux" && goarch == "riscv64": return "riscv64gc-unknown-linux-gnu", true + case goos == "linux" && goarch == "loong64": + return "loongarch64-unknown-linux-gnu", true case goos == "windows" && goarch == "386": return "i686-pc-windows-msvc", true case goos == "windows" && goarch == "amd64": diff --git a/integration/sharedlib_load_args_purego_nocgo_test.go b/integration/sharedlib_load_args_purego_nocgo_test.go index b547f6f..a182f17 100644 --- a/integration/sharedlib_load_args_purego_nocgo_test.go +++ b/integration/sharedlib_load_args_purego_nocgo_test.go @@ -1,4 +1,4 @@ -//go:build !cgo && ((darwin && (amd64 || arm64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64)) || (linux && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64))) +//go:build !cgo && ((darwin && (amd64 || arm64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64)) || (linux && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64))) package reflektor_test diff --git a/integration/sharedlib_load_args_test.go b/integration/sharedlib_load_args_test.go index 412b296..9ef6fb8 100644 --- a/integration/sharedlib_load_args_test.go +++ b/integration/sharedlib_load_args_test.go @@ -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 diff --git a/integration/sharedlib_load_linux_go_test.go b/integration/sharedlib_load_linux_go_test.go index 6622238..38bf604 100644 --- a/integration/sharedlib_load_linux_go_test.go +++ b/integration/sharedlib_load_linux_go_test.go @@ -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 diff --git a/integration/sharedlib_load_linux_rust_test.go b/integration/sharedlib_load_linux_rust_test.go index 15b2acd..2e65902 100644 --- a/integration/sharedlib_load_linux_rust_test.go +++ b/integration/sharedlib_load_linux_rust_test.go @@ -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 diff --git a/integration/sharedlib_load_linux_test.go b/integration/sharedlib_load_linux_test.go index cbe27b0..c7df4c1 100644 --- a/integration/sharedlib_load_linux_test.go +++ b/integration/sharedlib_load_linux_test.go @@ -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 diff --git a/integration/sharedlib_load_recursive_assert_linux_test.go b/integration/sharedlib_load_recursive_assert_linux_test.go index 41d2789..c46006d 100644 --- a/integration/sharedlib_load_recursive_assert_linux_test.go +++ b/integration/sharedlib_load_recursive_assert_linux_test.go @@ -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 diff --git a/integration/sharedlib_load_recursive_go_test.go b/integration/sharedlib_load_recursive_go_test.go index fc6088c..c456ae9 100644 --- a/integration/sharedlib_load_recursive_go_test.go +++ b/integration/sharedlib_load_recursive_go_test.go @@ -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 diff --git a/integration/sharedlib_load_recursive_guard_other_test.go b/integration/sharedlib_load_recursive_guard_other_test.go index d452ec6..1d21f60 100644 --- a/integration/sharedlib_load_recursive_guard_other_test.go +++ b/integration/sharedlib_load_recursive_guard_other_test.go @@ -1,4 +1,4 @@ -//go:build (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64)) || (linux && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (windows && (386 || amd64 || arm64)) +//go:build (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64)) || (linux && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (windows && (386 || amd64 || arm64)) package reflektor_test diff --git a/integration/sharedlib_load_recursive_modes_test.go b/integration/sharedlib_load_recursive_modes_test.go index f253cb7..5f48dfb 100644 --- a/integration/sharedlib_load_recursive_modes_test.go +++ b/integration/sharedlib_load_recursive_modes_test.go @@ -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 diff --git a/integration/sharedlib_load_recursive_test.go b/integration/sharedlib_load_recursive_test.go index 5f0274c..351a2e9 100644 --- a/integration/sharedlib_load_recursive_test.go +++ b/integration/sharedlib_load_recursive_test.go @@ -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 diff --git a/integration/sharedlib_matrix_test.go b/integration/sharedlib_matrix_test.go index b7e0d89..4c063f9 100644 --- a/integration/sharedlib_matrix_test.go +++ b/integration/sharedlib_matrix_test.go @@ -27,6 +27,7 @@ var sharedLibTargets = []sharedLibTarget{ {goos: "linux", goarch: "arm64", ext: "so", fileProbe: "ARM aarch64"}, {goos: "linux", goarch: "ppc64le", ext: "so", fileProbe: "64-bit PowerPC"}, {goos: "linux", goarch: "riscv64", ext: "so", fileProbe: "UCB RISC-V"}, + {goos: "linux", goarch: "loong64", ext: "so", fileProbe: "LoongArch"}, {goos: "netbsd", goarch: "amd64", ext: "so", fileProbe: "x86-64"}, {goos: "netbsd", goarch: "arm64", ext: "so", fileProbe: "ARM aarch64"}, {goos: "windows", goarch: "386", ext: "dll", fileProbe: "Intel 80386"}, @@ -134,6 +135,8 @@ func buildCSharedLib(t *testing.T, outDir string, goos string, goarch string, ba zigTarget, ext = "powerpc64le-linux-gnu", "so" case goos == "linux" && goarch == "riscv64": zigTarget, ext = "riscv64-linux-gnu", "so" + case goos == "linux" && goarch == "loong64": + zigTarget, ext = "loongarch64-linux-gnu", "so" case goos == "windows" && goarch == "386": zigTarget, ext = "x86-windows-gnu", "dll" case goos == "windows" && goarch == "amd64": diff --git a/internal/bofloader/callbacks_unix.go b/internal/bofloader/callbacks_unix.go index eb675f8..3564d56 100644 --- a/internal/bofloader/callbacks_unix.go +++ b/internal/bofloader/callbacks_unix.go @@ -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 bofloader diff --git a/internal/bofloader/callbacks_unsupported.go b/internal/bofloader/callbacks_unsupported.go index 2ecb8bd..ca59f3e 100644 --- a/internal/bofloader/callbacks_unsupported.go +++ b/internal/bofloader/callbacks_unsupported.go @@ -1,4 +1,4 @@ -//go:build android || ios || (darwin && !ios && !(amd64 || arm64)) || (freebsd && !(amd64 || arm64)) || (netbsd && !(amd64 || arm64)) || (linux && !android && !(386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (windows && !(386 || amd64 || arm64)) || (!darwin && !freebsd && !netbsd && !linux && !windows) +//go:build android || ios || (darwin && !ios && !(amd64 || arm64)) || (freebsd && !(amd64 || arm64)) || (netbsd && !(amd64 || arm64)) || (linux && !android && !(386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (windows && !(386 || amd64 || arm64)) || (!darwin && !freebsd && !netbsd && !linux && !windows) package bofloader diff --git a/internal/bofloader/invoke_unix_64.go b/internal/bofloader/invoke_unix_64.go index 8942232..5d0c181 100644 --- a/internal/bofloader/invoke_unix_64.go +++ b/internal/bofloader/invoke_unix_64.go @@ -1,4 +1,4 @@ -//go:build (darwin && !ios && (amd64 || arm64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64)) || (linux && !android && (amd64 || arm64 || ppc64le || riscv64)) +//go:build (darwin && !ios && (amd64 || arm64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64)) || (linux && !android && (amd64 || arm64 || ppc64le || riscv64 || loong64)) package bofloader diff --git a/internal/bofloader/invoke_unsupported.go b/internal/bofloader/invoke_unsupported.go index 38fbe7f..257fdfe 100644 --- a/internal/bofloader/invoke_unsupported.go +++ b/internal/bofloader/invoke_unsupported.go @@ -1,4 +1,4 @@ -//go:build android || ios || (darwin && !ios && !(amd64 || arm64)) || (freebsd && !(amd64 || arm64)) || (netbsd && !(amd64 || arm64)) || (linux && !android && !(386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (windows && !(386 || amd64 || arm64)) || (!darwin && !freebsd && !netbsd && !linux && !windows) +//go:build android || ios || (darwin && !ios && !(amd64 || arm64)) || (freebsd && !(amd64 || arm64)) || (netbsd && !(amd64 || arm64)) || (linux && !android && !(386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (windows && !(386 || amd64 || arm64)) || (!darwin && !freebsd && !netbsd && !linux && !windows) package bofloader diff --git a/internal/bofloader/memory_flush_linux_loong64.go b/internal/bofloader/memory_flush_linux_loong64.go new file mode 100644 index 0000000..7a511ce --- /dev/null +++ b/internal/bofloader/memory_flush_linux_loong64.go @@ -0,0 +1,13 @@ +//go:build linux && !android && loong64 + +package bofloader + +import "github.com/sliverarmory/reflektor/internal/loong64cache" + +func (region *memoryRegion) flushInstructionCache(offset, length int) error { + if _, err := region.rangeBytes(offset, length); err != nil || length == 0 { + return err + } + loong64cache.Flush() + return nil +} diff --git a/internal/bofloader/memory_unix.go b/internal/bofloader/memory_unix.go index 883770a..db19df0 100644 --- a/internal/bofloader/memory_unix.go +++ b/internal/bofloader/memory_unix.go @@ -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 bofloader diff --git a/internal/bofloader/memory_unsupported.go b/internal/bofloader/memory_unsupported.go index 81909aa..d1b8a72 100644 --- a/internal/bofloader/memory_unsupported.go +++ b/internal/bofloader/memory_unsupported.go @@ -1,4 +1,4 @@ -//go:build android || ios || (darwin && !ios && !(amd64 || arm64)) || (freebsd && !(amd64 || arm64)) || (netbsd && !(amd64 || arm64)) || (linux && !android && !(386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (windows && !(386 || amd64 || arm64)) || (!darwin && !freebsd && !netbsd && !linux && !windows) +//go:build android || ios || (darwin && !ios && !(amd64 || arm64)) || (freebsd && !(amd64 || arm64)) || (netbsd && !(amd64 || arm64)) || (linux && !android && !(386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (windows && !(386 || amd64 || arm64)) || (!darwin && !freebsd && !netbsd && !linux && !windows) package bofloader diff --git a/internal/bofloader/parser_elf.go b/internal/bofloader/parser_elf.go index 5398905..caca0e7 100644 --- a/internal/bofloader/parser_elf.go +++ b/internal/bofloader/parser_elf.go @@ -21,6 +21,8 @@ const ( riscvELFTSO = 0x00000010 riscvELFKnownFlags = riscvELFRVC | riscvELFFloatABIMask | riscvELFRVE | riscvELFTSO + loongELFLP64DABIv1 = 0x43 + ppc64ELFABI = 0x00000003 ppc64ELFABI2 = 0x00000002 ppc64LocalEntryMask = 0xe0 @@ -79,6 +81,12 @@ func parseELF(data []byte) (object *objectFile, err error) { return nil, fmt.Errorf("bofloader: ELF/riscv64 BOF has unknown flags %#08x", unknown) } arch = "riscv64" + case file.Machine == elf.EM_LOONGARCH && file.Class == elf.ELFCLASS64: + flags := binary.LittleEndian.Uint32(data[48:52]) + if flags != loongELFLP64DABIv1 { + return nil, fmt.Errorf("bofloader: ELF/loong64 BOFs require LP64D ABI v1 flags, got %#08x", flags) + } + arch = "loong64" case file.Machine == elf.EM_PPC64 && file.Class == elf.ELFCLASS64: flags := binary.LittleEndian.Uint32(data[48:52]) if flags&ppc64ELFABI != ppc64ELFABI2 || flags&^uint32(ppc64ELFABI) != 0 { @@ -271,7 +279,7 @@ func parseELF(data []byte) (object *objectFile, err error) { if !result.sections[targetSection].mapped { continue } - if (arch == "riscv64" || arch == "ppc64le") && section.Type != elf.SHT_RELA { + if (arch == "riscv64" || arch == "ppc64le" || arch == "loong64") && section.Type != elf.SHT_RELA { return nil, fmt.Errorf("bofloader: ELF/%s relocation section %q must use RELA encoding", arch, section.Name) } contents, dataErr := section.Data() diff --git a/internal/bofloader/reloc_elf.go b/internal/bofloader/reloc_elf.go index 294c627..2f1d10a 100644 --- a/internal/bofloader/reloc_elf.go +++ b/internal/bofloader/reloc_elf.go @@ -60,6 +60,18 @@ func elfRelocationWidth(arch string, typeID uint32) (int, bool, error) { // padding and instruction sequence remain valid as emitted. return 0, true, nil } + case "loong64": + switch elf.R_LARCH(typeID) { + case elf.R_LARCH_64, elf.R_LARCH_64_PCREL, elf.R_LARCH_CALL36: + return 8, false, nil + case elf.R_LARCH_32, elf.R_LARCH_32_PCREL, + elf.R_LARCH_PCALA_HI20, elf.R_LARCH_PCALA_LO12, + elf.R_LARCH_GOT_PC_HI20, elf.R_LARCH_GOT_PC_LO12, + elf.R_LARCH_B26: + return 4, false, nil + case elf.R_LARCH_RELAX, elf.R_LARCH_ALIGN: + return 0, true, nil + } case "ppc64le": switch elf.R_PPC64(typeID) { case elf.R_PPC64_ADDR64: @@ -96,6 +108,9 @@ func elfRelocationNeedsGOT(arch string, typeID uint32) bool { } case "riscv64": return elf.R_RISCV(typeID) == elf.R_RISCV_GOT_HI20 + case "loong64": + return elf.R_LARCH(typeID) == elf.R_LARCH_GOT_PC_HI20 || + elf.R_LARCH(typeID) == elf.R_LARCH_GOT_PC_LO12 case "ppc64le": return false } @@ -114,6 +129,8 @@ func applyELFRelocation(object *objectFile, relocation objectRelocation, locatio return applyELFARM64Relocation(relocation, location, place, linked) case "riscv64": return applyELFRISCV64Relocation(object, relocation, location, place, linked, externals) + case "loong64": + return applyELFLoong64Relocation(relocation, location, place, linked) case "ppc64le": return applyELFPPC64LERelocation(object, relocation, location, place, linked) default: diff --git a/internal/bofloader/reloc_loong64_elf.go b/internal/bofloader/reloc_loong64_elf.go new file mode 100644 index 0000000..a30c8e1 --- /dev/null +++ b/internal/bofloader/reloc_loong64_elf.go @@ -0,0 +1,159 @@ +package bofloader + +import ( + "debug/elf" + "encoding/binary" + "fmt" +) + +// LoongArch instruction relocations below follow the LP64D ELF psABI. The +// loader owns one contiguous image, so PCALA and GOT targets remain within +// the signed 32-bit page span even when host callbacks are far away. +func applyELFLoong64Relocation(relocation objectRelocation, location []byte, place uint64, linked linkedSymbol) error { + if !relocation.hasAdd { + return fmt.Errorf("LoongArch relocations require RELA encoding") + } + target := directLinkedAddress(linked) + switch elf.R_LARCH(relocation.typeID) { + case elf.R_LARCH_64: + value, err := addSigned(target, relocation.addend) + if err != nil { + return err + } + putUint64(location, value) + return nil + case elf.R_LARCH_32: + value, err := addSigned(target, relocation.addend) + if err != nil { + return err + } + return putUint32(location, value) + case elf.R_LARCH_64_PCREL: + delta, err := relativeValue(target, relocation.addend, place, 0) + if err != nil { + return err + } + putUint64(location, uint64(delta)) + return nil + case elf.R_LARCH_32_PCREL: + delta, err := relativeValue(target, relocation.addend, place, 0) + if err != nil { + return err + } + return putInt32(location, delta) + case elf.R_LARCH_CALL36: + if linked.external != nil && relocation.addend != 0 { + return fmt.Errorf("external LoongArch CALL36 relocation must have a zero addend") + } + return patchLoongCall36(location, thunkLinkedAddress(linked), place, relocation.addend) + case elf.R_LARCH_B26: + return patchLoongBranch26(location, thunkLinkedAddress(linked), place, relocation.addend) + case elf.R_LARCH_PCALA_HI20: + return patchLoongPageHigh20(location, target, place, relocation.addend) + case elf.R_LARCH_PCALA_LO12: + return patchLoongLow12(location, target, relocation.addend) + case elf.R_LARCH_GOT_PC_HI20, elf.R_LARCH_GOT_PC_LO12: + got, err := gotLinkedAddress(linked) + if err != nil { + return err + } + if relocation.addend != 0 { + return fmt.Errorf("LoongArch GOT relocation must have a zero addend") + } + if elf.R_LARCH(relocation.typeID) == elf.R_LARCH_GOT_PC_HI20 { + return patchLoongPageHigh20(location, got, place, 0) + } + return patchLoongLow12(location, got, 0) + default: + return fmt.Errorf("unsupported ELF/loong64 relocation") + } +} + +func patchLoongCall36(location []byte, target, place uint64, addend int64) error { + if len(location) != 8 { + return fmt.Errorf("LoongArch CALL36 requires an 8-byte instruction pair") + } + highWord := binary.LittleEndian.Uint32(location[:4]) + lowWord := binary.LittleEndian.Uint32(location[4:]) + if highWord&0xfe000000 != 0x1e000000 || lowWord&0xfc000000 != 0x4c000000 || + (lowWord>>5)&0x1f != highWord&0x1f { + return fmt.Errorf("LoongArch CALL36 requires pcaddu18i and matching jirl, got %#08x/%#08x", highWord, lowWord) + } + delta, err := relativeValue(target, addend, place, 0) + if err != nil { + return err + } + if delta&3 != 0 || delta < -(1<<37) || delta > (1<<37)-4 { + return fmt.Errorf("LoongArch CALL36 displacement %d exceeds aligned signed 38-bit range", delta) + } + // The two instructions encode offset bits [37:18] and [17:2]. + // The high part is rounded because JIRL sign-extends its low 16 bits. + units := delta >> 2 + high := (units + (1 << 15)) >> 16 + highWord = (highWord &^ (0xfffff << 5)) | ((uint32(high) & 0xfffff) << 5) + lowWord = (lowWord &^ (0xffff << 10)) | ((uint32(units) & 0xffff) << 10) + binary.LittleEndian.PutUint32(location[:4], highWord) + binary.LittleEndian.PutUint32(location[4:], lowWord) + return nil +} + +func patchLoongBranch26(location []byte, target, place uint64, addend int64) error { + word := binary.LittleEndian.Uint32(location) + if word&0xfc000000 != 0x50000000 && word&0xfc000000 != 0x54000000 { + return fmt.Errorf("LoongArch B26 requires b or bl, got %#08x", word) + } + delta, err := relativeValue(target, addend, place, 0) + if err != nil { + return err + } + if delta&3 != 0 || delta < -(1<<27) || delta > (1<<27)-4 { + return fmt.Errorf("LoongArch B26 displacement %d exceeds aligned signed 28-bit range", delta) + } + units := uint32(delta >> 2) + word = (word & 0xfc000000) | ((units & 0xffff) << 10) | ((units >> 16) & 0x3ff) + binary.LittleEndian.PutUint32(location, word) + return nil +} + +func patchLoongPageHigh20(location []byte, target, place uint64, addend int64) error { + word := binary.LittleEndian.Uint32(location) + if word&0xfe000000 != 0x1a000000 { + return fmt.Errorf("LoongArch page relocation requires pcalau12i, got %#08x", word) + } + value, err := addSigned(target, addend) + if err != nil { + return err + } + adjusted, err := addSigned(value, 0x800) + if err != nil { + return err + } + delta, err := signedDifference(adjusted&^uint64(0xfff), place&^uint64(0xfff)) + if err != nil { + return err + } + pages := delta >> 12 + if pages < -(1<<19) || pages > (1<<19)-1 { + return fmt.Errorf("LoongArch page displacement %d exceeds signed 20-bit range", pages) + } + word = (word &^ (0xfffff << 5)) | ((uint32(pages) & 0xfffff) << 5) + binary.LittleEndian.PutUint32(location, word) + return nil +} + +func patchLoongLow12(location []byte, target uint64, addend int64) error { + word := binary.LittleEndian.Uint32(location) + // The BOF fixtures use addi.d for addresses and ld.d for GOT slots. + // Corpus hash routines load constant vectors with LSX vld. + if word&0xffc00000 != 0x02c00000 && word&0xffc00000 != 0x28c00000 && + word&0xffc00000 != 0x2c000000 { + return fmt.Errorf("LoongArch low relocation requires addi.d, ld.d, or vld, got %#08x", word) + } + value, err := addSigned(target, addend) + if err != nil { + return err + } + word = (word &^ (0xfff << 10)) | (uint32(value&0xfff) << 10) + binary.LittleEndian.PutUint32(location, word) + return nil +} diff --git a/internal/bofloader/reloc_loong64_elf_test.go b/internal/bofloader/reloc_loong64_elf_test.go new file mode 100644 index 0000000..8e385de --- /dev/null +++ b/internal/bofloader/reloc_loong64_elf_test.go @@ -0,0 +1,95 @@ +package bofloader + +import ( + "debug/elf" + "encoding/binary" + "strings" + "testing" +) + +func TestELFLoong64RelocationCapabilities(t *testing.T) { + for _, typeID := range []elf.R_LARCH{ + elf.R_LARCH_CALL36, elf.R_LARCH_PCALA_HI20, elf.R_LARCH_PCALA_LO12, + elf.R_LARCH_GOT_PC_HI20, elf.R_LARCH_GOT_PC_LO12, + } { + width, noop, err := elfRelocationWidth("loong64", uint32(typeID)) + wantWidth := 4 + if typeID == elf.R_LARCH_CALL36 { + wantWidth = 8 + } + if err != nil || noop || width != wantWidth { + t.Fatalf("loong64 relocation %s: width=%d noop=%v err=%v", typeID, width, noop, err) + } + } + for _, typeID := range []elf.R_LARCH{elf.R_LARCH_GOT_PC_HI20, elf.R_LARCH_GOT_PC_LO12} { + if !elfRelocationNeedsGOT("loong64", uint32(typeID)) { + t.Fatalf("loong64 relocation %s must allocate a GOT slot", typeID) + } + } + if _, _, err := elfRelocationWidth("loong64", uint32(elf.R_LARCH_TLS_GD_PC_HI20)); err == nil { + t.Fatal("LoongArch TLS relocation was accepted without TLS support") + } +} + +func TestLoong64Call36AndBranch26(t *testing.T) { + for _, delta := range []int64{0, 4, 0x12345678, -0x8765434} { + call := make([]byte, 8) + binary.LittleEndian.PutUint32(call[:4], 0x1e000001) + binary.LittleEndian.PutUint32(call[4:], 0x4c000021) + place := uint64(0x400000000) + if err := patchLoongCall36(call, uint64(int64(place)+delta), place, 0); err != nil { + t.Fatalf("CALL36 delta %d: %v", delta, err) + } + hi := signExtend(uint64(binary.LittleEndian.Uint32(call[:4])>>5)&0xfffff, 20) << 18 + lo := signExtend(uint64(binary.LittleEndian.Uint32(call[4:])>>10)&0xffff, 16) << 2 + if got := hi + lo; got != delta { + t.Fatalf("CALL36 encoded displacement = %d, want %d", got, delta) + } + } + bad := make([]byte, 8) + binary.LittleEndian.PutUint32(bad[:4], 0x1e000001) + binary.LittleEndian.PutUint32(bad[4:], 0x4c000041) + if err := patchLoongCall36(bad, 0x1000, 0x1000, 0); err == nil || !strings.Contains(err.Error(), "matching jirl") { + t.Fatalf("mismatched CALL36 pair error = %v", err) + } + branch := make([]byte, 4) + binary.LittleEndian.PutUint32(branch, 0x54000000) + if err := patchLoongBranch26(branch, 0x1200, 0x1000, 0); err != nil { + t.Fatal(err) + } + word := binary.LittleEndian.Uint32(branch) + units := (uint64(word&0x3ff) << 16) | uint64((word>>10)&0xffff) + if got := signExtend(units, 26) << 2; got != 0x200 { + t.Fatalf("B26 encoded displacement = %#x, want 0x200", got) + } +} + +func TestLoong64PageRelocationAndThunk(t *testing.T) { + page := make([]byte, 4) + low := make([]byte, 4) + binary.LittleEndian.PutUint32(page, 0x1a00000c) + binary.LittleEndian.PutUint32(low, 0x28c0018c) + place, target := uint64(0x40000ffc), uint64(0x40002980) + if err := patchLoongPageHigh20(page, target, place, 0); err != nil { + t.Fatal(err) + } + if err := patchLoongLow12(low, target, 0); err != nil { + t.Fatal(err) + } + high := signExtend(uint64(binary.LittleEndian.Uint32(page)>>5)&0xfffff, 20) << 12 + lo := signExtend(uint64(binary.LittleEndian.Uint32(low)>>10)&0xfff, 12) + if got := int64(place&^0xfff) + high + lo; got != int64(target) { + t.Fatalf("PCALA pair resolves to %#x, want %#x", got, target) + } + + thunk := make([]byte, 16) + if err := writeLoong64Thunk(thunk, uintptr(place), uintptr(target)); err != nil { + t.Fatal(err) + } + if got := binary.LittleEndian.Uint32(thunk[8:12]); got != 0x4c000180 { + t.Fatalf("import thunk tail call = %#08x, want jirl zero,t0,0", got) + } + if err := writeLoong64Thunk(thunk[:15], uintptr(place), uintptr(target)); err == nil { + t.Fatal("short import thunk destination accepted") + } +} diff --git a/internal/bofloader/symbol_unix.go b/internal/bofloader/symbol_unix.go index cb2f764..4f19b39 100644 --- a/internal/bofloader/symbol_unix.go +++ b/internal/bofloader/symbol_unix.go @@ -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 bofloader diff --git a/internal/bofloader/symbol_unix_test.go b/internal/bofloader/symbol_unix_test.go index 89353cc..02523c4 100644 --- a/internal/bofloader/symbol_unix_test.go +++ b/internal/bofloader/symbol_unix_test.go @@ -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 bofloader diff --git a/internal/bofloader/symbol_unsupported.go b/internal/bofloader/symbol_unsupported.go index e850846..67bb2bd 100644 --- a/internal/bofloader/symbol_unsupported.go +++ b/internal/bofloader/symbol_unsupported.go @@ -1,4 +1,4 @@ -//go:build android || ios || (darwin && !ios && !(amd64 || arm64)) || (freebsd && !(amd64 || arm64)) || (netbsd && !(amd64 || arm64)) || (linux && !android && !(386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (windows && !(386 || amd64 || arm64)) || (!darwin && !freebsd && !netbsd && !linux && !windows) +//go:build android || ios || (darwin && !ios && !(amd64 || arm64)) || (freebsd && !(amd64 || arm64)) || (netbsd && !(amd64 || arm64)) || (linux && !android && !(386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (windows && !(386 || amd64 || arm64)) || (!darwin && !freebsd && !netbsd && !linux && !windows) package bofloader diff --git a/internal/bofloader/threadid_linux.go b/internal/bofloader/threadid_linux.go index a453acb..ccf434f 100644 --- a/internal/bofloader/threadid_linux.go +++ b/internal/bofloader/threadid_linux.go @@ -1,4 +1,4 @@ -//go:build linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64) +//go:build linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64) package bofloader diff --git a/internal/bofloader/threadid_test.go b/internal/bofloader/threadid_test.go index a031164..f5e5eb0 100644 --- a/internal/bofloader/threadid_test.go +++ b/internal/bofloader/threadid_test.go @@ -1,4 +1,4 @@ -//go:build (darwin && !ios && (amd64 || arm64)) || (freebsd && (amd64 || arm64)) || (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (windows && (386 || amd64 || arm64)) +//go:build (darwin && !ios && (amd64 || arm64)) || (freebsd && (amd64 || arm64)) || (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (windows && (386 || amd64 || arm64)) package bofloader diff --git a/internal/bofloader/threadid_unsupported.go b/internal/bofloader/threadid_unsupported.go index 8b63f71..1400691 100644 --- a/internal/bofloader/threadid_unsupported.go +++ b/internal/bofloader/threadid_unsupported.go @@ -1,4 +1,4 @@ -//go:build android || ios || (darwin && !ios && !(amd64 || arm64)) || (freebsd && !(amd64 || arm64)) || (netbsd && !(amd64 || arm64)) || (linux && !android && !(386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (windows && !(386 || amd64 || arm64)) || (!darwin && !freebsd && !netbsd && !linux && !windows) +//go:build android || ios || (darwin && !ios && !(amd64 || arm64)) || (freebsd && !(amd64 || arm64)) || (netbsd && !(amd64 || arm64)) || (linux && !android && !(386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (windows && !(386 || amd64 || arm64)) || (!darwin && !freebsd && !netbsd && !linux && !windows) package bofloader diff --git a/internal/bofloader/thunk.go b/internal/bofloader/thunk.go index a2e61e5..a3c7822 100644 --- a/internal/bofloader/thunk.go +++ b/internal/bofloader/thunk.go @@ -45,6 +45,8 @@ func writeThunk(destination []byte, target, thunk, got, toc uintptr) error { binary.LittleEndian.PutUint64(destination[8:16], uint64(target)) case "riscv64": return writeRISCV64Thunk(destination, thunk, got) + case "loong64": + return writeLoong64Thunk(destination, thunk, got) case "ppc64le": return writePPC64LEThunk(destination, got, toc) default: @@ -96,6 +98,22 @@ func writeRISCV64Thunk(destination []byte, thunk, got uintptr) error { return nil } +func writeLoong64Thunk(destination []byte, thunk, got uintptr) error { + if len(destination) < 16 { + return errors.New("LoongArch import thunk requires a 16-byte destination") + } + // pcalau12i t0, page(GOT); ld.d t0, t0, lo12(GOT); + // jirl zero, t0, 0; nop. The GOT holds the unrestricted callback address. + binary.LittleEndian.PutUint32(destination[0:4], 0x1a00000c) + binary.LittleEndian.PutUint32(destination[4:8], 0x28c0018c) + binary.LittleEndian.PutUint32(destination[8:12], 0x4c000180) + binary.LittleEndian.PutUint32(destination[12:16], 0x03400000) + if err := patchLoongPageHigh20(destination[0:4], uint64(got), uint64(thunk), 0); err != nil { + return err + } + return patchLoongLow12(destination[4:8], uint64(got), 0) +} + func writeARMThunk(destination []byte, target uint32) { // LDR IP, [PC]; BX IP; followed by the absolute destination. BX preserves // ARM/Thumb interworking when the resolved function has its low bit set. diff --git a/internal/bofloader/towidechar_unix.go b/internal/bofloader/towidechar_unix.go index 415727c..45dac3b 100644 --- a/internal/bofloader/towidechar_unix.go +++ b/internal/bofloader/towidechar_unix.go @@ -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 bofloader diff --git a/internal/bofloader/towidechar_unix_test.go b/internal/bofloader/towidechar_unix_test.go index 64b7320..d11040d 100644 --- a/internal/bofloader/towidechar_unix_test.go +++ b/internal/bofloader/towidechar_unix_test.go @@ -1,4 +1,4 @@ -//go:build (darwin && !ios && (amd64 || arm64)) || (freebsd && (amd64 || arm64)) || (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) +//go:build (darwin && !ios && (amd64 || arm64)) || (freebsd && (amd64 || arm64)) || (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) package bofloader diff --git a/internal/loong64cache/flush.go b/internal/loong64cache/flush.go new file mode 100644 index 0000000..8b59536 --- /dev/null +++ b/internal/loong64cache/flush.go @@ -0,0 +1,7 @@ +//go:build linux && loong64 + +package loong64cache + +// Flush synchronizes subsequently executed instructions with recently written +// code. Linux/LoongArch compiler-rt implements __clear_cache with ibar 0. +func Flush() diff --git a/internal/loong64cache/flush.s b/internal/loong64cache/flush.s new file mode 100644 index 0000000..f098948 --- /dev/null +++ b/internal/loong64cache/flush.s @@ -0,0 +1,7 @@ +//go:build linux && loong64 + +#include "textflag.h" + +TEXT ·Flush(SB),NOSPLIT,$0-0 + WORD $0x38728000 // ibar 0 + RET diff --git a/memmod/memmod_elf.go b/memmod/memmod_elf.go index 06c0826..678332f 100644 --- a/memmod/memmod_elf.go +++ b/memmod/memmod_elf.go @@ -1,4 +1,4 @@ -//go:build (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64)) +//go:build (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64)) package memmod @@ -620,6 +620,8 @@ func applyOneRelocation(machine elf.Machine, class elf.Class, mapped mappedELF, return applyARMReloc(relocType, place, mapped.loadBias, symValue, addend, mapped.tlsOffset, mapped.hasTLS) case elf.EM_RISCV: return applyRISCV64Reloc(relocType, place, mapped.loadBias, symValue, addend, mapped.tlsOffset, mapped.hasTLS) + case elf.EM_LOONGARCH: + return applyLoong64Reloc(relocType, place, mapped.loadBias, symValue, addend, mapped.tlsOffset, mapped.hasTLS) case elf.EM_PPC64: return applyPPC64LEReloc(relocType, place, mapped.loadBias, symValue, addend, mapped.tlsOffset, mapped.hasTLS) default: @@ -772,6 +774,34 @@ func applyRISCV64Reloc(relocType uint32, place uintptr, loadBias uintptr, symVal } } +func applyLoong64Reloc(relocType uint32, place uintptr, loadBias uintptr, symValue uintptr, addend int64, tlsOffset int64, hasTLS bool) error { + switch elf.R_LARCH(relocType) { + case elf.R_LARCH_NONE: + return nil + case elf.R_LARCH_RELATIVE: + writeU64(place, uint64(int64(loadBias)+addend)) + return nil + case elf.R_LARCH_TLS_TPREL64: + if !hasTLS { + return errors.New("loong64 static TLS relocation has no reserved host TLS slot") + } + writeU64(place, uint64(tlsOffset+addend)) + return nil + case elf.R_LARCH_JUMP_SLOT, elf.R_LARCH_64: + writeU64(place, uint64(int64(symValue)+addend)) + return nil + case elf.R_LARCH_32: + value := int64(symValue) + addend + if value < 0 || value > 0xffffffff { + return fmt.Errorf("loong64 32-bit relocation overflow: value=%d", value) + } + writeU32(place, uint32(value)) + return nil + default: + return fmt.Errorf("unsupported loong64 relocation type: %d", relocType) + } +} + func applyPPC64LEReloc(relocType uint32, place uintptr, loadBias uintptr, symValue uintptr, addend int64, tlsOffset int64, hasTLS bool) error { switch elf.R_PPC64(relocType) { case elf.R_PPC64_NONE: @@ -1166,6 +1196,8 @@ func commonLinuxDependencies() []string { deps = append(deps, "ld-linux-armhf.so.3", "ld-linux.so.3", "ld-musl-armhf.so.1") case "riscv64": deps = append(deps, "ld-linux-riscv64-lp64d.so.1", "ld-musl-riscv64.so.1") + case "loong64": + deps = append(deps, "ld-linux-loongarch-lp64d.so.1", "ld-musl-loongarch64.so.1") case "ppc64le": deps = append(deps, "ld64.so.2", "ld-musl-powerpc64le.so.1") } @@ -1345,6 +1377,8 @@ func linuxLibrarySearchDirs() []string { dirs = append(dirs, "/lib/arm-linux-gnueabihf", "/usr/lib/arm-linux-gnueabihf") case "riscv64": dirs = append(dirs, "/lib/riscv64-linux-gnu", "/usr/lib/riscv64-linux-gnu") + case "loong64": + dirs = append(dirs, "/lib/loongarch64-linux-gnu", "/usr/lib/loongarch64-linux-gnu") case "ppc64le": dirs = append(dirs, "/lib/powerpc64le-linux-gnu", "/usr/lib/powerpc64le-linux-gnu") } @@ -1901,6 +1935,10 @@ func validateLinuxELFABI(machine elf.Machine, class elf.Class, flags uint32) err if unknown := flags &^ uint32(riscvKnownFlags); unknown != 0 { return fmt.Errorf("unsupported RISC-V ELF flags %#x; unknown flags %#x", flags, unknown) } + case elf.EM_LOONGARCH: + if flags != 0x43 { + return fmt.Errorf("unsupported LoongArch ELF flags %#x; require LP64D ABI v1 (0x43)", flags) + } case elf.EM_PPC64: const ppc64ABIMask = 0x00000003 if flags&ppc64ABIMask != 2 || flags&^uint32(ppc64ABIMask) != 0 { @@ -1922,6 +1960,8 @@ func currentELFMachine() (elf.Machine, error) { return elf.EM_ARM, nil case "riscv64": return elf.EM_RISCV, nil + case "loong64": + return elf.EM_LOONGARCH, nil case "ppc64le": return elf.EM_PPC64, nil default: diff --git a/memmod/memmod_elf_dynapi_linux.go b/memmod/memmod_elf_dynapi_linux.go index 2f7076d..f430f85 100644 --- a/memmod/memmod_elf_dynapi_linux.go +++ b/memmod/memmod_elf_dynapi_linux.go @@ -1,4 +1,4 @@ -//go:build linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64) +//go:build linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64) package memmod diff --git a/memmod/memmod_elf_recursive.go b/memmod/memmod_elf_recursive.go index 3010abf..b9a56e3 100644 --- a/memmod/memmod_elf_recursive.go +++ b/memmod/memmod_elf_recursive.go @@ -1,4 +1,4 @@ -//go:build (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64)) +//go:build (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64)) package memmod @@ -893,6 +893,10 @@ func hasRecursiveIRelative(f *elf.File) bool { if relocationType == 58 { return true } + case elf.EM_LOONGARCH: + if elf.R_LARCH(relocationType) == elf.R_LARCH_IRELATIVE { + return true + } case elf.EM_PPC64: if elf.R_PPC64(relocationType) == elf.R_PPC64_IRELATIVE { return true diff --git a/memmod/memmod_linux_cacheflush_loong64.go b/memmod/memmod_linux_cacheflush_loong64.go new file mode 100644 index 0000000..51a094d --- /dev/null +++ b/memmod/memmod_linux_cacheflush_loong64.go @@ -0,0 +1,12 @@ +//go:build linux && !android && loong64 + +package memmod + +import "github.com/sliverarmory/reflektor/internal/loong64cache" + +func flushLinuxInstructionCache(start, end uintptr) error { + if start < end { + loong64cache.Flush() + } + return nil +} diff --git a/memmod/memmod_linux_call_args_test.go b/memmod/memmod_linux_call_args_test.go index 7304c5b..5135d19 100644 --- a/memmod/memmod_linux_call_args_test.go +++ b/memmod/memmod_linux_call_args_test.go @@ -1,4 +1,4 @@ -//go:build linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64) +//go:build linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64) package memmod @@ -212,6 +212,8 @@ func linuxCallArgsZigTarget(t *testing.T) string { return "arm-linux-gnueabihf" case "riscv64": return "riscv64-linux-gnu" + case "loong64": + return "loongarch64-linux-gnu" case "ppc64le": return "powerpc64le-linux-gnu" default: diff --git a/memmod/memmod_linux_call_cgo.go b/memmod/memmod_linux_call_cgo.go index 5182c60..9e2f8e0 100644 --- a/memmod/memmod_linux_call_cgo.go +++ b/memmod/memmod_linux_call_cgo.go @@ -1,4 +1,4 @@ -//go:build cgo && ((linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64))) +//go:build cgo && ((linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64))) package memmod diff --git a/memmod/memmod_linux_call_noasm.go b/memmod/memmod_linux_call_noasm.go index 632b69f..0d2796b 100644 --- a/memmod/memmod_linux_call_noasm.go +++ b/memmod/memmod_linux_call_noasm.go @@ -1,4 +1,4 @@ -//go:build linux && !android && !cgo && ((arm && arm.7) || ppc64le || riscv64) +//go:build linux && !android && !cgo && ((arm && arm.7) || ppc64le || riscv64 || loong64) package memmod diff --git a/memmod/memmod_linux_call_purego.go b/memmod/memmod_linux_call_purego.go index 3aea9cc..5c2d8f9 100644 --- a/memmod/memmod_linux_call_purego.go +++ b/memmod/memmod_linux_call_purego.go @@ -1,4 +1,4 @@ -//go:build !cgo && ((linux && !android && ((arm && arm.7) || amd64 || arm64 || ppc64le || riscv64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64))) +//go:build !cgo && ((linux && !android && ((arm && arm.7) || amd64 || arm64 || ppc64le || riscv64 || loong64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64))) package memmod diff --git a/memmod/memmod_linux_extended_reloc_test.go b/memmod/memmod_linux_extended_reloc_test.go index ce5e9d4..5abf9e6 100644 --- a/memmod/memmod_linux_extended_reloc_test.go +++ b/memmod/memmod_linux_extended_reloc_test.go @@ -1,4 +1,4 @@ -//go:build (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64)) +//go:build (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64)) package memmod @@ -56,6 +56,29 @@ func TestLinuxExtendedArchitectureDynamicRelocations(t *testing.T) { } }) + t.Run("loong64", func(t *testing.T) { + word := make([]byte, 8) + place := uintptr(unsafe.Pointer(&word[0])) + if err := applyLoong64Reloc(uint32(elf.R_LARCH_RELATIVE), place, 0x1000, 0, 0x28, 0, false); err != nil { + t.Fatal(err) + } + if got, want := readU64(place), uint64(0x1028); got != want { + t.Fatalf("R_LARCH_RELATIVE = %#x, want %#x", got, want) + } + if err := applyLoong64Reloc(uint32(elf.R_LARCH_64), place, 0, 0x2200, -8, 0, false); err != nil { + t.Fatal(err) + } + if got, want := readU64(place), uint64(0x21f8); got != want { + t.Fatalf("R_LARCH_64 = %#x, want %#x", got, want) + } + if err := applyLoong64Reloc(uint32(elf.R_LARCH_TLS_TPREL64), place, 0, 0, 8, -0x400, true); err != nil { + t.Fatal(err) + } + if got, want := readU64(place), ^uint64(0)-0x3f7; got != want { + t.Fatalf("R_LARCH_TLS_TPREL64 = %#x, want %#x", got, want) + } + }) + t.Run("ppc64le", func(t *testing.T) { word := make([]byte, 8) place := uintptr(unsafe.Pointer(&word[0])) @@ -89,6 +112,7 @@ func TestLinuxExtendedArchitectureTLSRelocationsRequireReservedSlot(t *testing.T }{ {name: "arm", call: func() error { return applyARMReloc(uint32(elf.R_ARM_TLS_TPOFF32), place, 0, 0, 0, 0, false) }}, {name: "riscv64", call: func() error { return applyRISCV64Reloc(uint32(elf.R_RISCV_TLS_TPREL64), place, 0, 0, 0, 0, false) }}, + {name: "loong64", call: func() error { return applyLoong64Reloc(uint32(elf.R_LARCH_TLS_TPREL64), place, 0, 0, 0, 0, false) }}, {name: "ppc64le", call: func() error { return applyPPC64LEReloc(uint32(elf.R_PPC64_TPREL64), place, 0, 0, 0, 0, false) }}, } for _, test := range tests { diff --git a/memmod/memmod_linux_initargs_stub.go b/memmod/memmod_linux_initargs_stub.go index ec71888..f10ca90 100644 --- a/memmod/memmod_linux_initargs_stub.go +++ b/memmod/memmod_linux_initargs_stub.go @@ -1,4 +1,4 @@ -//go:build !cgo && ((linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64))) +//go:build !cgo && ((linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64))) package memmod diff --git a/memmod/memmod_linux_recursive_test.go b/memmod/memmod_linux_recursive_test.go index 04b12c2..217ed17 100644 --- a/memmod/memmod_linux_recursive_test.go +++ b/memmod/memmod_linux_recursive_test.go @@ -1,4 +1,4 @@ -//go:build (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (freebsd && (amd64 || arm64)) +//go:build (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (freebsd && (amd64 || arm64)) package memmod diff --git a/memmod/memmod_linux_test.go b/memmod/memmod_linux_test.go index d65c103..596e92e 100644 --- a/memmod/memmod_linux_test.go +++ b/memmod/memmod_linux_test.go @@ -1,4 +1,4 @@ -//go:build linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64) +//go:build linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64) package memmod @@ -76,6 +76,8 @@ func buildLinuxTestSO(t *testing.T, output string) { zigTarget = "arm-linux-gnueabihf" case "riscv64": zigTarget = "riscv64-linux-gnu" + case "loong64": + zigTarget = "loongarch64-linux-gnu" case "ppc64le": zigTarget = "powerpc64le-linux-gnu" default: diff --git a/memmod/memmod_unsupported.go b/memmod/memmod_unsupported.go index bae0333..9d491d2 100644 --- a/memmod/memmod_unsupported.go +++ b/memmod/memmod_unsupported.go @@ -1,4 +1,4 @@ -//go:build android || ios || (darwin && !ios && !(amd64 || arm64)) || (linux && !android && !(386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (freebsd && !(amd64 || arm64)) || (netbsd && !(amd64 || arm64)) || (!windows && !darwin && !linux && !freebsd && !netbsd) +//go:build android || ios || (darwin && !ios && !(amd64 || arm64)) || (linux && !android && !(386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (freebsd && !(amd64 || arm64)) || (netbsd && !(amd64 || arm64)) || (!windows && !darwin && !linux && !freebsd && !netbsd) package memmod diff --git a/native/backend_elf.go b/native/backend_elf.go index 3cef8c7..043bcf4 100644 --- a/native/backend_elf.go +++ b/native/backend_elf.go @@ -1,4 +1,4 @@ -//go:build (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64)) +//go:build (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64)) package native diff --git a/native/backend_unsupported.go b/native/backend_unsupported.go index 3dfba16..b1e5097 100644 --- a/native/backend_unsupported.go +++ b/native/backend_unsupported.go @@ -1,4 +1,4 @@ -//go:build !(darwin && !ios && (amd64 || arm64)) && !(linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) && !(freebsd && (amd64 || arm64)) && !(netbsd && (amd64 || arm64)) && !(windows && (386 || amd64 || arm64)) +//go:build !(darwin && !ios && (amd64 || arm64)) && !(linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) && !(freebsd && (amd64 || arm64)) && !(netbsd && (amd64 || arm64)) && !(windows && (386 || amd64 || arm64)) package native diff --git a/native/internal/linuxmem/cache_flush_loong64.go b/native/internal/linuxmem/cache_flush_loong64.go new file mode 100644 index 0000000..be80bc8 --- /dev/null +++ b/native/internal/linuxmem/cache_flush_loong64.go @@ -0,0 +1,12 @@ +//go:build linux && !android && loong64 + +package linuxmem + +import "github.com/sliverarmory/reflektor/internal/loong64cache" + +func flushMappedInstructionCache(mapping []byte) error { + if len(mapping) != 0 { + loong64cache.Flush() + } + return nil +} diff --git a/native/internal/linuxmem/call_64.go b/native/internal/linuxmem/call_64.go index 1edbe4d..a67ffd2 100644 --- a/native/internal/linuxmem/call_64.go +++ b/native/internal/linuxmem/call_64.go @@ -1,4 +1,4 @@ -//go:build (linux && !android && (amd64 || arm64 || ppc64le || riscv64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64)) +//go:build (linux && !android && (amd64 || arm64 || ppc64le || riscv64 || loong64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64)) // SPDX-License-Identifier: MIT package linuxmem diff --git a/native/internal/linuxmem/dynapi_linux.go b/native/internal/linuxmem/dynapi_linux.go index d71b2d6..3b82b32 100644 --- a/native/internal/linuxmem/dynapi_linux.go +++ b/native/internal/linuxmem/dynapi_linux.go @@ -1,4 +1,4 @@ -//go:build linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64) +//go:build linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64) package linuxmem diff --git a/native/internal/linuxmem/loader_elf.go b/native/internal/linuxmem/loader_elf.go index f347079..aeeb9ef 100644 --- a/native/internal/linuxmem/loader_elf.go +++ b/native/internal/linuxmem/loader_elf.go @@ -1,4 +1,4 @@ -//go:build (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64)) +//go:build (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64)) // SPDX-License-Identifier: MIT // @@ -571,6 +571,8 @@ func applyOneRelocation(machine elf.Machine, class elf.Class, mapped mappedELF, return applyARMReloc(relocType, place, mapped.loadBias, symValue, addend, mapped.tlsOffset, mapped.hasTLS) case elf.EM_RISCV: return applyRISCV64Reloc(relocType, place, mapped.loadBias, symValue, addend, mapped.tlsOffset, mapped.hasTLS) + case elf.EM_LOONGARCH: + return applyLoong64Reloc(relocType, place, mapped.loadBias, symValue, addend, mapped.tlsOffset, mapped.hasTLS) case elf.EM_PPC64: return applyPPC64LEReloc(relocType, place, mapped.loadBias, symValue, addend, mapped.tlsOffset, mapped.hasTLS) default: @@ -723,6 +725,34 @@ func applyRISCV64Reloc(relocType uint32, place uintptr, loadBias uintptr, symVal } } +func applyLoong64Reloc(relocType uint32, place uintptr, loadBias uintptr, symValue uintptr, addend int64, tlsOffset int64, hasTLS bool) error { + switch elf.R_LARCH(relocType) { + case elf.R_LARCH_NONE: + return nil + case elf.R_LARCH_RELATIVE: + writeU64(place, uint64(int64(loadBias)+addend)) + return nil + case elf.R_LARCH_TLS_TPREL64: + if !hasTLS { + return errors.New("loong64 static TLS relocation has no reserved host TLS slot") + } + writeU64(place, uint64(tlsOffset+addend)) + return nil + case elf.R_LARCH_JUMP_SLOT, elf.R_LARCH_64: + writeU64(place, uint64(int64(symValue)+addend)) + return nil + case elf.R_LARCH_32: + value := int64(symValue) + addend + if value < 0 || value > 0xffffffff { + return fmt.Errorf("loong64 32-bit relocation overflow: value=%d", value) + } + writeU32(place, uint32(value)) + return nil + default: + return fmt.Errorf("unsupported loong64 relocation type: %d", relocType) + } +} + func applyPPC64LEReloc(relocType uint32, place uintptr, loadBias uintptr, symValue uintptr, addend int64, tlsOffset int64, hasTLS bool) error { switch elf.R_PPC64(relocType) { case elf.R_PPC64_NONE: @@ -1141,6 +1171,8 @@ func commonLinuxDependencies() []string { deps = append(deps, "ld-linux-armhf.so.3", "ld-linux.so.3", "ld-musl-armhf.so.1") case "riscv64": deps = append(deps, "ld-linux-riscv64-lp64d.so.1", "ld-musl-riscv64.so.1") + case "loong64": + deps = append(deps, "ld-linux-loongarch-lp64d.so.1", "ld-musl-loongarch64.so.1") case "ppc64le": deps = append(deps, "ld64.so.2", "ld-musl-powerpc64le.so.1") } @@ -1359,6 +1391,8 @@ func linuxLibrarySearchDirs() []string { dirs = append(dirs, "/lib/arm-linux-gnueabihf", "/usr/lib/arm-linux-gnueabihf") case "riscv64": dirs = append(dirs, "/lib/riscv64-linux-gnu", "/usr/lib/riscv64-linux-gnu") + case "loong64": + dirs = append(dirs, "/lib/loongarch64-linux-gnu", "/usr/lib/loongarch64-linux-gnu") case "ppc64le": dirs = append(dirs, "/lib/powerpc64le-linux-gnu", "/usr/lib/powerpc64le-linux-gnu") } @@ -1923,6 +1957,10 @@ func validateELFArchitectureFlags(machine elf.Machine, flags uint32) error { if unknown := flags &^ riscvELFKnownFlags; unknown != 0 { return fmt.Errorf("ELF/riscv64 shared library has unknown flags %#08x", unknown) } + case elf.EM_LOONGARCH: + if flags != 0x43 { + return fmt.Errorf("ELF/loong64 shared libraries require LP64D ABI v1 flags (0x43), got %#08x", flags) + } case elf.EM_PPC64: if flags&ppc64ELFABI != ppc64ELFABI2 || flags&^uint32(ppc64ELFABI) != 0 { return fmt.Errorf("ELF/ppc64le shared libraries require the ELFv2 ABI flags, got %#08x", flags) @@ -1967,6 +2005,8 @@ func currentELFMachine() (elf.Machine, error) { return elf.EM_ARM, nil case "riscv64": return elf.EM_RISCV, nil + case "loong64": + return elf.EM_LOONGARCH, nil case "ppc64le": return elf.EM_PPC64, nil default: diff --git a/native/internal/linuxmem/loader_linux_test.go b/native/internal/linuxmem/loader_linux_test.go index ecb1f05..407477e 100644 --- a/native/internal/linuxmem/loader_linux_test.go +++ b/native/internal/linuxmem/loader_linux_test.go @@ -1,4 +1,4 @@ -//go:build (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64)) +//go:build (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (freebsd && (amd64 || arm64)) || (netbsd && (amd64 || arm64)) package linuxmem @@ -227,6 +227,26 @@ func TestNewLinuxArchitectureRelocations(t *testing.T) { } }) + t.Run("loong64", func(t *testing.T) { + var word uint64 + place := uintptr(unsafe.Pointer(&word)) + if err := applyLoong64Reloc(uint32(elf.R_LARCH_RELATIVE), place, 0x1000, 0, 0x234, 0, false); err != nil { + t.Fatal(err) + } + if word != 0x1234 { + t.Fatalf("R_LARCH_RELATIVE = %#x, want 0x1234", word) + } + if err := applyLoong64Reloc(uint32(elf.R_LARCH_64), place, 0, 0x5000, 0x678, 0, false); err != nil { + t.Fatal(err) + } + if word != 0x5678 { + t.Fatalf("R_LARCH_64 = %#x, want 0x5678", word) + } + if err := applyLoong64Reloc(uint32(elf.R_LARCH_TLS_TPREL64), place, 0, 0, 0, 0, false); err == nil { + t.Fatal("R_LARCH_TLS_TPREL64 without a TLS slot succeeded") + } + }) + t.Run("ppc64le", func(t *testing.T) { var word uint64 place := uintptr(unsafe.Pointer(&word)) @@ -263,6 +283,9 @@ func TestNewLinuxArchitectureABIFlags(t *testing.T) { {name: "riscv64 RVE", machine: elf.EM_RISCV, flags: riscvELFFloatABIDouble | riscvELFRVE, wantErr: true}, {name: "riscv64 TSO", machine: elf.EM_RISCV, flags: riscvELFFloatABIDouble | riscvELFTSO, wantErr: true}, {name: "riscv64 unknown", machine: elf.EM_RISCV, flags: riscvELFFloatABIDouble | 0x20, wantErr: true}, + {name: "loong64 LP64D ABI v1", machine: elf.EM_LOONGARCH, flags: 0x43}, + {name: "loong64 LP64S", machine: elf.EM_LOONGARCH, flags: 0x41, wantErr: true}, + {name: "loong64 unknown extension", machine: elf.EM_LOONGARCH, flags: 0x4b, wantErr: true}, {name: "ppc64le ELFv2", machine: elf.EM_PPC64, flags: ppc64ELFABI2}, {name: "ppc64 ELFv1", machine: elf.EM_PPC64, flags: 1, wantErr: true}, {name: "ppc64 unspecified ABI", machine: elf.EM_PPC64, flags: 0, wantErr: true}, diff --git a/platform-support.json b/platform-support.json index a26b4ac..68659e5 100644 --- a/platform-support.json +++ b/platform-support.json @@ -3,7 +3,7 @@ "status_values": ["runtime", "runtime-emulated", "unsupported", "n-a"], "endian_values": ["little", "big"], "bof_format_values": ["elf", "macho", "coff", "xcoff", "undefined"], - "runner_values": ["github-native", "github-container", "github-qemu-freebsd-amd64", "github-qemu-freebsd-arm64", "github-qemu-arm-v7", "github-qemu-ppc64le", "github-qemu-riscv64", "github-qemu-netbsd-amd64", "github-qemu-netbsd-arm64", "none"], + "runner_values": ["github-native", "github-container", "github-qemu-freebsd-amd64", "github-qemu-freebsd-arm64", "github-qemu-arm-v7", "github-qemu-loong64", "github-qemu-ppc64le", "github-qemu-riscv64", "github-qemu-netbsd-amd64", "github-qemu-netbsd-arm64", "none"], "excluded_targets": ["js/wasm", "wasip1/wasm"], "targets": [ {"goos": "aix", "goarch": "ppc64", "endian": "big", "bof_format": "xcoff", "bof": "unsupported", "bof_cgo_free": false, "root_shared": "unsupported", "native_shared": "unsupported", "recursive_shared": "unsupported", "go_c_shared": "n-a", "shared_cgo_free": false, "runner": "none"}, @@ -25,7 +25,7 @@ {"goos": "linux", "goarch": "amd64", "endian": "little", "bof_format": "elf", "bof": "runtime", "bof_cgo_free": true, "root_shared": "runtime", "native_shared": "runtime", "recursive_shared": "runtime", "go_c_shared": "runtime", "shared_cgo_free": true, "runner": "github-native"}, {"goos": "linux", "goarch": "arm", "goarm": "7,hardfloat", "endian": "little", "bof_format": "elf", "bof": "runtime-emulated", "bof_cgo_free": true, "root_shared": "runtime-emulated", "native_shared": "runtime-emulated", "recursive_shared": "runtime-emulated", "go_c_shared": "runtime-emulated", "shared_cgo_free": true, "runner": "github-qemu-arm-v7"}, {"goos": "linux", "goarch": "arm64", "endian": "little", "bof_format": "elf", "bof": "runtime", "bof_cgo_free": true, "root_shared": "runtime", "native_shared": "runtime", "recursive_shared": "runtime", "go_c_shared": "runtime", "shared_cgo_free": true, "runner": "github-native"}, - {"goos": "linux", "goarch": "loong64", "endian": "little", "bof_format": "elf", "bof": "unsupported", "bof_cgo_free": false, "root_shared": "unsupported", "native_shared": "unsupported", "recursive_shared": "unsupported", "go_c_shared": "unsupported", "shared_cgo_free": false, "runner": "none"}, + {"goos": "linux", "goarch": "loong64", "endian": "little", "bof_format": "elf", "bof": "runtime-emulated", "bof_cgo_free": true, "root_shared": "runtime-emulated", "native_shared": "runtime-emulated", "recursive_shared": "runtime-emulated", "go_c_shared": "runtime-emulated", "shared_cgo_free": true, "runner": "github-qemu-loong64"}, {"goos": "linux", "goarch": "mips", "endian": "big", "bof_format": "elf", "bof": "unsupported", "bof_cgo_free": false, "root_shared": "unsupported", "native_shared": "unsupported", "recursive_shared": "unsupported", "go_c_shared": "n-a", "shared_cgo_free": false, "runner": "none"}, {"goos": "linux", "goarch": "mips64", "endian": "big", "bof_format": "elf", "bof": "unsupported", "bof_cgo_free": false, "root_shared": "unsupported", "native_shared": "unsupported", "recursive_shared": "unsupported", "go_c_shared": "n-a", "shared_cgo_free": false, "runner": "none"}, {"goos": "linux", "goarch": "mips64le", "endian": "little", "bof_format": "elf", "bof": "unsupported", "bof_cgo_free": false, "root_shared": "unsupported", "native_shared": "unsupported", "recursive_shared": "unsupported", "go_c_shared": "n-a", "shared_cgo_free": false, "runner": "none"}, diff --git a/testdata/build_c_shared_libs.sh b/testdata/build_c_shared_libs.sh index 3372025..38fadd2 100755 --- a/testdata/build_c_shared_libs.sh +++ b/testdata/build_c_shared_libs.sh @@ -128,6 +128,7 @@ for fixture in "basic:${SOURCE_FILE}" "args:${ARGS_SOURCE_FILE}"; do build_one "linux" "amd64" "x86_64-linux-gnu" "so" "${base_name}" "${source_file}" build_one "linux" "arm" "arm-linux-gnueabihf" "so" "${base_name}" "${source_file}" build_one "linux" "arm64" "aarch64-linux-gnu" "so" "${base_name}" "${source_file}" + build_one "linux" "loong64" "loongarch64-linux-gnu" "so" "${base_name}" "${source_file}" build_one "linux" "ppc64le" "powerpc64le-linux-gnu" "so" "${base_name}" "${source_file}" build_one "linux" "riscv64" "riscv64-linux-gnu" "so" "${base_name}" "${source_file}" build_one "windows" "386" "x86-windows-gnu" "dll" "${base_name}" "${source_file}" @@ -141,6 +142,7 @@ build_recursive_one "linux" "386" "x86-linux-gnu" "so" build_recursive_one "linux" "amd64" "x86_64-linux-gnu" "so" build_recursive_one "linux" "arm" "arm-linux-gnueabihf" "so" build_recursive_one "linux" "arm64" "aarch64-linux-gnu" "so" +build_recursive_one "linux" "loong64" "loongarch64-linux-gnu" "so" build_recursive_one "linux" "ppc64le" "powerpc64le-linux-gnu" "so" build_recursive_one "linux" "riscv64" "riscv64-linux-gnu" "so" build_recursive_one "windows" "386" "x86-windows-gnu" "dll" diff --git a/testdata/corpus/situational-awareness-linux-loong64.patch b/testdata/corpus/situational-awareness-linux-loong64.patch new file mode 100644 index 0000000..f9f75a5 --- /dev/null +++ b/testdata/corpus/situational-awareness-linux-loong64.patch @@ -0,0 +1,11 @@ +diff --git a/scripts/build-target.sh b/scripts/build-target.sh +--- a/scripts/build-target.sh ++++ b/scripts/build-target.sh +@@ -20,6 +20,7 @@ case "$goos/$goarch" in + linux/386) zig_target=x86-linux-none; platform_define=BOF_LINUX; object_pattern="ELF 32-bit LSB relocatable, Intel 80386" ;; + linux/amd64) zig_target=x86_64-linux-none; platform_define=BOF_LINUX; object_pattern="ELF 64-bit LSB relocatable, x86-64" ;; + linux/arm64) zig_target=aarch64-linux-none; platform_define=BOF_LINUX; object_pattern="ELF 64-bit LSB relocatable, ARM aarch64" ;; ++ linux/loong64) zig_target=loongarch64-linux-none; platform_define=BOF_LINUX; object_pattern="ELF 64-bit LSB relocatable, LoongArch" ;; + darwin/amd64) zig_target=x86_64-macos-none; platform_define=BOF_DARWIN; object_pattern="Mach-O"; macho_cpu=07000001 ;; + darwin/arm64) zig_target=aarch64-macos-none; platform_define=BOF_DARWIN; object_pattern="Mach-O"; macho_cpu=0c000001 ;; + *) echo "error: unsupported target $goos/$goarch" >&2; exit 2 ;; diff --git a/testdata/docker/linux-loong64-bof.Dockerfile b/testdata/docker/linux-loong64-bof.Dockerfile new file mode 100644 index 0000000..f686910 --- /dev/null +++ b/testdata/docker/linux-loong64-bof.Dockerfile @@ -0,0 +1,79 @@ +ARG GO_VERSION=1.26.6 +ARG GO_LOONG64_SHA256=dc7143e4da3e993956e09e19ae72b3330ccea9ceb1cc1fb8e27ea225ac8f8477 +ARG ZIG_VERSION=0.14.0 +ARG RUST_VERSION=1.94.0 + +FROM --platform=$BUILDPLATFORM alpine:3.23 AS go-runtime + +ARG GO_VERSION +ARG GO_LOONG64_SHA256 + +ADD https://go.dev/dl/go${GO_VERSION}.linux-loong64.tar.gz /tmp/go-linux-loong64.tar.gz +RUN echo "${GO_LOONG64_SHA256} /tmp/go-linux-loong64.tar.gz" | sha256sum -c - \ + && mkdir -p /opt \ + && tar -C /opt -xzf /tmp/go-linux-loong64.tar.gz + +FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-bookworm AS modules + +WORKDIR /workspace +ENV GOMODCACHE=/opt/go-mod-cache + +COPY go.mod go.sum ./ +RUN go mod download + +FROM ghcr.io/loong64/debian:trixie-slim + +ARG TARGETOS +ARG TARGETARCH +ARG ZIG_VERSION +ARG RUST_VERSION + +ENV DEBIAN_FRONTEND=noninteractive +ENV CARGO_HOME=/opt/reflektor-cargo +ENV RUSTUP_HOME=/opt/reflektor-rustup +ENV PATH=/opt/reflektor-cargo/bin:/usr/local/go/bin:$PATH + +RUN test "${TARGETOS}/${TARGETARCH}" = "linux/loong64" +RUN apt-get update && apt-get install -y --no-install-recommends \ + bash \ + build-essential \ + ca-certificates \ + curl \ + xz-utils \ + file \ + binutils \ + libcurl4-openssl-dev \ + && rm -rf /var/lib/apt/lists/* +RUN test -e /lib64/ld-linux-loongarch-lp64d.so.1 + +RUN curl -fsSL "https://ziglang.org/download/${ZIG_VERSION}/zig-linux-loongarch64-${ZIG_VERSION}.tar.xz" -o /tmp/zig.tar.xz \ + && tar -xJf /tmp/zig.tar.xz -C /opt \ + && ln -sf "/opt/zig-linux-loongarch64-${ZIG_VERSION}/zig" /usr/local/bin/zig \ + && zig version + +RUN curl --proto '=https' --tlsv1.2 -fsSL https://sh.rustup.rs -o /tmp/rustup-init.sh \ + && sh /tmp/rustup-init.sh -y --profile minimal \ + --default-host loongarch64-unknown-linux-gnu \ + --default-toolchain "${RUST_VERSION}" \ + && rm -f /tmp/rustup-init.sh \ + && rustc --version \ + && cargo --version + +COPY --from=go-runtime /opt/go /usr/local/go +COPY --from=modules /opt/go-mod-cache /opt/go-mod-cache + +WORKDIR /workspace + +ENV CGO_ENABLED=0 +ENV GOCACHE=/tmp/go-build-cache +ENV GOMODCACHE=/opt/go-mod-cache +ENV GOPROXY=off +ENV GOTOOLCHAIN=local +ENV REFLEKTOR_BOF_FIXTURE_DIR=/workspace/test/bof-fixtures +ENV REFLEKTOR_BOF_CORPUS_DIR=/workspace/test/corpus/Situational-Awareness-BOFs + +COPY . /workspace + +RUN go version | grep -F 'linux/loong64' + +CMD ["/bin/bash", "/workspace/testdata/docker/run-linux-loong64-bof-tests.sh"] diff --git a/testdata/docker/run-linux-loong64-bof-tests.sh b/testdata/docker/run-linux-loong64-bof-tests.sh new file mode 100755 index 0000000..302d1c4 --- /dev/null +++ b/testdata/docker/run-linux-loong64-bof-tests.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +set -euo pipefail + +cd /workspace + +echo "runtime: $(uname -a)" +echo "go: $(go version)" + +if [[ "$(go env GOOS)/$(go env GOARCH)" != "linux/loong64" ]]; then + echo "linux/loong64 test container is not running the required target" >&2 + exit 1 +fi +if [[ "$(go env CGO_ENABLED)" != "0" ]]; then + echo "linux/loong64 BOF runtime must remain CGO-free" >&2 + exit 1 +fi + +for fixture in \ + "${REFLEKTOR_BOF_FIXTURE_DIR}/fixture_linux_loong64.o" \ + "${REFLEKTOR_BOF_FIXTURE_DIR}/options_fixture_linux_loong64.o" \ + "${REFLEKTOR_BOF_FIXTURE_DIR}/async_fixture_linux_loong64.o" \ + "${REFLEKTOR_BOF_FIXTURE_DIR}/stop_fixture_linux_loong64.o"; do + if [[ ! -s "${fixture}" ]]; then + echo "required prebuilt BOF fixture is absent or empty: ${fixture}" >&2 + exit 1 + fi + description="$(file -b "${fixture}")" + if [[ "${description}" != *"ELF 64-bit LSB relocatable, LoongArch"* ]]; then + echo "prebuilt BOF fixture has the wrong target: ${fixture}: ${description}" >&2 + exit 1 + fi +done + +if [[ ! -s "${REFLEKTOR_BOF_CORPUS_DIR}/testdata/e2e-manifest.json" ]]; then + echo "prepared linux/loong64 BOF corpus is absent" >&2 + exit 1 +fi +corpus_count="$(find "${REFLEKTOR_BOF_CORPUS_DIR}/dist/linux/loong64" -maxdepth 1 -type f -name '*.o' | wc -l | tr -d '[:space:]')" +if [[ "${corpus_count}" != "25" ]]; then + echo "prepared linux/loong64 BOF corpus has ${corpus_count} objects, want 25" >&2 + exit 1 +fi + +go test ./bof ./internal/bofloader -count=1 + +go test ./integration \ + -run '^(TestPlatformSupportManifest|TestLoadAndExecuteGeneratedBOF|TestBOFLoadWithOptions|TestConcurrentBOFStreaming|TestSameImageBOFExecutionIsSerialized|TestCooperativeBOFStop|TestBOFPackageDependencyGraphIsIsolated)$' \ + -count=1 -v | tee linux-loong64-bof-test.log + +for test_name in \ + TestPlatformSupportManifest \ + TestLoadAndExecuteGeneratedBOF \ + TestBOFLoadWithOptions \ + TestConcurrentBOFStreaming \ + TestSameImageBOFExecutionIsSerialized \ + TestCooperativeBOFStop \ + TestBOFPackageDependencyGraphIsIsolated; do + if ! grep -Fq -- "--- PASS: ${test_name} " linux-loong64-bof-test.log; then + echo "Required CGO-free linux/loong64 BOF test did not pass: ${test_name}" >&2 + exit 1 + fi +done +if grep -Eq '^[[:space:]]*--- SKIP:' linux-loong64-bof-test.log; then + echo "CGO-free linux/loong64 BOF test was skipped; refusing to pass CI." >&2 + exit 1 +fi + +go test ./integration -run '^TestSituationalAwarenessBOFCorpus$' \ + -timeout 20m -count=1 -v | tee linux-loong64-bof-corpus-test.log +if ! grep -Fq -- '--- PASS: TestSituationalAwarenessBOFCorpus ' linux-loong64-bof-corpus-test.log; then + echo "Required linux/loong64 BOF corpus execution test did not pass" >&2 + exit 1 +fi +if grep -Eq '^[[:space:]]*--- SKIP:' linux-loong64-bof-corpus-test.log; then + echo "linux/loong64 BOF corpus execution test was skipped; refusing to pass CI." >&2 + exit 1 +fi + +/bin/bash /workspace/testdata/docker/run-linux-shared-tests.sh diff --git a/testdata/docker/run-linux-memmod-matrix.sh b/testdata/docker/run-linux-memmod-matrix.sh index 2ecf416..225aafb 100755 --- a/testdata/docker/run-linux-memmod-matrix.sh +++ b/testdata/docker/run-linux-memmod-matrix.sh @@ -16,6 +16,7 @@ platforms=( "linux/amd64" "linux/arm/v7" "linux/arm64" + "linux/loong64" "linux/ppc64le" "linux/riscv64" ) @@ -33,6 +34,12 @@ for platform in "${platforms[@]}"; do dockerfile="${SCRIPT_DIR}/linux-ppc64le-bof.Dockerfile" run_command=("/bin/bash" "/workspace/testdata/docker/run-linux-shared-tests.sh") ;; + linux/loong64) + dockerfile="${SCRIPT_DIR}/linux-loong64-bof.Dockerfile" + run_command=("/bin/bash" "/workspace/testdata/docker/run-linux-shared-tests.sh") + docker run --privileged --rm \ + ghcr.io/loong64/binfmt@sha256:abe730caa95d5495aa1b888e2d77eadc41b3ea303ef75e2d82f8dad420b7f4d9 --install loong64 + ;; linux/riscv64) dockerfile="${SCRIPT_DIR}/linux-riscv64-bof.Dockerfile" run_command=("/bin/bash" "/workspace/testdata/docker/run-linux-shared-tests.sh") diff --git a/testdata/docker/run-linux-shared-tests.sh b/testdata/docker/run-linux-shared-tests.sh index a61de1e..92f6f5d 100755 --- a/testdata/docker/run-linux-shared-tests.sh +++ b/testdata/docker/run-linux-shared-tests.sh @@ -18,6 +18,10 @@ case "${target}" in zig_target="riscv64-linux-gnu" rust_target="riscv64gc-unknown-linux-gnu" ;; + linux/loong64) + zig_target="loongarch64-linux-gnu" + rust_target="loongarch64-unknown-linux-gnu" + ;; linux/ppc64le) zig_target="powerpc64le-linux-gnu" rust_target="powerpc64le-unknown-linux-gnu"