cmd/link: allow one to specify the data section in the internal linker

Fixes #74945

Change-Id: Ia73a8dcdf707222e822522daaa7f31a38b1c31e6
GitHub-Last-Rev: da1526ad8c
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>
This commit is contained in:
Kevaundray Wedderburn
2025-09-10 19:25:49 +00:00
committed by Cherry Mui
parent cdb3d467fa
commit cf5e993177
4 changed files with 142 additions and 1 deletions

View 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)
}
}

View 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

View 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)
}
}
}

View 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")
}