From b0cc25119362009a86e08fded840c56a5161f3cf Mon Sep 17 00:00:00 2001 From: moloch-- <875022+moloch--@users.noreply.github.com> Date: Thu, 15 Oct 2026 12:28:42 -0600 Subject: [PATCH 1/3] feat: add Linux loong64 BOF and ELF loader foundation --- internal/bofloader/callbacks_unix.go | 2 +- internal/bofloader/callbacks_unsupported.go | 2 +- internal/bofloader/invoke_unix_64.go | 2 +- internal/bofloader/invoke_unsupported.go | 2 +- .../bofloader/memory_flush_linux_loong64.go | 13 ++ internal/bofloader/memory_unix.go | 2 +- internal/bofloader/memory_unsupported.go | 2 +- internal/bofloader/parser_elf.go | 10 +- internal/bofloader/reloc_elf.go | 17 ++ internal/bofloader/reloc_loong64_elf.go | 157 ++++++++++++++++++ internal/bofloader/symbol_unix.go | 2 +- internal/bofloader/symbol_unsupported.go | 2 +- internal/bofloader/threadid_linux.go | 2 +- internal/bofloader/threadid_unsupported.go | 2 +- internal/bofloader/thunk.go | 18 ++ internal/bofloader/towidechar_unix.go | 2 +- internal/loong64cache/flush.go | 7 + internal/loong64cache/flush.s | 7 + memmod/memmod_elf.go | 42 ++++- memmod/memmod_elf_dynapi_linux.go | 2 +- memmod/memmod_elf_recursive.go | 6 +- memmod/memmod_linux_cacheflush_loong64.go | 12 ++ memmod/memmod_linux_call_cgo.go | 2 +- memmod/memmod_linux_call_noasm.go | 2 +- memmod/memmod_linux_call_purego.go | 2 +- memmod/memmod_linux_initargs_stub.go | 2 +- memmod/memmod_unsupported.go | 2 +- native/backend_elf.go | 2 +- native/backend_unsupported.go | 2 +- .../internal/linuxmem/cache_flush_loong64.go | 12 ++ native/internal/linuxmem/call_64.go | 2 +- native/internal/linuxmem/dynapi_linux.go | 2 +- native/internal/linuxmem/loader_elf.go | 42 ++++- 33 files changed, 360 insertions(+), 25 deletions(-) create mode 100644 internal/bofloader/memory_flush_linux_loong64.go create mode 100644 internal/bofloader/reloc_loong64_elf.go create mode 100644 internal/loong64cache/flush.go create mode 100644 internal/loong64cache/flush.s create mode 100644 memmod/memmod_linux_cacheflush_loong64.go create mode 100644 native/internal/linuxmem/cache_flush_loong64.go diff --git a/internal/bofloader/callbacks_unix.go b/internal/bofloader/callbacks_unix.go index c75729b..9ee5292 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)) || (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/bofloader/callbacks_unsupported.go b/internal/bofloader/callbacks_unsupported.go index 8fa19ad..b7a01cc 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)) || (linux && !android && !(386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (windows && !(386 || amd64 || arm64)) || (!darwin && !freebsd && !linux && !windows) +//go:build android || ios || (darwin && !ios && !(amd64 || arm64)) || (freebsd && !(amd64 || arm64)) || (linux && !android && !(386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (windows && !(386 || amd64 || arm64)) || (!darwin && !freebsd && !linux && !windows) package bofloader diff --git a/internal/bofloader/invoke_unix_64.go b/internal/bofloader/invoke_unix_64.go index 1a8347a..1d169a0 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)) || (linux && !android && (amd64 || arm64 || ppc64le || riscv64)) +//go:build (darwin && !ios && (amd64 || arm64)) || (freebsd && (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 5072ef6..b9fe4c4 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)) || (linux && !android && !(386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (windows && !(386 || amd64 || arm64)) || (!darwin && !freebsd && !linux && !windows) +//go:build android || ios || (darwin && !ios && !(amd64 || arm64)) || (freebsd && !(amd64 || arm64)) || (linux && !android && !(386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (windows && !(386 || amd64 || arm64)) || (!darwin && !freebsd && !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 84f1d4e..9708076 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)) || (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/bofloader/memory_unsupported.go b/internal/bofloader/memory_unsupported.go index 3c26348..26656f1 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)) || (linux && !android && !(386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (windows && !(386 || amd64 || arm64)) || (!darwin && !freebsd && !linux && !windows) +//go:build android || ios || (darwin && !ios && !(amd64 || arm64)) || (freebsd && !(amd64 || arm64)) || (linux && !android && !(386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (windows && !(386 || amd64 || arm64)) || (!darwin && !freebsd && !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..2a9e086 --- /dev/null +++ b/internal/bofloader/reloc_loong64_elf.go @@ -0,0 +1,157 @@ +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. + if word&0xffc00000 != 0x02c00000 && word&0xffc00000 != 0x28c00000 { + return fmt.Errorf("LoongArch low relocation requires addi.d or ld.d, 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/symbol_unix.go b/internal/bofloader/symbol_unix.go index d3ddb3d..8730a57 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)) || (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/bofloader/symbol_unsupported.go b/internal/bofloader/symbol_unsupported.go index f9b30cc..3b218fb 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)) || (linux && !android && !(386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (windows && !(386 || amd64 || arm64)) || (!darwin && !freebsd && !linux && !windows) +//go:build android || ios || (darwin && !ios && !(amd64 || arm64)) || (freebsd && !(amd64 || arm64)) || (linux && !android && !(386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (windows && !(386 || amd64 || arm64)) || (!darwin && !freebsd && !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_unsupported.go b/internal/bofloader/threadid_unsupported.go index 186d9a8..1fd7326 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)) || (linux && !android && !(386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (windows && !(386 || amd64 || arm64)) || (!darwin && !freebsd && !linux && !windows) +//go:build android || ios || (darwin && !ios && !(amd64 || arm64)) || (freebsd && !(amd64 || arm64)) || (linux && !android && !(386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (windows && !(386 || amd64 || arm64)) || (!darwin && !freebsd && !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 182be0d..b1bfd66 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)) || (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 bfc341b..73ef321 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)) +//go:build (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (freebsd && (amd64 || arm64)) package memmod @@ -617,6 +617,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: @@ -769,6 +771,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: @@ -1160,6 +1190,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") } @@ -1327,6 +1359,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") } @@ -1883,6 +1917,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 { @@ -1904,6 +1942,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 4fffcf5..8afc36b 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)) +//go:build (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (freebsd && (amd64 || arm64)) package memmod @@ -890,6 +890,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_cgo.go b/memmod/memmod_linux_call_cgo.go index d8881c9..0bce485 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))) +//go:build cgo && ((linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (freebsd && (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 ae77e3c..120ab1f 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))) +//go:build !cgo && ((linux && !android && ((arm && arm.7) || amd64 || arm64 || ppc64le || riscv64 || loong64)) || (freebsd && (amd64 || arm64))) package memmod diff --git a/memmod/memmod_linux_initargs_stub.go b/memmod/memmod_linux_initargs_stub.go index d2539d5..e4da52a 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))) +//go:build !cgo && ((linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (freebsd && (amd64 || arm64))) package memmod diff --git a/memmod/memmod_unsupported.go b/memmod/memmod_unsupported.go index 3c6d8c2..647883b 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)) || (!windows && !darwin && !linux && !freebsd) +//go:build android || ios || (darwin && !ios && !(amd64 || arm64)) || (linux && !android && !(386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (freebsd && !(amd64 || arm64)) || (!windows && !darwin && !linux && !freebsd) package memmod diff --git a/native/backend_elf.go b/native/backend_elf.go index d8c23c9..3965979 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)) +//go:build (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (freebsd && (amd64 || arm64)) package native diff --git a/native/backend_unsupported.go b/native/backend_unsupported.go index ffe0ad7..2efc7f4 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)) && !(windows && (386 || amd64 || arm64)) +//go:build !(darwin && !ios && (amd64 || arm64)) && !(linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) && !(freebsd && (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 699bdd2..f176918 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)) +//go:build (linux && !android && (amd64 || arm64 || ppc64le || riscv64 || loong64)) || (freebsd && (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 58c03c2..f0ea772 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)) +//go:build (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (freebsd && (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: @@ -1138,6 +1168,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") } @@ -1344,6 +1376,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") } @@ -1908,6 +1942,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) @@ -1952,6 +1990,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: From a8f97687ff87dde6ab3e17584337c397a040114b Mon Sep 17 00:00:00 2001 From: moloch-- <875022+moloch--@users.noreply.github.com> Date: Thu, 15 Oct 2026 12:39:43 -0600 Subject: [PATCH 2/3] test: prove Linux loong64 BOF and shared library support under QEMU --- .github/workflows/bof-corpus.yml | 27 +++++++ .github/workflows/test-matrix.yml | 68 ++++++++++++++++ README.md | 14 ++-- integration/bof_corpus_test.go | 2 +- integration/bof_options_callback_unix_test.go | 2 +- integration/bof_test.go | 13 ++- integration/native_load_go_test.go | 2 +- integration/native_package_linux_cgo_test.go | 2 +- integration/native_package_linux_test.go | 5 +- integration/native_package_test.go | 2 +- integration/platform_support_test.go | 16 +++- integration/sharedlib_build_go_test.go | 2 + integration/sharedlib_build_rust_test.go | 2 + .../sharedlib_load_args_purego_nocgo_test.go | 2 +- integration/sharedlib_load_args_test.go | 2 +- integration/sharedlib_load_linux_go_test.go | 2 +- integration/sharedlib_load_linux_rust_test.go | 2 +- integration/sharedlib_load_linux_test.go | 2 +- ...redlib_load_recursive_assert_linux_test.go | 2 +- .../sharedlib_load_recursive_go_test.go | 2 +- ...aredlib_load_recursive_guard_other_test.go | 2 +- .../sharedlib_load_recursive_modes_test.go | 2 +- integration/sharedlib_load_recursive_test.go | 2 +- integration/sharedlib_matrix_test.go | 3 + internal/bofloader/reloc_loong64_elf.go | 6 +- memmod/memmod_linux_call_args_test.go | 4 +- memmod/memmod_linux_extended_reloc_test.go | 26 +++++- memmod/memmod_linux_recursive_test.go | 2 +- memmod/memmod_linux_test.go | 4 +- native/internal/linuxmem/loader_linux_test.go | 25 +++++- platform-support.json | 4 +- testdata/build_c_shared_libs.sh | 2 + .../situational-awareness-linux-loong64.patch | 11 +++ testdata/docker/linux-loong64-bof.Dockerfile | 79 ++++++++++++++++++ .../docker/run-linux-loong64-bof-tests.sh | 80 +++++++++++++++++++ testdata/docker/run-linux-memmod-matrix.sh | 7 ++ testdata/docker/run-linux-shared-tests.sh | 4 + 37 files changed, 396 insertions(+), 38 deletions(-) create mode 100644 testdata/corpus/situational-awareness-linux-loong64.patch create mode 100644 testdata/docker/linux-loong64-bof.Dockerfile create mode 100644 testdata/docker/run-linux-loong64-bof-tests.sh 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 385c0fa..3beea35 100644 --- a/.github/workflows/test-matrix.yml +++ b/.github/workflows/test-matrix.yml @@ -511,6 +511,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 @@ -741,6 +806,7 @@ jobs: linux amd64 linux arm linux arm64 + linux loong64 linux ppc64le linux riscv64 windows 386 @@ -784,6 +850,7 @@ jobs: linux amd64 linux arm linux arm64 + linux loong64 linux ppc64le linux riscv64 windows 386 @@ -812,6 +879,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 c233318..b6bf741 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. | | Other | - | - | Unsupported | Returns an explicit unsupported-platform error. | The machine-readable [platform capability manifest](platform-support.json) @@ -116,7 +116,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, @@ -126,7 +126,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 | -| 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 @@ -140,13 +140,13 @@ The FreeBSD `amd64` and `arm64` rows run in FreeBSD 15.1 full-system QEMU guests. Each executes generated BOFs, load options, repeated and concurrent calls, close behavior, and all 25 objects from the pinned external BOF corpus 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 @@ -258,7 +258,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 @@ -379,6 +379,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 0c9afa4..d52b60b 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", "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", "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 a62bbf0..ed81d87 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)) || (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 reflektor_test diff --git a/integration/bof_test.go b/integration/bof_test.go index a934527..37d8735 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: "windows", goarch: "386", zigTarget: "x86-windows-gnu", format: "coff"}, {goos: "windows", goarch: "amd64", zigTarget: "x86_64-windows-gnu", format: "coff"}, {goos: "windows", goarch: "arm64", zigTarget: "aarch64-windows-gnu", format: "coff"}, @@ -66,7 +67,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) } @@ -306,11 +307,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) } @@ -342,6 +343,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 3e31520..e5be55a 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)) || (linux && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (windows && (386 || amd64 || arm64)) +//go:build (darwin && (amd64 || arm64)) || (freebsd && (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 e01be70..e5c0210 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", "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", "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 @@ -227,6 +228,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" || @@ -240,8 +250,8 @@ func TestPlatformSupportManifest(t *testing.T) { if !sawFreeBSDAMD64 || !sawFreeBSDARM64 { t.Fatalf("platform support manifest emulated FreeBSD targets: amd64=%v arm64=%v, want both", sawFreeBSDAMD64, sawFreeBSDARM64) } - 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 e8053a6..7424811 100644 --- a/integration/sharedlib_build_go_test.go +++ b/integration/sharedlib_build_go_test.go @@ -107,6 +107,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 cd45e41..c4c99b6 100644 --- a/integration/sharedlib_build_rust_test.go +++ b/integration/sharedlib_build_rust_test.go @@ -85,6 +85,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 455382e..3047764 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)) || (linux && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64))) +//go:build !cgo && ((darwin && (amd64 || arm64)) || (freebsd && (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 b985c16..9253176 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)) || (linux && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (windows && (386 || amd64 || arm64)) +//go:build (darwin && (amd64 || arm64)) || (freebsd && (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 c339a01..41c4d68 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)) || (linux && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (windows && (386 || amd64 || arm64)) +//go:build (freebsd && (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 e81ab05..d37affa 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)) || (linux && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (windows && (386 || amd64 || arm64)) +//go:build (darwin && (amd64 || arm64)) || (freebsd && (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 80ede2f..42a3387 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)) || (linux && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64)) || (windows && (386 || amd64 || arm64)) +//go:build (darwin && (amd64 || arm64)) || (freebsd && (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 b063faa..3e1b0dd 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: "windows", goarch: "386", ext: "dll", fileProbe: "Intel 80386"}, {goos: "windows", goarch: "amd64", ext: "dll", fileProbe: "x86-64"}, {goos: "windows", goarch: "arm64", ext: "dll", fileProbe: "Aarch64"}, @@ -128,6 +129,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/reloc_loong64_elf.go b/internal/bofloader/reloc_loong64_elf.go index 2a9e086..a30c8e1 100644 --- a/internal/bofloader/reloc_loong64_elf.go +++ b/internal/bofloader/reloc_loong64_elf.go @@ -144,8 +144,10 @@ func patchLoongPageHigh20(location []byte, target, place uint64, addend int64) e 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. - if word&0xffc00000 != 0x02c00000 && word&0xffc00000 != 0x28c00000 { - return fmt.Errorf("LoongArch low relocation requires addi.d or ld.d, got %#08x", word) + // 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 { 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_extended_reloc_test.go b/memmod/memmod_linux_extended_reloc_test.go index 20243e1..76e2691 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)) +//go:build (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (freebsd && (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_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/native/internal/linuxmem/loader_linux_test.go b/native/internal/linuxmem/loader_linux_test.go index d1625a5..bdf35da 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)) +//go:build (linux && !android && (386 || amd64 || (arm && arm.7) || arm64 || ppc64le || riscv64 || loong64)) || (freebsd && (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 a7b1d76..79b522a 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", "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", "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 100644 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" From 5fdc03d28940604209943271fce731c2b0336338 Mon Sep 17 00:00:00 2001 From: moloch-- <875022+moloch--@users.noreply.github.com> Date: Thu, 15 Oct 2026 12:39:52 -0600 Subject: [PATCH 3/3] test: cover loong64 BOF relocation and callback paths --- internal/bofloader/reloc_loong64_elf_test.go | 95 +++++++++++++++++++ internal/bofloader/symbol_unix_test.go | 2 +- internal/bofloader/threadid_test.go | 2 +- internal/bofloader/towidechar_unix_test.go | 2 +- .../docker/run-linux-loong64-bof-tests.sh | 0 5 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 internal/bofloader/reloc_loong64_elf_test.go mode change 100644 => 100755 testdata/docker/run-linux-loong64-bof-tests.sh 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_test.go b/internal/bofloader/symbol_unix_test.go index 06a24e7..0351ddf 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)) || (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/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/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/testdata/docker/run-linux-loong64-bof-tests.sh b/testdata/docker/run-linux-loong64-bof-tests.sh old mode 100644 new mode 100755