Skip to content

Commit 94074f2

Browse files
Kevaundraykevaundray
authored andcommitted
initial commit
1 parent ffc85ee commit 94074f2

File tree

3 files changed

+173
-1
lines changed

3 files changed

+173
-1
lines changed

src/cmd/link/elf_test.go

Lines changed: 153 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,150 @@ 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+
// (Test only on ELF for now.)
583+
testenv.MustHaveGoBuild(t)
584+
t.Parallel()
585+
tmpdir := t.TempDir()
586+
src := filepath.Join(tmpdir, "x.go")
587+
if err := os.WriteFile(src, []byte(goSourceWithData), 0444); err != nil {
588+
t.Fatal(err)
589+
}
590+
exe := filepath.Join(tmpdir, "x.exe")
591+
592+
dataAddr := "0x10000000"
593+
expectedAddr := uint64(0x10000000)
594+
595+
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-D="+dataAddr, "-o", exe, src)
596+
if out, err := cmd.CombinedOutput(); err != nil {
597+
t.Fatalf("build failed: %v, output:\n%s", err, out)
598+
}
599+
600+
cmd = testenv.Command(t, exe)
601+
if out, err := cmd.CombinedOutput(); err != nil {
602+
t.Errorf("executable failed to run: %v\n%s", err, out)
603+
}
604+
605+
ef, err := elf.Open(exe)
606+
if err != nil {
607+
t.Fatalf("open elf file failed: %v", err)
608+
}
609+
defer ef.Close()
610+
611+
// Find the first data-related section to verify segment placement
612+
var firstDataSectionAddr uint64
613+
var found bool = false
614+
for _, sec := range ef.Sections {
615+
if sec.Type == elf.SHT_PROGBITS || sec.Type == elf.SHT_NOBITS {
616+
617+
// These sections are writable, allocated at runtime, but not executable
618+
isWrite := sec.Flags&elf.SHF_WRITE != 0
619+
isExec := sec.Flags&elf.SHF_EXECINSTR != 0
620+
isAlloc := sec.Flags&elf.SHF_ALLOC != 0
621+
622+
if isWrite && !isExec && isAlloc {
623+
addrLower := sec.Addr < firstDataSectionAddr
624+
if !found || addrLower {
625+
firstDataSectionAddr = sec.Addr
626+
found = true
627+
}
628+
}
629+
}
630+
}
631+
632+
if !found {
633+
t.Fatalf("can't find any writable data sections")
634+
}
635+
if firstDataSectionAddr != expectedAddr {
636+
t.Errorf("data section starts at 0x%x, expected 0x%x", firstDataSectionAddr, expectedAddr)
637+
}
638+
}
639+
640+
func TestFlagDWithR(t *testing.T) {
641+
// Test that using the -D flag with -R flag works together.
642+
// Note: The data address gets rounded to the specified alignment quantum.
643+
testenv.MustHaveGoBuild(t)
644+
t.Parallel()
645+
tmpdir := t.TempDir()
646+
src := filepath.Join(tmpdir, "x.go")
647+
if err := os.WriteFile(src, []byte(goSourceWithData), 0444); err != nil {
648+
t.Fatal(err)
649+
}
650+
exe := filepath.Join(tmpdir, "x.exe")
651+
652+
// Use an unaligned address that tests -D and -R rounding
653+
dataAddr := "0x30001234"
654+
roundQuantum := "8192"
655+
expectedAddr := uint64(0x30002000) // Should be rounded up to next 8KB boundary
656+
657+
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-D="+dataAddr+" -R="+roundQuantum, "-o", exe, src)
658+
if out, err := cmd.CombinedOutput(); err != nil {
659+
t.Fatalf("build failed: %v, output:\n%s", err, out)
660+
}
661+
662+
cmd = testenv.Command(t, exe)
663+
if out, err := cmd.CombinedOutput(); err != nil {
664+
t.Errorf("executable failed to run: %v\n%s", err, out)
665+
}
666+
667+
ef, err := elf.Open(exe)
668+
if err != nil {
669+
t.Fatalf("open elf file failed: %v", err)
670+
}
671+
defer ef.Close()
672+
673+
// Find the first data-related section to verify segment placement
674+
var firstDataSectionAddr uint64
675+
var found bool = false
676+
for _, sec := range ef.Sections {
677+
if sec.Type == elf.SHT_PROGBITS || sec.Type == elf.SHT_NOBITS {
678+
679+
// These sections are writable, allocated at runtime, but not executable
680+
isWrite := sec.Flags&elf.SHF_WRITE != 0
681+
isExec := sec.Flags&elf.SHF_EXECINSTR != 0
682+
isAlloc := sec.Flags&elf.SHF_ALLOC != 0
683+
684+
if isWrite && !isExec && isAlloc {
685+
addrLower := sec.Addr < firstDataSectionAddr
686+
if !found || addrLower {
687+
firstDataSectionAddr = sec.Addr
688+
found = true
689+
}
690+
}
691+
}
692+
}
693+
694+
if !found {
695+
t.Fatalf("can't find any writable data sections")
696+
}
697+
if firstDataSectionAddr != expectedAddr {
698+
t.Errorf("data section starts at 0x%x, expected 0x%x",
699+
firstDataSectionAddr, expectedAddr)
700+
}
701+
}
702+
703+
// TODO: sanity check, delete this
704+
func TestRounding(t *testing.T) {
705+
testCases := []struct {
706+
input int64
707+
quantum int64
708+
expected int64
709+
}{
710+
{0x30000000, 0x2000, 0x30000000}, // Already aligned
711+
{0x30002000, 0x2000, 0x30002000}, // Exactly on boundary
712+
{0x30001234, 0x2000, 0x30002000},
713+
{0x30001000, 0x2000, 0x30002000},
714+
{0x30001fff, 0x2000, 0x30002000},
715+
}
716+
717+
for _, tc := range testCases {
718+
result := ld.Rnd(tc.input, tc.quantum)
719+
if result != tc.expected {
720+
t.Errorf("Rnd(0x%x, 0x%x) = 0x%x, expected 0x%x",
721+
tc.input, tc.quantum, result, tc.expected)
722+
}
723+
}
724+
}

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

