fix(asm): emit NOPTR data as its own symbol kind

Assisted-by: GLM 5.3
This commit is contained in:
2026-10-02 00:40:43 +02:00
parent bafb2fd130
commit 17cc49fee4
6 changed files with 175 additions and 94 deletions
+131
View File
@@ -514,6 +514,137 @@ func fieldAfter(line, flag string) string {
return ""
}
// buildLogSteps is what a substitution test needs from a `go build -x -work`
// log: the work directory, the assembler's object, the package archive and
// the link command line.
type buildLogSteps struct {
work string
asmObj string // $WORK expanded
pkgArch string // $WORK expanded
linkLine string // still carries $WORK placeholders
}
// parseBuildLog extracts the build steps from a `go build -x -work` log.
// asmFile names the assembly file whose object the test substitutes. A
// missing step is a failure, not a skip: the toolchain changed shape and the
// substitution would silently test nothing.
func parseBuildLog(t *testing.T, log []byte, asmFile string) buildLogSteps {
t.Helper()
var st buildLogSteps
for line := range strings.SplitSeq(string(log), "\n") {
switch {
case strings.HasPrefix(line, "WORK="):
st.work = strings.TrimPrefix(line, "WORK=")
case strings.Contains(line, "/asm ") && strings.Contains(line, asmFile) && !strings.Contains(line, "-gensymabis"):
st.asmObj = fieldAfter(line, "-o")
case strings.Contains(line, "pack r") && strings.Contains(line, "_pkg_.a"):
rest := strings.TrimSpace(strings.SplitN(line, "pack r", 2)[1])
st.pkgArch = strings.Fields(strings.SplitN(rest, "#", 2)[0])[0]
case strings.Contains(line, "/link ") && strings.Contains(line, "-importcfg"):
st.linkLine = line
}
}
if st.work == "" || st.asmObj == "" || st.pkgArch == "" || st.linkLine == "" {
t.Fatalf("could not locate the build steps (work=%q asmObj=%q pkgArch=%q link=%q):\n%s",
st.work, st.asmObj, st.pkgArch, st.linkLine, log)
}
st.asmObj = strings.ReplaceAll(st.asmObj, "$WORK", st.work)
st.pkgArch = strings.ReplaceAll(st.pkgArch, "$WORK", st.work)
return st
}
// substituteAndRelink swaps the gasm object into the package archive the
// baseline build produced and re-runs the captured link line against the
// rebuilt archive, writing the binary to outBin (the -x log's link step
// always targets the action graph's internal a.out, which the helper
// redirects; the copy to the -o target is a separate build action the helper
// does not need). The archive handed to the linker is proven to carry the
// gasm object byte for byte, so a build-layout change that skipped the
// substitution fails here instead of passing vacuously.
func substituteAndRelink(t *testing.T, goBin, dir string, st buildLogSteps, outBin string, gasmObj []byte, extraEnv ...string) {
t.Helper()
// Extract the archive, overwrite the assembler's member with the gasm
// object and repack (go tool pack has no replace-in-place).
membersDir := filepath.Join(dir, "members")
if err := os.MkdirAll(membersDir, 0o755); err != nil {
t.Fatal(err)
}
extract := exec.Command(goBin, "tool", "pack", "x", st.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(st.asmObj))
if _, err := os.Stat(member); err != nil {
t.Fatalf("the assembler's archive member was not extracted: %v", err)
}
if err := os.Chmod(member, 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(member, gasmObj, 0o644); err != nil {
t.Fatal(err)
}
listCmd := exec.Command(goBin, "tool", "pack", "t", st.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, 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)
}
// Prove the substitution: the archive the linker is about to consume
// holds the gasm object, byte for byte.
checkDir := filepath.Join(dir, "check")
if err := os.MkdirAll(checkDir, 0o755); err != nil {
t.Fatal(err)
}
check := exec.Command(goBin, "tool", "pack", "x", newArch)
check.Dir = checkDir
if out, err := check.CombinedOutput(); err != nil {
t.Fatalf("pack x (verification): %v\n%s", err, out)
}
got, err := os.ReadFile(filepath.Join(checkDir, filepath.Base(st.asmObj)))
if err != nil {
t.Fatalf("read the substituted member back: %v", err)
}
if !bytes.Equal(got, gasmObj) {
t.Fatal("the repacked archive does not carry the gasm object")
}
// Re-link. The line carries a GOROOT assignment and $WORK placeholders;
// GOEXPERIMENT must match the toolchain's own, because the linker
// compares the object header against its configuration.
goExp, _ := exec.Command(goBin, "env", "GOEXPERIMENT").Output()
linkLine := strings.ReplaceAll(st.linkLine, "$WORK", st.work)
linkLine = strings.ReplaceAll(linkLine, filepath.Join(st.work, "b001", "_pkg_.a"), newArch)
linkLine = strings.ReplaceAll(linkLine, filepath.Join(st.work, "b001", "exe", "a.out"), outBin)
env := append(os.Environ(), "GOEXPERIMENT="+strings.TrimSpace(string(goExp)))
env = append(env, extraEnv...)
link := exec.Command("sh", "-c", linkLine)
link.Dir = dir
link.Env = env
if out, err := link.CombinedOutput(); err != nil {
t.Fatalf("link with the gasm object: %v\n%s", err, out)
}
}
// 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