mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
Fixes #76656 Change-Id: If2e823ba1577700af00f5883e4ea5c139e4749c1 Reviewed-on: https://go-review.googlesource.com/c/go/+/726100 Reviewed-by: Keith Randall <khr@golang.org> Reviewed-by: Keith Randall <khr@google.com> Reviewed-by: Michael Stapelberg <stapelberg@google.com> Auto-Submit: Ian Lance Taylor <iant@golang.org> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
805 lines
20 KiB
Go
805 lines
20 KiB
Go
// Copyright 2019 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
//go:build dragonfly || freebsd || linux || netbsd || openbsd
|
|
|
|
package main
|
|
|
|
import (
|
|
"cmd/internal/buildid"
|
|
"cmd/internal/hash"
|
|
"cmd/link/internal/ld"
|
|
"debug/elf"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"internal/platform"
|
|
"internal/testenv"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"text/template"
|
|
"unsafe"
|
|
)
|
|
|
|
func getCCAndCCFLAGS(t *testing.T, env []string) (string, []string) {
|
|
goTool := testenv.GoToolPath(t)
|
|
cmd := testenv.Command(t, goTool, "env", "CC")
|
|
cmd.Env = env
|
|
ccb, err := cmd.Output()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cc := strings.TrimSpace(string(ccb))
|
|
|
|
cmd = testenv.Command(t, goTool, "env", "GOGCCFLAGS")
|
|
cmd.Env = env
|
|
cflagsb, err := cmd.Output()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cflags := strings.Fields(string(cflagsb))
|
|
|
|
return cc, cflags
|
|
}
|
|
|
|
var asmSource = `
|
|
.section .text1,"ax"
|
|
s1:
|
|
.byte 0
|
|
.section .text2,"ax"
|
|
s2:
|
|
.byte 0
|
|
`
|
|
|
|
var goSource = `
|
|
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) {
|
|
testenv.MustHaveGoBuild(t)
|
|
testenv.MustHaveCGO(t)
|
|
t.Parallel()
|
|
|
|
objcopy, err := exec.LookPath("objcopy")
|
|
if err != nil {
|
|
t.Skipf("can't find objcopy: %v", err)
|
|
}
|
|
|
|
dir := t.TempDir()
|
|
|
|
gopath := filepath.Join(dir, "GOPATH")
|
|
gopathEnv := "GOPATH=" + gopath
|
|
env := append(os.Environ(), gopathEnv)
|
|
|
|
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module elf_test\n"), 0666); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
asmFile := filepath.Join(dir, "x.s")
|
|
if err := os.WriteFile(asmFile, []byte(asmSource), 0444); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cc, cflags := getCCAndCCFLAGS(t, env)
|
|
|
|
asmObj := filepath.Join(dir, "x.o")
|
|
t.Logf("%s %v -c -o %s %s", cc, cflags, asmObj, asmFile)
|
|
if out, err := testenv.Command(t, cc, append(cflags, "-c", "-o", asmObj, asmFile)...).CombinedOutput(); err != nil {
|
|
t.Logf("%s", out)
|
|
t.Fatal(err)
|
|
}
|
|
|
|
asm2Obj := filepath.Join(dir, "x2.syso")
|
|
t.Logf("%s --rename-section .text2=.text1 %s %s", objcopy, asmObj, asm2Obj)
|
|
if out, err := testenv.Command(t, objcopy, "--rename-section", ".text2=.text1", asmObj, asm2Obj).CombinedOutput(); err != nil {
|
|
t.Logf("%s", out)
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for _, s := range []string{asmFile, asmObj} {
|
|
if err := os.Remove(s); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
goFile := filepath.Join(dir, "main.go")
|
|
if err := os.WriteFile(goFile, []byte(goSource), 0444); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cmd := goCmd(t, "build")
|
|
cmd.Dir = dir
|
|
cmd.Env = append(cmd.Env, gopathEnv)
|
|
t.Logf("%s build", testenv.GoToolPath(t))
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
t.Logf("%s", out)
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
var cSources35779 = []string{`
|
|
static int blah() { return 42; }
|
|
int Cfunc1() { return blah(); }
|
|
`, `
|
|
static int blah() { return 42; }
|
|
int Cfunc2() { return blah(); }
|
|
`,
|
|
}
|
|
|
|
// TestMinusRSymsWithSameName tests a corner case in the new
|
|
// loader. Prior to the fix this failed with the error 'loadelf:
|
|
// $WORK/b001/_pkg_.a(ldr.syso): duplicate symbol reference: blah in
|
|
// both main(.text) and main(.text)'. See issue #35779.
|
|
func TestMinusRSymsWithSameName(t *testing.T) {
|
|
testenv.MustHaveGoBuild(t)
|
|
testenv.MustHaveCGO(t)
|
|
t.Parallel()
|
|
|
|
dir := t.TempDir()
|
|
|
|
gopath := filepath.Join(dir, "GOPATH")
|
|
gopathEnv := "GOPATH=" + gopath
|
|
env := append(os.Environ(), gopathEnv)
|
|
|
|
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module elf_test\n"), 0666); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cc, cflags := getCCAndCCFLAGS(t, env)
|
|
|
|
objs := []string{}
|
|
csrcs := []string{}
|
|
for i, content := range cSources35779 {
|
|
csrcFile := filepath.Join(dir, fmt.Sprintf("x%d.c", i))
|
|
csrcs = append(csrcs, csrcFile)
|
|
if err := os.WriteFile(csrcFile, []byte(content), 0444); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
obj := filepath.Join(dir, fmt.Sprintf("x%d.o", i))
|
|
objs = append(objs, obj)
|
|
t.Logf("%s %v -c -o %s %s", cc, cflags, obj, csrcFile)
|
|
if out, err := testenv.Command(t, cc, append(cflags, "-c", "-o", obj, csrcFile)...).CombinedOutput(); err != nil {
|
|
t.Logf("%s", out)
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
sysoObj := filepath.Join(dir, "ldr.syso")
|
|
t.Logf("%s %v -nostdlib -r -o %s %v", cc, cflags, sysoObj, objs)
|
|
if out, err := testenv.Command(t, cc, append(cflags, "-nostdlib", "-r", "-o", sysoObj, objs[0], objs[1])...).CombinedOutput(); err != nil {
|
|
t.Logf("%s", out)
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cruft := [][]string{objs, csrcs}
|
|
for _, sl := range cruft {
|
|
for _, s := range sl {
|
|
if err := os.Remove(s); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
goFile := filepath.Join(dir, "main.go")
|
|
if err := os.WriteFile(goFile, []byte(goSource), 0444); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Logf("%s build", testenv.GoToolPath(t))
|
|
cmd := goCmd(t, "build")
|
|
cmd.Dir = dir
|
|
cmd.Env = append(cmd.Env, gopathEnv)
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
t.Logf("%s", out)
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestGNUBuildID(t *testing.T) {
|
|
testenv.MustHaveGoBuild(t)
|
|
|
|
t.Parallel()
|
|
|
|
tmpdir := t.TempDir()
|
|
goFile := filepath.Join(tmpdir, "notes.go")
|
|
if err := os.WriteFile(goFile, []byte(goSource), 0444); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Use a specific Go buildid for testing.
|
|
const gobuildid = "testbuildid"
|
|
h := hash.Sum32([]byte(gobuildid))
|
|
gobuildidHash := string(h[:20])
|
|
|
|
tests := []struct{ name, ldflags, expect string }{
|
|
{"default", "", gobuildidHash},
|
|
{"gobuildid", "-B=gobuildid", gobuildidHash},
|
|
{"specific", "-B=0x0123456789abcdef", "\x01\x23\x45\x67\x89\xab\xcd\xef"},
|
|
{"none", "-B=none", ""},
|
|
}
|
|
if testenv.HasCGO() && runtime.GOOS != "solaris" && runtime.GOOS != "illumos" {
|
|
// Solaris ld doesn't support --build-id. So we don't
|
|
// add it in external linking mode.
|
|
for _, test := range tests {
|
|
t1 := test
|
|
t1.name += "_external"
|
|
t1.ldflags += " -linkmode=external"
|
|
tests = append(tests, t1)
|
|
}
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
exe := filepath.Join(tmpdir, test.name)
|
|
cmd := goCmd(t, "build", "-ldflags=-buildid="+gobuildid+" "+test.ldflags, "-o", exe, goFile)
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
t.Fatalf("%v: %v:\n%s", cmd.Args, err, out)
|
|
}
|
|
gnuBuildID, err := buildid.ReadELFNote(exe, string(ld.ELF_NOTE_BUILDINFO_NAME), ld.ELF_NOTE_BUILDINFO_TAG)
|
|
if err != nil {
|
|
t.Fatalf("can't read GNU build ID")
|
|
}
|
|
if string(gnuBuildID) != test.expect {
|
|
t.Errorf("build id mismatch: got %x, want %x", gnuBuildID, test.expect)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMergeNoteSections(t *testing.T) {
|
|
testenv.MustHaveGoBuild(t)
|
|
expected := 1
|
|
|
|
switch runtime.GOOS {
|
|
case "linux", "dragonfly":
|
|
case "openbsd", "netbsd", "freebsd":
|
|
// These OSes require independent segment
|
|
expected = 2
|
|
default:
|
|
t.Skip("We should only test on elf output.")
|
|
}
|
|
t.Parallel()
|
|
|
|
goFile := filepath.Join(t.TempDir(), "notes.go")
|
|
if err := os.WriteFile(goFile, []byte(goSource), 0444); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
outFile := filepath.Join(t.TempDir(), "notes.exe")
|
|
// sha1sum of "gopher"
|
|
id := "0xf4e8cd51ce8bae2996dc3b74639cdeaa1f7fee5f"
|
|
cmd := goCmd(t, "build", "-o", outFile, "-ldflags", "-B "+id, goFile)
|
|
cmd.Dir = t.TempDir()
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
t.Logf("%s", out)
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ef, err := elf.Open(outFile)
|
|
if err != nil {
|
|
t.Fatalf("open elf file failed:%v", err)
|
|
}
|
|
defer ef.Close()
|
|
sec := ef.Section(".note.gnu.build-id")
|
|
if sec == nil {
|
|
t.Fatalf("can't find gnu build id")
|
|
}
|
|
|
|
sec = ef.Section(".note.go.buildid")
|
|
if sec == nil {
|
|
t.Fatalf("can't find go build id")
|
|
}
|
|
cnt := 0
|
|
for _, ph := range ef.Progs {
|
|
if ph.Type == elf.PT_NOTE {
|
|
cnt += 1
|
|
}
|
|
}
|
|
if cnt != expected {
|
|
t.Fatalf("want %d PT_NOTE segment, got %d", expected, cnt)
|
|
}
|
|
}
|
|
|
|
const pieSourceTemplate = `
|
|
package main
|
|
|
|
import "fmt"
|
|
|
|
// Force the creation of a lot of type descriptors that will go into
|
|
// the .data.rel.ro section.
|
|
{{range $index, $element := .}}var V{{$index}} interface{} = [{{$index}}]int{}
|
|
{{end}}
|
|
|
|
func main() {
|
|
{{range $index, $element := .}} fmt.Println(V{{$index}})
|
|
{{end}}
|
|
}
|
|
`
|
|
|
|
func TestPIESize(t *testing.T) {
|
|
testenv.MustHaveGoBuild(t)
|
|
|
|
// We don't want to test -linkmode=external if cgo is not supported.
|
|
// On some systems -buildmode=pie implies -linkmode=external, so just
|
|
// always skip the test if cgo is not supported.
|
|
testenv.MustHaveCGO(t)
|
|
|
|
if !platform.BuildModeSupported(runtime.Compiler, "pie", runtime.GOOS, runtime.GOARCH) {
|
|
t.Skip("-buildmode=pie not supported")
|
|
}
|
|
|
|
t.Parallel()
|
|
|
|
tmpl := template.Must(template.New("pie").Parse(pieSourceTemplate))
|
|
|
|
writeGo := func(t *testing.T, dir string) {
|
|
f, err := os.Create(filepath.Join(dir, "pie.go"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Passing a 100-element slice here will cause
|
|
// pieSourceTemplate to create 100 variables with
|
|
// different types.
|
|
if err := tmpl.Execute(f, make([]byte, 100)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := f.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
var linkmodes []string
|
|
if platform.InternalLinkPIESupported(runtime.GOOS, runtime.GOARCH) {
|
|
linkmodes = append(linkmodes, "internal")
|
|
}
|
|
linkmodes = append(linkmodes, "external")
|
|
|
|
for _, linkmode := range linkmodes {
|
|
t.Run(fmt.Sprintf("TestPieSize-%v", linkmode), func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dir := t.TempDir()
|
|
|
|
writeGo(t, dir)
|
|
|
|
binexe := filepath.Join(dir, "exe")
|
|
binpie := filepath.Join(dir, "pie")
|
|
binexe += linkmode
|
|
binpie += linkmode
|
|
|
|
build := func(bin, mode string) error {
|
|
cmd := goCmd(t, "build", "-o", bin, "-buildmode="+mode, "-ldflags=-linkmode="+linkmode)
|
|
cmd.Args = append(cmd.Args, "pie.go")
|
|
cmd.Dir = dir
|
|
t.Logf("%v", cmd.Args)
|
|
out, err := cmd.CombinedOutput()
|
|
if len(out) > 0 {
|
|
t.Logf("%s", out)
|
|
}
|
|
if err != nil {
|
|
t.Log(err)
|
|
}
|
|
return err
|
|
}
|
|
|
|
var errexe, errpie error
|
|
var wg sync.WaitGroup
|
|
wg.Add(2)
|
|
go func() {
|
|
defer wg.Done()
|
|
errexe = build(binexe, "exe")
|
|
}()
|
|
go func() {
|
|
defer wg.Done()
|
|
errpie = build(binpie, "pie")
|
|
}()
|
|
wg.Wait()
|
|
if errexe != nil || errpie != nil {
|
|
if runtime.GOOS == "android" && runtime.GOARCH == "arm64" {
|
|
testenv.SkipFlaky(t, 58806)
|
|
}
|
|
t.Fatal("link failed")
|
|
}
|
|
|
|
var sizeexe, sizepie uint64
|
|
if fi, err := os.Stat(binexe); err != nil {
|
|
t.Fatal(err)
|
|
} else {
|
|
sizeexe = uint64(fi.Size())
|
|
}
|
|
if fi, err := os.Stat(binpie); err != nil {
|
|
t.Fatal(err)
|
|
} else {
|
|
sizepie = uint64(fi.Size())
|
|
}
|
|
|
|
elfexe, err := elf.Open(binexe)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer elfexe.Close()
|
|
|
|
elfpie, err := elf.Open(binpie)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer elfpie.Close()
|
|
|
|
// The difference in size between exe and PIE
|
|
// should be approximately the difference in
|
|
// size of the .text section plus the size of
|
|
// the PIE dynamic data sections plus the
|
|
// difference in size of the .got and .plt
|
|
// sections if they exist.
|
|
// We ignore unallocated sections.
|
|
// There may be gaps between non-writeable and
|
|
// writable PT_LOAD segments. We also skip those
|
|
// gaps (see issue #36023).
|
|
|
|
textsize := func(ef *elf.File, name string) uint64 {
|
|
for _, s := range ef.Sections {
|
|
if s.Name == ".text" {
|
|
return s.Size
|
|
}
|
|
}
|
|
t.Fatalf("%s: no .text section", name)
|
|
return 0
|
|
}
|
|
textexe := textsize(elfexe, binexe)
|
|
textpie := textsize(elfpie, binpie)
|
|
|
|
dynsize := func(ef *elf.File) uint64 {
|
|
var ret uint64
|
|
for _, s := range ef.Sections {
|
|
if s.Flags&elf.SHF_ALLOC == 0 {
|
|
continue
|
|
}
|
|
switch s.Type {
|
|
case elf.SHT_DYNSYM, elf.SHT_STRTAB, elf.SHT_REL, elf.SHT_RELA, elf.SHT_HASH, elf.SHT_GNU_HASH, elf.SHT_GNU_VERDEF, elf.SHT_GNU_VERNEED, elf.SHT_GNU_VERSYM:
|
|
ret += s.Size
|
|
}
|
|
if s.Flags&elf.SHF_WRITE != 0 && (strings.Contains(s.Name, ".got") || strings.Contains(s.Name, ".plt")) {
|
|
ret += s.Size
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
dynexe := dynsize(elfexe)
|
|
dynpie := dynsize(elfpie)
|
|
|
|
extrasize := func(ef *elf.File) uint64 {
|
|
var ret uint64
|
|
// skip unallocated sections
|
|
for _, s := range ef.Sections {
|
|
if s.Flags&elf.SHF_ALLOC == 0 {
|
|
ret += s.Size
|
|
}
|
|
}
|
|
// also skip gaps between PT_LOAD segments
|
|
var prev *elf.Prog
|
|
for _, seg := range ef.Progs {
|
|
if seg.Type != elf.PT_LOAD {
|
|
continue
|
|
}
|
|
if prev != nil {
|
|
ret += seg.Off - prev.Off - prev.Filesz
|
|
}
|
|
prev = seg
|
|
}
|
|
return ret
|
|
}
|
|
|
|
extraexe := extrasize(elfexe)
|
|
extrapie := extrasize(elfpie)
|
|
|
|
if sizepie < sizeexe || sizepie-extrapie < sizeexe-extraexe {
|
|
return
|
|
}
|
|
diffReal := (sizepie - extrapie) - (sizeexe - extraexe)
|
|
diffExpected := (textpie + dynpie) - (textexe + dynexe)
|
|
|
|
t.Logf("real size difference %#x, expected %#x", diffReal, diffExpected)
|
|
|
|
if diffReal > (diffExpected + diffExpected/10) {
|
|
t.Errorf("PIE unexpectedly large: got difference of %d (%d - %d), expected difference %d", diffReal, sizepie, sizeexe, diffExpected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIssue51939(t *testing.T) {
|
|
testenv.MustHaveGoBuild(t)
|
|
t.Parallel()
|
|
td := t.TempDir()
|
|
goFile := filepath.Join(td, "issue51939.go")
|
|
if err := os.WriteFile(goFile, []byte(goSource), 0444); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
outFile := filepath.Join(td, "issue51939.exe")
|
|
cmd := goCmd(t, "build", "-o", outFile, goFile)
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
t.Logf("%s", out)
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ef, err := elf.Open(outFile)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for _, s := range ef.Sections {
|
|
if s.Flags&elf.SHF_ALLOC == 0 && s.Addr != 0 {
|
|
t.Errorf("section %s should not allocated with addr %x", s.Name, s.Addr)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFlagR(t *testing.T) {
|
|
// Test that using the -R flag to specify a (large) alignment generates
|
|
// a working binary.
|
|
// (Test only on ELF for now. The alignment allowed differs from platform
|
|
// to platform.)
|
|
testenv.MustHaveGoBuild(t)
|
|
t.Parallel()
|
|
tmpdir := t.TempDir()
|
|
src := filepath.Join(tmpdir, "x.go")
|
|
if err := os.WriteFile(src, []byte(goSource), 0444); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
exe := filepath.Join(tmpdir, "x.exe")
|
|
|
|
cmd := goCmd(t, "build", "-ldflags=-R=0x100000", "-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)
|
|
}
|
|
}
|
|
|
|
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 := goCmd(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 firstDataSection *elf.Section
|
|
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
|
|
// nor TLS.
|
|
isWrite := sec.Flags&elf.SHF_WRITE != 0
|
|
isExec := sec.Flags&elf.SHF_EXECINSTR != 0
|
|
isAlloc := sec.Flags&elf.SHF_ALLOC != 0
|
|
isTLS := sec.Flags&elf.SHF_TLS != 0
|
|
|
|
if isWrite && !isExec && isAlloc && !isTLS {
|
|
if firstDataSection == nil || sec.Addr < firstDataSection.Addr {
|
|
firstDataSection = sec
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if firstDataSection == nil {
|
|
t.Fatalf("can't find any writable data sections")
|
|
}
|
|
if firstDataSection.Addr != expectedAddr {
|
|
t.Errorf("data section starts at 0x%x for section %s, expected 0x%x",
|
|
firstDataSection.Addr, firstDataSection.Name, 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 := goCmd(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)
|
|
}
|
|
}
|
|
|
|
func TestELFHeadersSorted(t *testing.T) {
|
|
for _, buildmode := range []string{"exe", "pie"} {
|
|
t.Run(buildmode, func(t *testing.T) {
|
|
testELFHeadersSorted(t, buildmode)
|
|
})
|
|
}
|
|
}
|
|
|
|
func testELFHeadersSorted(t *testing.T, buildmode string) {
|
|
testenv.MustHaveGoBuild(t)
|
|
|
|
// We can only test this for internal linking mode.
|
|
// For external linking the external linker will
|
|
// decide how to sort the sections.
|
|
testenv.MustInternalLink(t, testenv.NoSpecialBuildTypes)
|
|
if buildmode == "pie" {
|
|
testenv.MustInternalLinkPIE(t)
|
|
}
|
|
|
|
t.Parallel()
|
|
|
|
tmpdir := t.TempDir()
|
|
src := filepath.Join(tmpdir, "x.go")
|
|
if err := os.WriteFile(src, []byte(goSourceWithData), 0o444); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
exe := filepath.Join(tmpdir, "x.exe")
|
|
cmd := goCmd(t, "build", "-buildmode="+buildmode, "-ldflags=-linkmode=internal", "-o", exe, src)
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
t.Fatalf("build failed: %v, output:\n%s", err, out)
|
|
}
|
|
|
|
// Check that the first section header is all zeroes.
|
|
f, err := os.Open(exe)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer f.Close()
|
|
|
|
var ident [elf.EI_NIDENT]byte
|
|
if _, err := f.Read(ident[:]); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var bo binary.ByteOrder
|
|
switch elf.Data(ident[elf.EI_DATA]) {
|
|
case elf.ELFDATA2LSB:
|
|
bo = binary.LittleEndian
|
|
case elf.ELFDATA2MSB:
|
|
bo = binary.BigEndian
|
|
default:
|
|
t.Fatalf("unrecognized data encoding %d", ident[elf.EI_DATA])
|
|
}
|
|
|
|
var shoff int64
|
|
var shsize int
|
|
switch elf.Class(ident[elf.EI_CLASS]) {
|
|
case elf.ELFCLASS32:
|
|
var hdr elf.Header32
|
|
data := make([]byte, unsafe.Sizeof(hdr))
|
|
if _, err := f.ReadAt(data, 0); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
shoff = int64(bo.Uint32(data[unsafe.Offsetof(hdr.Shoff):]))
|
|
shsize = int(unsafe.Sizeof(elf.Section32{}))
|
|
|
|
case elf.ELFCLASS64:
|
|
var hdr elf.Header64
|
|
data := make([]byte, unsafe.Sizeof(hdr))
|
|
if _, err := f.ReadAt(data, 0); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
shoff = int64(bo.Uint64(data[unsafe.Offsetof(hdr.Shoff):]))
|
|
shsize = int(unsafe.Sizeof(elf.Section64{}))
|
|
|
|
default:
|
|
t.Fatalf("unrecognized class %d", ident[elf.EI_CLASS])
|
|
}
|
|
|
|
if shoff > 0 {
|
|
data := make([]byte, shsize)
|
|
if _, err := f.ReadAt(data, shoff); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for i, c := range data {
|
|
if c != 0 {
|
|
t.Errorf("section header 0 byte %d is %d, should be zero", i, c)
|
|
}
|
|
}
|
|
}
|
|
|
|
ef, err := elf.NewFile(f)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer ef.Close()
|
|
|
|
// After the first zero section header,
|
|
// we should see allocated sections,
|
|
// then unallocated sections.
|
|
// The allocated sections should be sorted by address.
|
|
i := 1
|
|
lastAddr := uint64(0)
|
|
for i < len(ef.Sections) {
|
|
sec := ef.Sections[i]
|
|
if sec.Flags&elf.SHF_ALLOC == 0 {
|
|
break
|
|
}
|
|
if sec.Addr < lastAddr {
|
|
t.Errorf("section %d %q address %#x less than previous address %#x", i, sec.Name, sec.Addr, lastAddr)
|
|
}
|
|
lastAddr = sec.Addr
|
|
i++
|
|
}
|
|
|
|
firstUnalc := i
|
|
for i < len(ef.Sections) {
|
|
sec := ef.Sections[i]
|
|
if sec.Flags&elf.SHF_ALLOC != 0 {
|
|
t.Errorf("allocated section %d %q follows first unallocated section %d %q", i, sec.Name, firstUnalc, ef.Sections[firstUnalc].Name)
|
|
}
|
|
i++
|
|
}
|
|
}
|