Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions src/cmd/link/elf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
}
}
7 changes: 6 additions & 1 deletion src/cmd/link/internal/ld/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions src/cmd/link/internal/ld/ld_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
1 change: 1 addition & 0 deletions src/cmd/link/internal/ld/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down