fix(asm): emit NOPTR data as its own symbol kind
Assisted-by: GLM 5.3
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user