diff --git a/src/cmd/link/elf_test.go b/src/cmd/link/elf_test.go index 59a19a20d2a215..61522970216d46 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,83 @@ 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 gets rounded + // to the default alignment boundary + t.Parallel() + testFlagD(t, "0x10000123", "", 0x10001000) +} + +func TestFlagDWithR(t *testing.T) { + // Test that using the -D flag with -R flag works together. + // The unaligned data address gets rounded to the specified alignment quantum. + t.Parallel() + testFlagD(t, "0x30001234", "8192", 0x30002000) +} + +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) + } +} diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index 138547a3d3f424..77a269b136eebd 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(Rnd(*FlagDataAddr, *FlagRound)) + 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 4f343f3eb80f92..9a27ac8c764fc5 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 cc6b2fd37a3d79..66cafeb20f34bc 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")