fix(asm): emit NOPTR data as its own symbol kind
Assisted-by: GLM 5.3
This commit is contained in:
+14
-65
@@ -178,26 +178,10 @@ func main() {
|
||||
if err != nil {
|
||||
t.Fatalf("baseline build: %v\n%s", err, buildLog)
|
||||
}
|
||||
var work, linkLine, asmObj 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_arm64.s") && !strings.Contains(line, "-gensymabis"):
|
||||
asmObj = fieldAfter(line, "-o")
|
||||
case strings.Contains(line, "/link ") && strings.Contains(line, "-importcfg"):
|
||||
linkLine = line
|
||||
}
|
||||
}
|
||||
if work == "" || asmObj == "" {
|
||||
t.Skipf("could not parse build log (work=%q asmObj=%q)", work, asmObj)
|
||||
}
|
||||
defer os.RemoveAll(work)
|
||||
st := parseBuildLog(t, buildLog, "main_arm64.s")
|
||||
defer os.RemoveAll(st.work)
|
||||
|
||||
// Expand $WORK in the object path.
|
||||
asmObj = strings.ReplaceAll(asmObj, "$WORK", work)
|
||||
|
||||
// Read the toolchain-produced object and assemble the same source with gasm.
|
||||
// Assemble the same source with gasm and substitute the object.
|
||||
src, err := os.ReadFile(filepath.Join(dir, "main_arm64.s"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -210,30 +194,16 @@ func main() {
|
||||
if err != nil {
|
||||
t.Fatalf("AssembleFileARM64: %v", err)
|
||||
}
|
||||
gasmObj, err := img.GOObjectAARCH64("a64link", "main_arm64.s")
|
||||
// The package path is "main", the prefix the Go code's references carry.
|
||||
gasmObj, err := img.GOObjectAARCH64("main", "main_arm64.s")
|
||||
if err != nil {
|
||||
t.Fatalf("GOObjectAARCH64: %v", err)
|
||||
}
|
||||
|
||||
// Replace the toolchain-produced object with gasm's.
|
||||
if err := os.WriteFile(asmObj, gasmObj, 0o644); err != nil {
|
||||
t.Fatalf("write gasm object: %v", err)
|
||||
}
|
||||
|
||||
// Re-link.
|
||||
if linkLine == "" {
|
||||
t.Skip("could not find link command in build log")
|
||||
}
|
||||
// Expand $WORK in the link command.
|
||||
linkLine = strings.ReplaceAll(linkLine, "$WORK", work)
|
||||
linkCmd := exec.Command("bash", "-c", "cd "+dir+" && "+linkLine)
|
||||
linkCmd.Env = append(os.Environ(), "GOARCH=arm64")
|
||||
if out, err := linkCmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("re-link with gasm object: %v\n%s", err, out)
|
||||
}
|
||||
substituteAndRelink(t, goBin, dir, st, filepath.Join(dir, "prog2"),
|
||||
gasmObj, "GOARCH=arm64")
|
||||
|
||||
// Verify the binary exists and contains the symbol.
|
||||
binPath := filepath.Join(dir, "prog")
|
||||
binPath := filepath.Join(dir, "prog2")
|
||||
if _, err := os.Stat(binPath); err != nil {
|
||||
t.Fatalf("binary not found: %v", err)
|
||||
}
|
||||
@@ -296,23 +266,8 @@ func main() {
|
||||
if err != nil {
|
||||
t.Fatalf("baseline build: %v\n%s", err, buildLog)
|
||||
}
|
||||
var work, linkLine, asmObj 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_arm64.s") && !strings.Contains(line, "-gensymabis"):
|
||||
asmObj = fieldAfter(line, "-o")
|
||||
case strings.Contains(line, "/link ") && strings.Contains(line, "-importcfg"):
|
||||
linkLine = line
|
||||
}
|
||||
}
|
||||
if work == "" || asmObj == "" || linkLine == "" {
|
||||
t.Skipf("could not parse build log (work=%q asmObj=%q link=%q)", work, asmObj, linkLine)
|
||||
}
|
||||
defer os.RemoveAll(work)
|
||||
asmObj = strings.ReplaceAll(asmObj, "$WORK", work)
|
||||
linkLine = strings.ReplaceAll(linkLine, "$WORK", work)
|
||||
st := parseBuildLog(t, buildLog, "main_arm64.s")
|
||||
defer os.RemoveAll(st.work)
|
||||
|
||||
src, err := os.ReadFile(filepath.Join(dir, "main_arm64.s"))
|
||||
if err != nil {
|
||||
@@ -326,19 +281,13 @@ func main() {
|
||||
if err != nil {
|
||||
t.Fatalf("AssembleFileARM64: %v", err)
|
||||
}
|
||||
gasmObj, err := img.GOObjectAARCH64("a64dlink", "main_arm64.s")
|
||||
gasmObj, err := img.GOObjectAARCH64("main", "main_arm64.s")
|
||||
if err != nil {
|
||||
t.Fatalf("GOObjectAARCH64: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(asmObj, gasmObj, 0o644); err != nil {
|
||||
t.Fatalf("write gasm object: %v", err)
|
||||
}
|
||||
linkCmd := exec.Command("bash", "-c", "cd "+dir+" && "+linkLine)
|
||||
linkCmd.Env = append(os.Environ(), "GOARCH=arm64")
|
||||
if out, err := linkCmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("re-link with gasm object: %v\n%s", err, out)
|
||||
}
|
||||
binData, err := os.ReadFile(filepath.Join(dir, "prog"))
|
||||
substituteAndRelink(t, goBin, dir, st, filepath.Join(dir, "prog2"),
|
||||
gasmObj, "GOARCH=arm64")
|
||||
binData, err := os.ReadFile(filepath.Join(dir, "prog2"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -4003,6 +4003,7 @@ func AssembleFileARM64(f *ast.File) (*Image, error) {
|
||||
Size: d.size,
|
||||
Static: d.static,
|
||||
Rodata: d.rodata,
|
||||
Noptr: d.noptr,
|
||||
Dupok: d.dupok,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ const (
|
||||
const (
|
||||
kindSTEXT = 1
|
||||
kindSRODATA = 3
|
||||
kindSNOPTRDATA = 5
|
||||
kindSDATA = 7
|
||||
kindSDWARFFCN = 14
|
||||
kindSDWARFLINES = 20
|
||||
@@ -305,9 +306,16 @@ func (img *Image) emitGOObject(pkgPath, srcPath string, pre []byte, minLC int, r
|
||||
if !d.Static {
|
||||
name = pkgPath + "." + name
|
||||
}
|
||||
// RODATA implies no pointers, so it wins over NOPTR: the kind is
|
||||
// SRODATA either way, exactly as the toolchain chooses it. Plain
|
||||
// NOPTR data is SNOPTRDATA, which the linker keeps out of the GC's
|
||||
// type scan; a plain SDATA symbol would demand Go type information
|
||||
// no assembly file can supply, and the link would fail.
|
||||
typ := uint8(kindSDATA)
|
||||
if d.Rodata {
|
||||
typ = kindSRODATA
|
||||
} else if d.Noptr {
|
||||
typ = kindSNOPTRDATA
|
||||
}
|
||||
flag := uint8(0)
|
||||
if d.Dupok {
|
||||
|
||||
@@ -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
|
||||
|
||||
+13
-3
@@ -134,7 +134,8 @@ type DataSymbol struct {
|
||||
Offset int // byte offset within Data
|
||||
Size int
|
||||
Static bool // the <> marker: file-local, not exported
|
||||
Rodata bool // the RODATA flag: read-only data
|
||||
Rodata bool // the RODATA flag: read-only data (implies no pointers)
|
||||
Noptr bool // the NOPTR flag: data with no pointers, kept out of GC scanning
|
||||
Dupok bool // the DUPOK flag: duplicate-OK
|
||||
// Relocs carries the symbol-valued DATA initialisers ("DATA s+0(SB)/8,
|
||||
// $other(SB)"): fields of this symbol's data that hold another symbol's
|
||||
@@ -269,6 +270,7 @@ func AssembleFile(f *ast.File, opts ...AssembleOption) (*Image, error) {
|
||||
Size: len(d.buf),
|
||||
Static: d.static,
|
||||
Rodata: d.rodata,
|
||||
Noptr: d.noptr,
|
||||
Dupok: d.dupok,
|
||||
})
|
||||
img.Data = append(img.Data, d.buf...)
|
||||
@@ -413,6 +415,7 @@ func AssembleFileRISCV(f *ast.File) (*Image, error) {
|
||||
Size: d.size,
|
||||
Static: d.static,
|
||||
Rodata: d.rodata,
|
||||
Noptr: d.noptr,
|
||||
Dupok: d.dupok,
|
||||
})
|
||||
}
|
||||
@@ -485,6 +488,7 @@ func AssembleFileLOONG64(f *ast.File) (*Image, error) {
|
||||
Size: d.size,
|
||||
Static: d.static,
|
||||
Rodata: d.rodata,
|
||||
Noptr: d.noptr,
|
||||
Dupok: d.dupok,
|
||||
})
|
||||
}
|
||||
@@ -546,6 +550,7 @@ type dataSym struct {
|
||||
size int
|
||||
static bool
|
||||
rodata bool
|
||||
noptr bool
|
||||
dupok bool
|
||||
// relocs are the symbol-valued DATA fields, in declaration order; Off
|
||||
// is relative to the symbol's data start.
|
||||
@@ -587,12 +592,14 @@ func collectData(f *ast.File) ([]dataSym, error) {
|
||||
switch f {
|
||||
case "RODATA":
|
||||
ds.rodata = true
|
||||
case "NOPTR":
|
||||
ds.noptr = true
|
||||
case "DUPOK":
|
||||
ds.dupok = true
|
||||
default:
|
||||
// Legacy numeric flag constants (runtime/textflag.h):
|
||||
// DUPOK is 2, RODATA is 8; combinations arrive as one
|
||||
// number (e.g. 10 = RODATA|DUPOK).
|
||||
// DUPOK is 2, RODATA is 8, NOPTR is 16; combinations arrive
|
||||
// as one number (e.g. 10 = RODATA|DUPOK).
|
||||
if n, err := strconv.Atoi(f); err == nil {
|
||||
if n&2 != 0 {
|
||||
ds.dupok = true
|
||||
@@ -600,6 +607,9 @@ func collectData(f *ast.File) ([]dataSym, error) {
|
||||
if n&8 != 0 {
|
||||
ds.rodata = true
|
||||
}
|
||||
if n&16 != 0 {
|
||||
ds.noptr = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-26
@@ -361,23 +361,8 @@ func main() {
|
||||
if err != nil {
|
||||
t.Fatalf("baseline build: %v\n%s", err, buildLog)
|
||||
}
|
||||
var work, linkLine, asmObj 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, "/link ") && strings.Contains(line, "-importcfg"):
|
||||
linkLine = line
|
||||
}
|
||||
}
|
||||
if work == "" || asmObj == "" || linkLine == "" {
|
||||
t.Skipf("could not parse build log (work=%q asmObj=%q link=%q)", work, asmObj, linkLine)
|
||||
}
|
||||
defer os.RemoveAll(work)
|
||||
asmObj = strings.ReplaceAll(asmObj, "$WORK", work)
|
||||
linkLine = strings.ReplaceAll(linkLine, "$WORK", work)
|
||||
st := parseBuildLog(t, buildLog, "main_amd64.s")
|
||||
defer os.RemoveAll(st.work)
|
||||
|
||||
// Assemble the same source with gasm and substitute the object.
|
||||
src, err := os.ReadFile(filepath.Join(dir, "main_amd64.s"))
|
||||
@@ -392,21 +377,18 @@ func main() {
|
||||
if err != nil {
|
||||
t.Fatalf("AssembleFile: %v", err)
|
||||
}
|
||||
gasmObj, err := img.GOObject("dlink", "main_amd64.s")
|
||||
// The package path is "main": the linker resolves the Go code's
|
||||
// references against main.<name>, so the object must define the symbols
|
||||
// under that prefix whatever the module is called.
|
||||
gasmObj, err := img.GOObject("main", "main_amd64.s")
|
||||
if err != nil {
|
||||
t.Fatalf("GOObject: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(asmObj, gasmObj, 0o644); err != nil {
|
||||
t.Fatalf("write gasm object: %v", err)
|
||||
}
|
||||
linkCmd := exec.Command("bash", "-c", "cd "+dir+" && "+linkLine)
|
||||
if out, err := linkCmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("re-link with gasm object: %v\n%s", err, out)
|
||||
}
|
||||
substituteAndRelink(t, goBin, dir, st, filepath.Join(dir, "prog2"), gasmObj)
|
||||
|
||||
// The linked program must run and find the right function behind the
|
||||
// data word.
|
||||
out, err := exec.Command(filepath.Join(dir, "prog")).CombinedOutput()
|
||||
out, err := exec.Command(filepath.Join(dir, "prog2")).CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("linked program failed: %v\n%s", err, out)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user