From cf5e993177dac0d9fd30e961416a391c37da1815 Mon Sep 17 00:00:00 2001 From: Kevaundray Wedderburn Date: Wed, 10 Sep 2025 19:25:49 +0000 Subject: [PATCH] cmd/link: allow one to specify the data section in the internal linker Fixes #74945 Change-Id: Ia73a8dcdf707222e822522daaa7f31a38b1c31e6 GitHub-Last-Rev: da1526ad8cebd5cfa2f979d49d86f3424d192ce0 GitHub-Pull-Request: golang/go#75117 Reviewed-on: https://go-review.googlesource.com/c/go/+/698355 Reviewed-by: Mark Freeman Reviewed-by: Cherry Mui LUCI-TryBot-Result: Go LUCI --- src/cmd/link/elf_test.go | 109 ++++++++++++++++++++++++++++ src/cmd/link/internal/ld/data.go | 7 +- src/cmd/link/internal/ld/ld_test.go | 22 ++++++ src/cmd/link/internal/ld/main.go | 5 ++ 4 files changed, 142 insertions(+), 1 deletion(-) diff --git a/src/cmd/link/elf_test.go b/src/cmd/link/elf_test.go index 59a19a20d2..c82eae8866 100644 --- a/src/cmd/link/elf_test.go +++ b/src/cmd/link/elf_test.go @@ -59,6 +59,12 @@ package main func main() {} ` +var goSourceWithData = ` +package main +var globalVar = 42 +func main() { println(&globalVar) } +` + // The linker used to crash if an ELF input file had multiple text sections // with the same name. func TestSectionsWithSameName(t *testing.T) { @@ -569,3 +575,106 @@ func TestFlagR(t *testing.T) { t.Errorf("executable failed to run: %v\n%s", err, out) } } + +func TestFlagD(t *testing.T) { + // Test that using the -D flag to specify data section address generates + // a working binary with data at the specified address. + t.Parallel() + testFlagD(t, "0x10000000", "", 0x10000000) +} + +func TestFlagDUnaligned(t *testing.T) { + // Test that using the -D flag with an unaligned address errors out + t.Parallel() + testFlagDError(t, "0x10000123", "", "invalid -D value 0x10000123") +} + +func TestFlagDWithR(t *testing.T) { + // Test that using the -D flag with -R flag errors on unaligned address. + t.Parallel() + testFlagDError(t, "0x30001234", "8192", "invalid -D value 0x30001234") +} + +func testFlagD(t *testing.T, dataAddr string, roundQuantum string, expectedAddr uint64) { + testenv.MustHaveGoBuild(t) + tmpdir := t.TempDir() + src := filepath.Join(tmpdir, "x.go") + if err := os.WriteFile(src, []byte(goSourceWithData), 0444); err != nil { + t.Fatal(err) + } + exe := filepath.Join(tmpdir, "x.exe") + + // Build linker flags + ldflags := "-D=" + dataAddr + if roundQuantum != "" { + ldflags += " -R=" + roundQuantum + } + + cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags="+ldflags, "-o", exe, src) + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("build failed: %v, output:\n%s", err, out) + } + + cmd = testenv.Command(t, exe) + if out, err := cmd.CombinedOutput(); err != nil { + t.Errorf("executable failed to run: %v\n%s", err, out) + } + + ef, err := elf.Open(exe) + if err != nil { + t.Fatalf("open elf file failed: %v", err) + } + defer ef.Close() + + // Find the first data-related section to verify segment placement + var firstDataSectionAddr uint64 + var found bool = false + for _, sec := range ef.Sections { + if sec.Type == elf.SHT_PROGBITS || sec.Type == elf.SHT_NOBITS { + // These sections are writable, allocated at runtime, but not executable + isWrite := sec.Flags&elf.SHF_WRITE != 0 + isExec := sec.Flags&elf.SHF_EXECINSTR != 0 + isAlloc := sec.Flags&elf.SHF_ALLOC != 0 + + if isWrite && !isExec && isAlloc { + addrLower := sec.Addr < firstDataSectionAddr + if !found || addrLower { + firstDataSectionAddr = sec.Addr + found = true + } + } + } + } + + if !found { + t.Fatalf("can't find any writable data sections") + } + if firstDataSectionAddr != expectedAddr { + t.Errorf("data section starts at 0x%x, expected 0x%x", firstDataSectionAddr, expectedAddr) + } +} + +func testFlagDError(t *testing.T, dataAddr string, roundQuantum string, expectedError string) { + testenv.MustHaveGoBuild(t) + tmpdir := t.TempDir() + src := filepath.Join(tmpdir, "x.go") + if err := os.WriteFile(src, []byte(goSourceWithData), 0444); err != nil { + t.Fatal(err) + } + exe := filepath.Join(tmpdir, "x.exe") + + // Build linker flags + ldflags := "-D=" + dataAddr + if roundQuantum != "" { + ldflags += " -R=" + roundQuantum + } + + cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags="+ldflags, "-o", exe, src) + out, err := cmd.CombinedOutput() + if err == nil { + t.Fatalf("expected build to fail with unaligned data address, but it succeeded") + } + if !strings.Contains(string(out), expectedError) { + t.Errorf("expected error message to contain %q, got:\n%s", expectedError, out) + } +} diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index 138547a3d3..a49cae0d95 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -2881,7 +2881,12 @@ func (ctxt *Link) address() []*sym.Segment { } order = append(order, &Segdata) Segdata.Rwx = 06 - Segdata.Vaddr = va + if *FlagDataAddr != -1 { + Segdata.Vaddr = uint64(*FlagDataAddr) + va = Segdata.Vaddr + } else { + Segdata.Vaddr = va + } var data *sym.Section var noptr *sym.Section var bss *sym.Section diff --git a/src/cmd/link/internal/ld/ld_test.go b/src/cmd/link/internal/ld/ld_test.go index 4f343f3eb8..9a27ac8c76 100644 --- a/src/cmd/link/internal/ld/ld_test.go +++ b/src/cmd/link/internal/ld/ld_test.go @@ -442,3 +442,25 @@ func d() t.Errorf("Trampoline b-tramp0 exists unnecessarily") } } + +func TestRounding(t *testing.T) { + testCases := []struct { + input int64 + quantum int64 + expected int64 + }{ + {0x30000000, 0x2000, 0x30000000}, // Already aligned + {0x30002000, 0x2000, 0x30002000}, // Exactly on boundary + {0x30001234, 0x2000, 0x30002000}, + {0x30001000, 0x2000, 0x30002000}, + {0x30001fff, 0x2000, 0x30002000}, + } + + for _, tc := range testCases { + result := Rnd(tc.input, tc.quantum) + if result != tc.expected { + t.Errorf("Rnd(0x%x, 0x%x) = 0x%x, expected 0x%x", + tc.input, tc.quantum, result, tc.expected) + } + } +} diff --git a/src/cmd/link/internal/ld/main.go b/src/cmd/link/internal/ld/main.go index cc6b2fd37a..d913953944 100644 --- a/src/cmd/link/internal/ld/main.go +++ b/src/cmd/link/internal/ld/main.go @@ -105,6 +105,7 @@ var ( FlagStrictDups = flag.Int("strictdups", 0, "sanity check duplicate symbol contents during object file reading (1=warn 2=err).") FlagRound = flag.Int64("R", -1, "set address rounding `quantum`") FlagTextAddr = flag.Int64("T", -1, "set the start address of text symbols") + FlagDataAddr = flag.Int64("D", -1, "set the start address of data symbols") FlagFuncAlign = flag.Int("funcalign", 0, "set function align to `N` bytes") flagEntrySymbol = flag.String("E", "", "set `entry` symbol name") flagPruneWeakMap = flag.Bool("pruneweakmap", true, "prune weak mapinit refs") @@ -317,6 +318,10 @@ func Main(arch *sys.Arch, theArch Arch) { bench.Start("Archinit") thearch.Archinit(ctxt) + if *FlagDataAddr != -1 && *FlagDataAddr%*FlagRound != 0 { + Exitf("invalid -D value 0x%x: not aligned to rounding quantum 0x%x", *FlagDataAddr, *FlagRound) + } + if ctxt.linkShared && !ctxt.IsELF { Exitf("-linkshared can only be used on elf systems") } -- 2.52.0