Lines changed: 19 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(Rnd(*FlagDataAddr, *FlagRound))
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
@@ -3135,6 +3140,19 @@ func (ctxt *Link) layout(order []*sym.Segment) uint64 {
31353140
// should ensure that this segment's
31363141
// VA ≡ Fileoff mod FlagRound.
31373142
seg.Fileoff = uint64(Rnd(int64(prev.Fileoff+prev.Filelen), *FlagRound))
3143+
// If -D flag was used to place data segment at a specific address,
3144+
// we may have a non-contiguous layout. In this case, we need to
3145+
// adjust the file offset to maintain the congruence requirement.
3146+
if seg == &Segdata && *FlagDataAddr != -1 {
3147+
// Adjust file offset to maintain VA ≡ Fileoff mod FlagRound
3148+
adjustment := int64(seg.Vaddr%uint64(*FlagRound)) - int64(seg.Fileoff%uint64(*FlagRound))
3149+
if adjustment != 0 {
3150+
seg.Fileoff = uint64(int64(seg.Fileoff) + adjustment)
3151+
if adjustment < 0 {
3152+
seg.Fileoff += uint64(*FlagRound)
3153+
}
3154+
}
3155+
}
31383156
if seg.Vaddr%uint64(*FlagRound) != seg.Fileoff%uint64(*FlagRound) {
31393157
Exitf("bad segment rounding (Vaddr=%#x Fileoff=%#x FlagRound=%#x)", seg.Vaddr, seg.Fileoff, *FlagRound)
31403158
}

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

Lines changed: 1 addition & 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")

0 commit comments

Comments
 (0)