Skip to content

Commit cf5e993

Browse files
kevaundraycherrymui
authored andcommitted
cmd/link: allow one to specify the data section in the internal linker
Fixes golang#74945 Change-Id: Ia73a8dcdf707222e822522daaa7f31a38b1c31e6 GitHub-Last-Rev: da1526a GitHub-Pull-Request: golang#75117 Reviewed-on: https://go-review.googlesource.com/c/go/+/698355 Reviewed-by: Mark Freeman <[email protected]> Reviewed-by: Cherry Mui <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent cdb3d46 commit cf5e993

File tree

4 files changed

+142
-1
lines changed

4 files changed

+142
-1
lines changed

src/cmd/link/elf_test.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ package main
5959
func main() {}
6060
`
6161

62+
var goSourceWithData = `
63+
package main
64+
var globalVar = 42
65+
func main() { println(&globalVar) }
66+
`
67+
6268
// The linker used to crash if an ELF input file had multiple text sections
6369
// with the same name.
6470
func TestSectionsWithSameName(t *testing.T) {
@@ -569,3 +575,106 @@ func TestFlagR(t *testing.T) {
569575
t.Errorf("executable failed to run: %v\n%s", err, out)
570576
}
571577
}
578+
579+
func TestFlagD(t *testing.T) {
580+
// Test that using the -D flag to specify data section address generates
581+
// a working binary with data at the specified address.
582+
t.Parallel()
583+
testFlagD(t, "0x10000000", "", 0x10000000)
584+
}
585+
586+
func TestFlagDUnaligned(t *testing.T) {
587+
// Test that using the -D flag with an unaligned address errors out
588+
t.Parallel()
589+
testFlagDError(t, "0x10000123", "", "invalid -D value 0x10000123")
590+
}
591+
592+
func TestFlagDWithR(t *testing.T) {
593+
// Test that using the -D flag with -R flag errors on unaligned address.
594+
t.Parallel()
595+
testFlagDError(t, "0x30001234", "8192", "invalid -D value 0x30001234")
596+
}
597+
598+
func testFlagD(t *testing.T, dataAddr string, roundQuantum string, expectedAddr uint64) {
599+
testenv.MustHaveGoBuild(t)
600+
tmpdir := t.TempDir()
601+
src := filepath.Join(tmpdir, "x.go")
602+
if err := os.WriteFile(src, []byte(goSourceWithData), 0444); err != nil {
603+
t.Fatal(err)
604+
}
605+
exe := filepath.Join(tmpdir, "x.exe")
606+
607+
// Build linker flags
608+
ldflags := "-D=" + dataAddr
609+
if roundQuantum != "" {
610+
ldflags += " -R=" + roundQuantum
611+
}
612+
613+
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags="+ldflags, "-o", exe, src)
614+
if out, err := cmd.CombinedOutput(); err != nil {
615+
t.Fatalf("build failed: %v, output:\n%s", err, out)
616+
}
617+
618+
cmd = testenv.Command(t, exe)
619+
if out, err := cmd.CombinedOutput(); err != nil {
620+
t.Errorf("executable failed to run: %v\n%s", err, out)
621+
}
622+
623+
ef, err := elf.Open(exe)
624+
if err != nil {
625+
t.Fatalf("open elf file failed: %v", err)
626+
}
627+
defer ef.Close()
628+
629+
// Find the first data-related section to verify segment placement
630+
var firstDataSectionAddr uint64
631+
var found bool = false
632+
for _, sec := range ef.Sections {
633+
if sec.Type == elf.SHT_PROGBITS || sec.Type == elf.SHT_NOBITS {
634+
// These sections are writable, allocated at runtime, but not executable
635+
isWrite := sec.Flags&elf.SHF_WRITE != 0
636+
isExec := sec.Flags&elf.SHF_EXECINSTR != 0
637+
isAlloc := sec.Flags&elf.SHF_ALLOC != 0
638+
639+
if isWrite && !isExec && isAlloc {
640+
addrLower := sec.Addr < firstDataSectionAddr
641+
if !found || addrLower {
642+
firstDataSectionAddr = sec.Addr
643+
found = true
644+
}
645+
}
646+
}
647+
}
648+
649+
if !found {
650+
t.Fatalf("can't find any writable data sections")
651+
}
652+
if firstDataSectionAddr != expectedAddr {
653+
t.Errorf("data section starts at 0x%x, expected 0x%x", firstDataSectionAddr, expectedAddr)
654+
}
655+
}
656+
657+
func testFlagDError(t *testing.T, dataAddr string, roundQuantum string, expectedError string) {
658+
testenv.MustHaveGoBuild(t)
659+
tmpdir := t.TempDir()
660+
src := filepath.Join(tmpdir, "x.go")
661+
if err := os.WriteFile(src, []byte(goSourceWithData), 0444); err != nil {
662+
t.Fatal(err)
663+
}
664+
exe := filepath.Join(tmpdir, "x.exe")
665+
666+
// Build linker flags
667+
ldflags := "-D=" + dataAddr
668+
if roundQuantum != "" {
669+
ldflags += " -R=" + roundQuantum
670+
}
671+
672+
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags="+ldflags, "-o", exe, src)
673+
out, err := cmd.CombinedOutput()
674+
if err == nil {
675+
t.Fatalf("expected build to fail with unaligned data address, but it succeeded")
676+
}
677+
if !strings.Contains(string(out), expectedError) {
678+
t.Errorf("expected error message to contain %q, got:\n%s", expectedError, out)
679+
}
680+
}

src/cmd/link/internal/ld/data.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2881,7 +2881,12 @@ func (ctxt *Link) address() []*sym.Segment {
28812881
}
28822882
order = append(order, &Segdata)
28832883
Segdata.Rwx = 06
2884-
Segdata.Vaddr = va
2884+
if *FlagDataAddr != -1 {
2885+
Segdata.Vaddr = uint64(*FlagDataAddr)
2886+
va = Segdata.Vaddr
2887+
} else {
2888+
Segdata.Vaddr = va
2889+
}
28852890
var data *sym.Section
28862891
var noptr *sym.Section
28872892
var bss *sym.Section

src/cmd/link/internal/ld/ld_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,3 +442,25 @@ func d()
442442
t.Errorf("Trampoline b-tramp0 exists unnecessarily")
443443
}
444444
}
445+
446+
func TestRounding(t *testing.T) {
447+
testCases := []struct {
448+
input int64
449+
quantum int64
450+
expected int64
451+
}{
452+
{0x30000000, 0x2000, 0x30000000}, // Already aligned
453+
{0x30002000, 0x2000, 0x30002000}, // Exactly on boundary
454+
{0x30001234, 0x2000, 0x30002000},
455+
{0x30001000, 0x2000, 0x30002000},
456+
{0x30001fff, 0x2000, 0x30002000},
457+
}
458+
459+
for _, tc := range testCases {
460+
result := Rnd(tc.input, tc.quantum)
461+
if result != tc.expected {
462+
t.Errorf("Rnd(0x%x, 0x%x) = 0x%x, expected 0x%x",
463+
tc.input, tc.quantum, result, tc.expected)
464+
}
465+
}
466+
}

src/cmd/link/internal/ld/main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ var (
105105
FlagStrictDups = flag.Int("strictdups", 0, "sanity check duplicate symbol contents during object file reading (1=warn 2=err).")
106106
FlagRound = flag.Int64("R", -1, "set address rounding `quantum`")
107107
FlagTextAddr = flag.Int64("T", -1, "set the start address of text symbols")
108+
FlagDataAddr = flag.Int64("D", -1, "set the start address of data symbols")
108109
FlagFuncAlign = flag.Int("funcalign", 0, "set function align to `N` bytes")
109110
flagEntrySymbol = flag.String("E", "", "set `entry` symbol name")
110111
flagPruneWeakMap = flag.Bool("pruneweakmap", true, "prune weak mapinit refs")
@@ -317,6 +318,10 @@ func Main(arch *sys.Arch, theArch Arch) {
317318
bench.Start("Archinit")
318319
thearch.Archinit(ctxt)
319320

321+
if *FlagDataAddr != -1 && *FlagDataAddr%*FlagRound != 0 {
322+
Exitf("invalid -D value 0x%x: not aligned to rounding quantum 0x%x", *FlagDataAddr, *FlagRound)
323+
}
324+
320325
if ctxt.linkShared && !ctxt.IsELF {
321326
Exitf("-linkshared can only be used on elf systems")
322327
}

0 commit comments

Comments
 (0)