diff --git a/README.md b/README.md index fd8561ab9..ee97408d3 100644 --- a/README.md +++ b/README.md @@ -163,3 +163,24 @@ go run -C cmd/plan9asm . transpile \ -o /tmp/file.ll \ -goos=linux -goarch=amd64 ``` + +## Foreign-ABI ARM64 assembly + +Raw C callbacks and tail-call trampolines sometimes have no Go function +signature. Passing them through typed LLVM lowering cannot preserve an unknown +set of incoming or outgoing registers. `ForeignARM64Functions` recognizes the +restricted file-local, NOSPLIT, zero-Go-frame form (non-leaf callbacks must +explicitly use NOFRAME). + +A compiler driver can assemble such a file with `go tool asm -p ` and +pass the Darwin/ARM64 Go object to `TranslateNativeARM64Object`. The returned +Mach-O assembly preserves the encoded instructions and maps R_ADDR and +R_CALLARM64 relocations to selected local definitions or explicitly supplied +dynamic imports. The accompanying DATA list lets the driver bind Go globals to +their assembly definitions. The driver remains responsible for selecting the +target, running tools, and supplying library link arguments. + +This path does not translate Go ABI functions. Unsupported object formats, +relocations, and undeclared foreign calls return errors instead of inventing +function signatures. The object reader accepts the go120ld format used since +Go 1.20; the runtime tests execute native callbacks only on Darwin/ARM64. diff --git a/doc/plan9asm-corpus.md b/doc/plan9asm-corpus.md index 77f79e358..80ee4bc5c 100644 --- a/doc/plan9asm-corpus.md +++ b/doc/plan9asm-corpus.md @@ -229,7 +229,7 @@ The ecosystem scan also pins these latest modules: - `github.com/klauspost/reedsolomon v1.14.2` - `github.com/minio/highwayhash v1.0.4` - `github.com/modern-go/gls v0.0.0-20250215024828-78308f6bb19d` -- `github.com/pierrec/lz4/v4 v4.1.29` +- `github.com/pierrec/lz4/v4 v4.1.30` - `github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50` - `github.com/tmthrgd/go-bitwise v0.0.0-20190904053232-1430ee983fca` - `github.com/tmthrgd/go-popcount v0.0.0-20190904054823-afb1ace8b04f` diff --git a/native_arm64_object.go b/native_arm64_object.go new file mode 100644 index 000000000..c4281302b --- /dev/null +++ b/native_arm64_object.go @@ -0,0 +1,340 @@ +package plan9asm + +import ( + "bytes" + "encoding/binary" + "fmt" + "regexp" + "sort" + "strconv" + "strings" +) + +// Layout constants from Go 1.27 cmd/internal/goobj/objfile.go (go120ld) +// and cmd/internal/objabi/reloctype.go. Reject other object formats below. +const ( + nativeBlockCount = 19 // includes the end offset + nativeHeaderSize = 20 + nativeBlockCount*4 + nativeSymSize = 21 + nativeSymFlags = 11 + nativeSymSizeOffset = 13 + nativeSymAlign = 17 + nativeRelocSize = 23 + nativeRelocKind = 5 + nativeRelocAddend = 7 + nativeRelocPkg = 15 + nativeRelocSym = 19 + nativeNoSplit = 1 << 4 + nativeRAddr = 1 + nativeRCallARM64 = 9 + nativeBlkSymdef = 3 + nativeBlkNonpkgref = 7 + nativeBlkRelocIdx = 11 + nativeBlkDataIdx = 13 + nativeBlkReloc = 14 + nativeBlkData = 16 + nativePkgSelf = 0x7ffffffb + nativePkgHashed64 = 0x7ffffffe + nativePkgHashed = 0x7ffffffd + nativePkgNone = 0x7fffffff +) + +// NativeData describes a Go global whose storage is supplied by native assembly. +type NativeData struct { + Name string + Size uint32 +} + +// TranslateNativeARM64Object preserves the machine code of foreign-ABI assembly. Unlike +// Go-declared functions, raw callbacks have no signature from which LLVM can +// reconstruct incoming registers. The Go assembler encodes those registers; +// this bridge only converts its address and branch relocations to Mach-O asm. +// +// The input must be a darwin/arm64 cmd/asm object in go120ld format. Only explicitly +// selected functions, their data, and declared dynamic imports are accepted. +// Unknown formats, symbol references and relocations are errors. +func TranslateNativeARM64Object(obj []byte, funcs map[string]bool, imports map[string]string, pkgPath string) (string, []NativeData, error) { + r, err := readNativeObject(obj) + if err != nil { + return "", nil, err + } + labels := make(map[int]string) + var data []NativeData + for i, s := range r.syms[:r.ndef] { + if funcs[s.name] { + if s.flags&nativeNoSplit == 0 { + return "", nil, fmt.Errorf("native function %s is not NOSPLIT", s.name) + } + labels[i] = fmt.Sprintf("Lllgo_native_%d", i) + } else if strings.HasPrefix(s.name, pkgPath+".") { + labels[i] = "_" + s.name + data = append(data, NativeData{s.name, s.size}) + } + } + for name := range funcs { + found := false + for i := range labels { + if r.syms[i].name == name { + found = true + break + } + } + if !found { + return "", nil, fmt.Errorf("missing native function %s", name) + } + } + var out strings.Builder + for i, s := range r.syms[:r.ndef] { + label, ok := labels[i] + if !ok { + continue + } + code := r.payload(i) + if uint64(len(code)) > uint64(s.size) { + return "", nil, fmt.Errorf("oversized native symbol %s", s.name) + } + code = append(append([]byte(nil), code...), make([]byte, int(s.size)-len(code))...) + if funcs[s.name] { + out.WriteString(".text\n.p2align 2\n") + } else { + out.WriteString(".data\n") + fmt.Fprintf(&out, ".globl %s\n", strconv.Quote(label)) + align := s.align + if align == 0 { + align = 8 + } + if align&(align-1) != 0 { + return "", nil, fmt.Errorf("invalid native alignment %d", align) + } + fmt.Fprintf(&out, ".balign %d\n", align) + } + fmt.Fprintf(&out, "%s:\n", strconv.Quote(label)) + rels := r.relocs(i) + sort.Slice(rels, func(i, j int) bool { return rels[i].off < rels[j].off }) + pos := uint32(0) + for _, rel := range rels { + if rel.off < pos || uint64(rel.off)+uint64(rel.size) > uint64(len(code)) { + return "", nil, fmt.Errorf("invalid native relocation in %s", s.name) + } + target, err := r.target(rel.pkg, rel.sym) + if err != nil { + return "", nil, err + } + dst, ok := labels[target] + if !ok { + alias, ok := imports[r.syms[target].name] + if !ok { + return "", nil, fmt.Errorf("native assembly references undeclared foreign symbol %s", r.syms[target].name) + } + dst = "_" + alias + } + expr := strconv.Quote(dst) + if rel.add != 0 { + expr += fmt.Sprintf("%+d", rel.add) + } + nativeBytes(&out, code[pos:rel.off]) + switch { + case rel.kind == nativeRAddr && rel.size == 8: // R_ADDR + fmt.Fprintf(&out, ".quad %s\n", expr) + case rel.kind == nativeRCallARM64 && rel.size == 4 && funcs[s.name]: // R_CALLARM64: B or BL + if rel.add != 0 { + return "", nil, fmt.Errorf("unsupported native branch addend %d in %s", rel.add, s.name) + } + word := binary.LittleEndian.Uint32(code[rel.off:]) + op := "b" + if word&0xfc000000 == 0x94000000 { + op = "bl" + } else if word&0xfc000000 != 0x14000000 { + return "", nil, fmt.Errorf("invalid native branch in %s", s.name) + } + fmt.Fprintf(&out, "%s %s\n", op, expr) + default: + return "", nil, fmt.Errorf("unsupported native relocation %d/%d in %s", rel.kind, rel.size, s.name) + } + pos = rel.off + uint32(rel.size) + } + nativeBytes(&out, code[pos:]) + } + return out.String(), data, nil +} + +func nativeBytes(out *strings.Builder, b []byte) { + var digits [3]byte // largest byte value is 255 + for len(b) > 0 { + n := len(b) + if n > 16 { + n = 16 + } + out.WriteString(".byte ") + for i, x := range b[:n] { + if i != 0 { + out.WriteByte(',') + } + out.Write(strconv.AppendUint(digits[:0], uint64(x), 10)) + } + out.WriteByte('\n') + b = b[n:] + } +} + +type nativeSym struct { + name string + size, align uint32 + flags byte +} +type nativeReloc struct { + off uint32 + size byte + kind uint16 + add int64 + pkg, sym uint32 +} +type nativeObject struct { + b []byte + blocks [nativeBlockCount]uint32 + syms []nativeSym + ndef int + counts [5]int +} + +func readNativeObject(obj []byte) (*nativeObject, error) { + // cmd/asm prefixes the binary object with a target/version text header. + h := bytes.Index(obj, []byte("\n!\n")) + if h < 0 || !bytes.HasPrefix(obj, []byte("go object darwin arm64 ")) { + return nil, fmt.Errorf("expected darwin/arm64 Go assembler object") + } + b := obj[h+3:] + if len(b) < nativeHeaderSize || string(b[:8]) != "\x00go120ld" { + return nil, fmt.Errorf("unsupported Go assembler object format") + } + r := &nativeObject{b: b} + for i := range r.blocks { + r.blocks[i] = binary.LittleEndian.Uint32(b[20+i*4:]) + if r.blocks[i] > uint32(len(b)) || (i > 0 && r.blocks[i] < r.blocks[i-1]) { + return nil, fmt.Errorf("invalid native object block %d", i) + } + } + if r.blocks[0] < nativeHeaderSize { + return nil, fmt.Errorf("invalid native object header") + } + for block := nativeBlkSymdef; block <= nativeBlkNonpkgref; block++ { + buf := r.block(block) + if len(buf)%nativeSymSize != 0 { + return nil, fmt.Errorf("invalid native symbol table") + } + r.counts[block-nativeBlkSymdef] = len(buf) / nativeSymSize + for len(buf) > 0 { + s := buf[:nativeSymSize] + n, off := binary.LittleEndian.Uint32(s), binary.LittleEndian.Uint32(s[4:]) + if uint64(off)+uint64(n) > uint64(len(b)) { + return nil, fmt.Errorf("invalid native symbol name") + } + size := binary.LittleEndian.Uint32(s[nativeSymSizeOffset:]) + if size > 64<<20 { + return nil, fmt.Errorf("native symbol too large") + } + r.syms = append(r.syms, nativeSym{string(b[off : off+n]), size, binary.LittleEndian.Uint32(s[nativeSymAlign:]), s[nativeSymFlags]}) + buf = buf[nativeSymSize:] + } + } + r.ndef = len(r.syms) - r.counts[4] + if len(r.block(nativeBlkRelocIdx)) != (r.ndef+1)*4 || len(r.block(nativeBlkDataIdx)) != (r.ndef+1)*4 || len(r.block(nativeBlkReloc))%nativeRelocSize != 0 { + return nil, fmt.Errorf("invalid native object indices") + } + for _, pair := range [][2]int{{nativeBlkRelocIdx, len(r.block(nativeBlkReloc)) / nativeRelocSize}, {nativeBlkDataIdx, len(r.block(nativeBlkData))}} { + prev := uint32(0) + buf := r.block(pair[0]) + for len(buf) > 0 { + v := binary.LittleEndian.Uint32(buf) + if v < prev || uint64(v) > uint64(pair[1]) { + return nil, fmt.Errorf("invalid native object index") + } + prev = v + buf = buf[4:] + } + } + return r, nil +} + +// block accepts only the block constants above, all strictly before the end +// offset. readNativeObject validates every offset before calling it. +func (r *nativeObject) block(i int) []byte { return r.b[r.blocks[i]:r.blocks[i+1]] } +func (r *nativeObject) payload(i int) []byte { + idx := r.block(nativeBlkDataIdx) + return r.block(nativeBlkData)[binary.LittleEndian.Uint32(idx[i*4:]):binary.LittleEndian.Uint32(idx[(i+1)*4:])] +} +func (r *nativeObject) relocs(i int) []nativeReloc { + idx := r.block(nativeBlkRelocIdx) + a, z := binary.LittleEndian.Uint32(idx[i*4:]), binary.LittleEndian.Uint32(idx[(i+1)*4:]) + var out []nativeReloc + for n := a; n < z; n++ { + v := r.block(nativeBlkReloc)[int(n)*nativeRelocSize:] + out = append(out, nativeReloc{binary.LittleEndian.Uint32(v), v[4], binary.LittleEndian.Uint16(v[nativeRelocKind:]), int64(binary.LittleEndian.Uint64(v[nativeRelocAddend:])), binary.LittleEndian.Uint32(v[nativeRelocPkg:]), binary.LittleEndian.Uint32(v[nativeRelocSym:])}) + } + return out +} +func (r *nativeObject) target(pkg, sym uint32) (int, error) { + // Reserved package indices from cmd/internal/goobj. Package imports and + // runtime builtins have Go ABI and cannot be called by these foreign stubs. + var start, count int + switch pkg { + case nativePkgSelf: + count = r.counts[0] + case nativePkgHashed64: + start = r.counts[0] + count = r.counts[1] + case nativePkgHashed: + start = r.counts[0] + r.counts[1] + count = r.counts[2] + case nativePkgNone: + start = r.counts[0] + r.counts[1] + r.counts[2] + count = r.counts[3] + r.counts[4] + default: + return 0, fmt.Errorf("native assembly references Go package index %#x", pkg) + } + if uint64(sym) >= uint64(count) { + return 0, fmt.Errorf("invalid native symbol reference") + } + return start + int(sym), nil +} + +var nativeTextRE = regexp.MustCompile(`(?m)^\s*TEXT\s+([^\s(),]+)<>\(SB\),\s*([^,]+),\s*\$0(?:-0)?\s*(?://[^\n]*)?$`) + +// ForeignARM64Functions selects files made entirely of raw foreign-ABI +// callbacks/trampolines. Go-declared functions continue through typed LLVM +// lowering. NOSPLIT and zero Go frames are required; the callback may manage +// its own native frame explicitly with NOFRAME. +func ForeignARM64Functions(src []byte) map[string]bool { + matches := nativeTextRE.FindAllSubmatchIndex(src, -1) + if len(matches) == 0 || len(reTextLines.FindAll(src, -1)) != len(matches) { + return nil + } + result := make(map[string]bool) + for i, m := range matches { + flags := string(src[m[4]:m[5]]) + if !hasAsmFlag(flags, "NOSPLIT") { + return nil + } + end := len(src) + if i+1 < len(matches) { + end = matches[i+1][0] + } + if !hasAsmFlag(flags, "NOFRAME") && nativeCallRE.Match(src[m[1]:end]) { + return nil + } + result[string(src[m[2]:m[3]])] = true + } + return result +} + +var reTextLines = regexp.MustCompile(`(?m)^\s*TEXT\b`) +var nativeCallRE = regexp.MustCompile(`(?m)^\s*(?:CALL|BL)\s`) + +func hasAsmFlag(flags, want string) bool { + for _, f := range strings.Split(flags, "|") { + if strings.TrimSpace(f) == want { + return true + } + } + return false +} diff --git a/native_arm64_object_test.go b/native_arm64_object_test.go new file mode 100644 index 000000000..97c4dc0c7 --- /dev/null +++ b/native_arm64_object_test.go @@ -0,0 +1,240 @@ +package plan9asm + +import ( + "encoding/binary" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" +) + +func nativeObjectFixture(t *testing.T) []byte { + t.Helper() + dir := t.TempDir() + src := filepath.Join(dir, "native.s") + out := filepath.Join(dir, "native.o") + code := `#include "textflag.h" +TEXT callback<>(SB), NOSPLIT|NOFRAME, $0 + SUB $16, RSP + MOVD R30, (RSP) + BL imported_strlen(SB) + MOVD (RSP), R30 + ADD $16, RSP + RET +GLOBL ·entry(SB), RODATA, $8 +DATA ·entry(SB)/8, $callback<>(SB) +TEXT mixedtramp<>(SB), NOSPLIT, $0-0 + FMOVD R1, F0 + JMP imported_mixed(SB) +GLOBL ·mixedEntry(SB), RODATA, $8 +DATA ·mixedEntry(SB)/8, $mixedtramp<>(SB) +` + if err := os.WriteFile(src, []byte(code), 0600); err != nil { + t.Fatal(err) + } + cmd := exec.Command("go", "tool", "asm", "-p", "probe", "-I", filepath.Join(runtime.GOROOT(), "pkg", "include"), "-o", out, src) + cmd.Env = append(os.Environ(), "GOOS=darwin", "GOARCH=arm64") + if b, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("asm: %v\n%s", err, b) + } + b, err := os.ReadFile(out) + if err != nil { + t.Fatal(err) + } + return b +} + +func TestNativeARM64Object(t *testing.T) { + obj := nativeObjectFixture(t) + funcs := map[string]bool{"callback": true, "mixedtramp": true} + imports := map[string]string{"imported_strlen": "strlen", "imported_mixed": "mixed"} + asm, data, err := TranslateNativeARM64Object(obj, funcs, imports, "probe") + if err != nil { + t.Fatal(err) + } + if len(data) != 2 || data[0].Name != "probe.entry" || data[0].Size != 8 { + t.Fatalf("data = %v", data) + } + if !strings.Contains(asm, `bl "_strlen"`) || !strings.Contains(asm, `.quad "Lllgo_native_`) { + t.Fatalf("missing relocations:\n%s", asm) + } + if _, _, err := TranslateNativeARM64Object(obj, funcs, nil, "probe"); err == nil { + t.Fatal("accepted undeclared foreign call") + } + if _, _, err := TranslateNativeARM64Object(obj, map[string]bool{"missing": true}, imports, "probe"); err == nil { + t.Fatal("accepted missing function") + } + for n := 0; n < len(obj); n++ { + if _, _, err := TranslateNativeARM64Object(obj[:n], funcs, imports, "probe"); err == nil { + t.Fatalf("accepted truncated object at %d", n) + } + } + t.Run("reject unsupported relocation", func(t *testing.T) { + damaged := append([]byte(nil), obj...) + r, err := readNativeObject(damaged) + if err != nil { + t.Fatal(err) + } + for i, sym := range r.syms[:r.ndef] { + if sym.name != "callback" { + continue + } + first := binary.LittleEndian.Uint32(r.block(11)[i*4:]) + rel := r.block(14)[first*23:] + binary.LittleEndian.PutUint16(rel[5:], 0xffff) + break + } + if _, _, err := TranslateNativeARM64Object(damaged, funcs, imports, "probe"); err == nil { + t.Fatal("accepted unknown relocation") + } + }) + if runtime.GOOS != "darwin" || runtime.GOARCH != "arm64" { + return + } + dir := t.TempDir() + s := filepath.Join(dir, "native.s") + c := filepath.Join(dir, "main.c") + exe := filepath.Join(dir, "probe") + if err := os.WriteFile(s, []byte(asm), 0600); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(c, []byte(`extern void *entry __asm("_probe.entry"); +extern void *mixedEntry __asm("_probe.mixedEntry"); +unsigned long mixed(unsigned long x, double y) { return x + (unsigned long)y; } +int main(void) { + if (((unsigned long (*)(const char *))entry)("native ABI") != 10) return 1; + return ((unsigned long (*)(unsigned long, unsigned long))mixedEntry)(5, 0x4000000000000000UL) != 7; +} +`), 0600); err != nil { + t.Fatal(err) + } + cmd := exec.Command("clang", s, c, "-o", exe) + if b, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("link: %v\n%s", err, b) + } + if b, err := exec.Command(exe).CombinedOutput(); err != nil { + t.Fatalf("callback: %v\n%s", err, b) + } +} + +func TestForeignARM64Selection(t *testing.T) { + for _, tc := range []struct { + src string + want bool + }{ + {"TEXT raw<>(SB), NOSPLIT, $0-0\n JMP imported(SB)\n", true}, + {"TEXT raw<>(SB), NOSPLIT, $0-0\r\n JMP imported(SB)\r\n", true}, + {"TEXT raw<>(SB), NOSPLIT, $0-0\n RET\nTEXT raw2<>(SB),NOSPLIT,$0\nRET\n", true}, + {"TEXT raw<>(SB), NOSPLIT|NOFRAME, $0\n BL imported(SB)\n RET\n", true}, + {"TEXT ·declared(SB), NOSPLIT, $0-0\n RET\n", false}, + {"TEXT raw<>(SB), 0, $0-0\n RET\n", false}, + {"TEXT raw<>(SB), NOSPLIT, $8-0\n RET\n", false}, + {"TEXT raw<>(SB), NOSPLIT, $0-0\n BL imported(SB)\n RET\n", false}, + {"TEXT raw<>(SB), NOSPLIT, $0-0\n RET\nTEXT ·goFunc(SB),NOSPLIT,$0\nRET\n", false}, + } { + if got := len(ForeignARM64Functions([]byte(tc.src))) != 0; got != tc.want { + t.Errorf("selection(%q) = %v", tc.src, got) + } + } +} + +// Exercise the parser's rejection contract using valid assembler output with +// one damaged field at a time, so each diagnostic identifies the broken field. +func TestNativeARM64MalformedFields(t *testing.T) { + obj := nativeObjectFixture(t) + funcs := map[string]bool{"callback": true, "mixedtramp": true} + imports := map[string]string{"imported_strlen": "strlen", "imported_mixed": "mixed"} + cases := []struct { + name, want string + damage func(*nativeObject) + }{ + {"header", "invalid native object header", func(r *nativeObject) { binary.LittleEndian.PutUint32(r.b[20:], nativeHeaderSize-1) }}, + {"symbol table", "invalid native symbol table", func(r *nativeObject) { + binary.LittleEndian.PutUint32(r.b[20+nativeBlkNonpkgref*4:], r.blocks[nativeBlkNonpkgref]-1) + }}, + {"symbol name", "invalid native symbol name", func(r *nativeObject) { binary.LittleEndian.PutUint32(r.block(nativeBlkSymdef), ^uint32(0)) }}, + {"symbol size", "native symbol too large", func(r *nativeObject) { + binary.LittleEndian.PutUint32(r.block(nativeBlkSymdef)[nativeSymSizeOffset:], 1<<30) + }}, + {"symbol payload", "oversized native symbol", func(r *nativeObject) { + binary.LittleEndian.PutUint32(r.block(nativeBlkSymdef)[nativeSymSizeOffset:], 0) + }}, + {"alignment", "invalid native alignment", func(r *nativeObject) { + binary.LittleEndian.PutUint32(nativeFixtureSym(r, "probe.entry")[nativeSymAlign:], 3) + }}, + {"indices length", "invalid native object indices", func(r *nativeObject) { + binary.LittleEndian.PutUint32(r.b[20+nativeBlkRelocIdx*4:], r.blocks[nativeBlkRelocIdx]+1) + }}, + {"index range", "invalid native object index", func(r *nativeObject) { binary.LittleEndian.PutUint32(r.block(nativeBlkDataIdx), ^uint32(0)) }}, + {"split function", "not NOSPLIT", func(r *nativeObject) { nativeFixtureSym(r, "callback")[nativeSymFlags] &^= nativeNoSplit }}, + {"relocation bounds", "invalid native relocation", func(r *nativeObject) { binary.LittleEndian.PutUint32(nativeCallbackReloc(r), ^uint32(0)) }}, + {"Go ABI reference", "Go package index", func(r *nativeObject) { binary.LittleEndian.PutUint32(nativeCallbackReloc(r)[nativeRelocPkg:], 1) }}, + {"reference range", "invalid native symbol reference", func(r *nativeObject) { + binary.LittleEndian.PutUint32(nativeCallbackReloc(r)[nativeRelocSym:], ^uint32(0)) + }}, + {"branch addend", "unsupported native branch addend", func(r *nativeObject) { binary.LittleEndian.PutUint64(nativeCallbackReloc(r)[nativeRelocAddend:], 8) }}, + {"branch opcode", "invalid native branch", func(r *nativeObject) { + for i, s := range r.syms { + if s.name == "callback" { + binary.LittleEndian.PutUint32(r.payload(i)[binary.LittleEndian.Uint32(nativeCallbackReloc(r)):], 0) + } + } + }}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + damaged := append([]byte(nil), obj...) + r, err := readNativeObject(damaged) + if err != nil { + t.Fatal(err) + } + tc.damage(r) + _, _, err = TranslateNativeARM64Object(damaged, funcs, imports, "probe") + if err == nil || !strings.Contains(err.Error(), tc.want) { + t.Fatalf("got %v, want %q", err, tc.want) + } + }) + } +} + +func nativeFixtureSym(r *nativeObject, name string) []byte { + for i, s := range r.syms { + if s.name == name { + for block, count := range r.counts { + if i < count { + return r.block(nativeBlkSymdef + block)[i*nativeSymSize:] + } + i -= count + } + } + } + panic("fixture missing symbol " + name) +} + +func nativeCallbackReloc(r *nativeObject) []byte { + for i, s := range r.syms { + if s.name == "callback" { + first := binary.LittleEndian.Uint32(r.block(nativeBlkRelocIdx)[i*4:]) + return r.block(nativeBlkReloc)[first*nativeRelocSize:] + } + } + panic("fixture missing callback") +} + +func TestNativeARM64SymbolReferences(t *testing.T) { + r := &nativeObject{counts: [5]int{2, 3, 4, 5, 6}} + for _, tc := range []struct { + pkg uint32 + sym uint32 + want int + }{ + {nativePkgSelf, 1, 1}, {nativePkgHashed64, 2, 4}, {nativePkgHashed, 3, 8}, {nativePkgNone, 10, 19}, + } { + got, err := r.target(tc.pkg, tc.sym) + if err != nil || got != tc.want { + t.Fatalf("target(%x,%d) = %d, %v; want %d", tc.pkg, tc.sym, got, err, tc.want) + } + } +} diff --git a/testdata/corpus/go.mod b/testdata/corpus/go.mod index d46191f19..3fa2be21c 100644 --- a/testdata/corpus/go.mod +++ b/testdata/corpus/go.mod @@ -19,7 +19,7 @@ require ( github.com/klauspost/reedsolomon v1.14.2 github.com/minio/highwayhash v1.0.4 github.com/modern-go/gls v0.0.0-20250215024828-78308f6bb19d - github.com/pierrec/lz4/v4 v4.1.29 + github.com/pierrec/lz4/v4 v4.1.30 github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50 github.com/tmthrgd/go-bitwise v0.0.0-20190904053232-1430ee983fca github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc diff --git a/testdata/corpus/go.sum b/testdata/corpus/go.sum index dc017377b..8e7ff4706 100644 --- a/testdata/corpus/go.sum +++ b/testdata/corpus/go.sum @@ -41,6 +41,8 @@ github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= github.com/pierrec/lz4/v4 v4.1.29 h1:CDQY6qZOLI4DW0Nx6R1vRrifrCeQHnNXkMb0hZWXFjg= github.com/pierrec/lz4/v4 v4.1.29/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= +github.com/pierrec/lz4/v4 v4.1.30 h1:cchX8N2DVP668WkElI9QMwVyoNabLkq1LofDHFeIrdg= +github.com/pierrec/lz4/v4 v4.1.30/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= diff --git a/testdata/corpus/reported-libraries.json b/testdata/corpus/reported-libraries.json index 0c1cb29bb..b11dcad44 100644 --- a/testdata/corpus/reported-libraries.json +++ b/testdata/corpus/reported-libraries.json @@ -360,7 +360,7 @@ "id": "pierrec-lz4-v4", "origin": "ecosystem-scan", "module": "github.com/pierrec/lz4/v4", - "version": "v4.1.29", + "version": "v4.1.30", "inventory": { "darwin/amd64": { "asm_files": 1,