]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/link: allow one to specify the data section in the internal linker
authorKevaundray Wedderburn <kevtheappdev@gmail.com>
Wed, 10 Sep 2025 19:25:49 +0000 (19:25 +0000)
committerCherry Mui <cherryyz@google.com>
Thu, 11 Sep 2025 17:59:06 +0000 (10:59 -0700)
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 <markfreeman@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

src/cmd/link/elf_test.go
src/cmd/link/internal/ld/data.go
src/cmd/link/internal/ld/ld_test.go
src/cmd/link/internal/ld/main.go

index 59a19a20d2a215228da8b91f8fd7580210916b49..c82eae8866769877023694b43b31132a12a5febd 100644 (file)
@@ -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)
+       }
+}
index 138547a3d3f424e32a5125b8b5016f852dc1f989..a49cae0d9527ca4331a500af695a82a2ac5b3a73 100644 (file)
@@ -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
index 4f343f3eb80f92b4b34d6afda23bb4269e8f469c..9a27ac8c764fc56a11531e7143b4ec9a2155534a 100644 (file)
@@ -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)
+               }
+       }
+}
index cc6b2fd37a3d79d552591aded546d738fe8cadb3..d913953944bc431c6ea0b3873f9176811c90fde6 100644 (file)
@@ -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")
        }