fix(goobj): external package and symbol indices and arm64 pair relocations
Assisted-by: GLM 5.3
This commit is contained in:
+188
-1
@@ -117,7 +117,9 @@ DATA mask<>+8(SB)/8, $0x800f0e0d0c0b0a09
|
||||
if len(defs) != 7 {
|
||||
t.Fatalf("symdefs = %d, want 7", len(defs))
|
||||
}
|
||||
if defs[0].name != "mask" || defs[0].abi != 0xffff || defs[0].typ != kindSRODATA || defs[0].size != 16 || defs[0].flag2 != symFlag2Link {
|
||||
// The linkname flag stays clear: the toolchain sets it only for
|
||||
// //go:linkname symbols, and an ordinary static GLOBL is not one.
|
||||
if defs[0].name != "mask" || defs[0].abi != 0xffff || defs[0].typ != kindSRODATA || defs[0].size != 16 || defs[0].flag2 != 0 {
|
||||
t.Errorf("mask symbol = %+v", defs[0])
|
||||
}
|
||||
if defs[1].name != "" || defs[1].typ != kindSDATA || defs[1].size != 28 {
|
||||
@@ -511,3 +513,188 @@ func fieldAfter(line, flag string) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// TestGOObjectExternalPackageLink is the cross-package end-to-end check: a
|
||||
// GOOBJ whose code references a real external package symbol (runtime's
|
||||
// morestack, a plain reference rather than the builtin noctxt form) must
|
||||
// carry a package index that points past the blkPkgIdx table's dummy entry
|
||||
// 0, and the object must link against the real runtime. Pre-fix, the
|
||||
// relocations carried block index 0, which the loader never fills, so the
|
||||
// reference resolved against whatever object was loaded first and the link
|
||||
// failed. The binary is not run: morestack returns to the call site's
|
||||
// stack check, which a hand-written caller has none of.
|
||||
func TestGOObjectExternalPackageLink(t *testing.T) {
|
||||
goBin, err := exec.LookPath("go")
|
||||
if err != nil {
|
||||
t.Skip("no Go toolchain available")
|
||||
}
|
||||
dir := t.TempDir()
|
||||
|
||||
const asmSrc = `
|
||||
#include "textflag.h"
|
||||
|
||||
TEXT ·fn(SB), NOSPLIT, $0-0
|
||||
CALL ·helper(SB)
|
||||
RET
|
||||
|
||||
TEXT ·helper(SB), NOSPLIT, $0-0
|
||||
RET
|
||||
`
|
||||
const mainSrc = `package main
|
||||
|
||||
func fn()
|
||||
func helper()
|
||||
|
||||
func main() {
|
||||
fn()
|
||||
helper()
|
||||
}
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(dir, "main_amd64.s"), []byte(asmSrc), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, "main.go"), []byte(mainSrc), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module extlink\n\ngo 1.27\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Capture the build the toolchain performs and re-run only its link
|
||||
// step with our object swapped into the package archive, mirroring
|
||||
// TestGOObjectLinkAndRun.
|
||||
build := exec.Command(goBin, "build", "-x", "-work", "-o", filepath.Join(dir, "prog"), ".")
|
||||
build.Dir = dir
|
||||
buildLog, err := build.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("baseline build: %v\n%s", err, buildLog)
|
||||
}
|
||||
var work, linkLine, asmObj, pkgArch string
|
||||
for line := range strings.SplitSeq(string(buildLog), "\n") {
|
||||
switch {
|
||||
case strings.HasPrefix(line, "WORK="):
|
||||
work = strings.TrimPrefix(line, "WORK=")
|
||||
case strings.Contains(line, "/asm ") && strings.Contains(line, "main_amd64.s") && !strings.Contains(line, "-gensymabis"):
|
||||
asmObj = fieldAfter(line, "-o")
|
||||
case strings.Contains(line, "pack r") && strings.Contains(line, "_pkg_.a"):
|
||||
pkgArch = strings.TrimSpace(strings.SplitN(line, "pack r", 2)[1])
|
||||
pkgArch = strings.Fields(strings.SplitN(pkgArch, "#", 2)[0])[0]
|
||||
case strings.Contains(line, "/link ") && strings.Contains(line, "-importcfg"):
|
||||
linkLine = line
|
||||
}
|
||||
}
|
||||
if work == "" || asmObj == "" || pkgArch == "" || linkLine == "" {
|
||||
t.Skipf("could not parse build log (work=%q asmObj=%q)", work, asmObj)
|
||||
}
|
||||
defer os.RemoveAll(work)
|
||||
asmObj = strings.ReplaceAll(asmObj, "$WORK", work)
|
||||
pkgArch = strings.ReplaceAll(pkgArch, "$WORK", work)
|
||||
|
||||
// Assemble the source with gasm, then retarget fn's internal call at
|
||||
// a real external package symbol: the reloc's qualified name drives
|
||||
// the export-data resolution the way a source-level runtime·sym(SB)
|
||||
// reference would.
|
||||
f, errs := parser.Parse("main_amd64.s", asmSrc)
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse: %v", errs)
|
||||
}
|
||||
img, err := AssembleFile(f)
|
||||
if err != nil {
|
||||
t.Fatalf("AssembleFile: %v", err)
|
||||
}
|
||||
fn := &img.Funcs[0]
|
||||
for i := range fn.Relocs {
|
||||
fn.Relocs[i].Name = "runtime\u00b7morestack"
|
||||
fn.Relocs[i].External = true
|
||||
}
|
||||
img.Externals = []string{"runtime\u00b7morestack"}
|
||||
obj, err := img.GOObject("main", "main_amd64.s")
|
||||
if err != nil {
|
||||
t.Fatalf("GOObject: %v", err)
|
||||
}
|
||||
|
||||
// Structural check: the blkPkgIdx block reserves entry 0 for the
|
||||
// dummy invalid package and places runtime at entry 1, and fn's call
|
||||
// relocation carries PkgIdx 1.
|
||||
v := openGoobj(t, obj)
|
||||
pkgBlk := v.blk(blkPkgIdx)
|
||||
if len(pkgBlk) != 2*8 {
|
||||
t.Fatalf("blkPkgIdx = %d bytes, want two entries", len(pkgBlk))
|
||||
}
|
||||
le := binary.LittleEndian
|
||||
strEntry := func(i int) string {
|
||||
e := pkgBlk[i*8 : (i+1)*8]
|
||||
return v.str(le.Uint32(e[4:]), le.Uint32(e[0:]))
|
||||
}
|
||||
if s := strEntry(0); s != "" {
|
||||
t.Errorf("blkPkgIdx[0] = %q, want the dummy empty package", s)
|
||||
}
|
||||
if s := strEntry(1); s != "runtime" {
|
||||
t.Errorf("blkPkgIdx[1] = %q, want runtime", s)
|
||||
}
|
||||
relocs := v.blk(blkReloc)
|
||||
// fn is the last non-package symbol (two functions, four pc tables
|
||||
// each); its one reloc is the final record.
|
||||
fnRec := relocs[len(relocs)-23:]
|
||||
if pIdx := le.Uint32(fnRec[15:]); pIdx != 1 {
|
||||
t.Errorf("external reloc PkgIdx = %d, want 1 (runtime)", pIdx)
|
||||
}
|
||||
|
||||
// Swap the object into the package archive and link with cmd/link;
|
||||
// the link line consumes the archive, not the loose object file.
|
||||
membersDir := filepath.Join(dir, "members")
|
||||
if err := os.MkdirAll(membersDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
extract := exec.Command(goBin, "tool", "pack", "x", pkgArch)
|
||||
extract.Dir = membersDir
|
||||
if out, err := extract.CombinedOutput(); err != nil {
|
||||
t.Fatalf("pack x: %v\n%s", err, out)
|
||||
}
|
||||
member := filepath.Join(membersDir, filepath.Base(asmObj))
|
||||
if err := os.Chmod(member, 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(member, obj, 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
listCmd := exec.Command(goBin, "tool", "pack", "t", pkgArch)
|
||||
listOut, err := listCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("pack t: %v\n%s", err, listOut)
|
||||
}
|
||||
newArch := filepath.Join(dir, "pkg.a")
|
||||
args := []string{"tool", "pack", "c", newArch}
|
||||
seen := map[string]bool{}
|
||||
for m := range strings.FieldsSeq(string(listOut)) {
|
||||
if seen[m] {
|
||||
continue
|
||||
}
|
||||
seen[m] = true
|
||||
if err := os.Chmod(filepath.Join(membersDir, m), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
args = append(args, filepath.Join(membersDir, m))
|
||||
}
|
||||
pack := exec.Command(goBin, args...)
|
||||
pack.Dir = membersDir
|
||||
if out, err := pack.CombinedOutput(); err != nil {
|
||||
t.Fatalf("pack c: %v\n%s", err, out)
|
||||
}
|
||||
linkLine = strings.ReplaceAll(linkLine, "$WORK", work)
|
||||
linkLine = strings.ReplaceAll(linkLine, pkgArch, newArch)
|
||||
linkLine = strings.ReplaceAll(linkLine, filepath.Join(work, "b001", "exe", "a.out"), filepath.Join(dir, "prog2"))
|
||||
linkCmd := exec.Command("sh", "-c", "cd "+dir+" && "+linkLine)
|
||||
if out, err := linkCmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("re-link with gasm object: %v\n%s", err, out)
|
||||
}
|
||||
|
||||
// The call must have resolved to the real runtime symbol.
|
||||
dump, err := exec.Command(goBin, "tool", "objdump", "-s", "main.fn", filepath.Join(dir, "prog2")).CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("objdump main.fn: %v\n%s", err, dump)
|
||||
}
|
||||
if !bytes.Contains(dump, []byte("runtime.morestack")) {
|
||||
t.Errorf("main.fn does not call runtime.morestack:\n%s", dump)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user