aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/link/internal/ld/elf_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/link/internal/ld/elf_test.go')
-rw-r--r--src/cmd/link/internal/ld/elf_test.go234
1 files changed, 224 insertions, 10 deletions
diff --git a/src/cmd/link/internal/ld/elf_test.go b/src/cmd/link/internal/ld/elf_test.go
index ad4149d55e..acccdee906 100644
--- a/src/cmd/link/internal/ld/elf_test.go
+++ b/src/cmd/link/internal/ld/elf_test.go
@@ -11,8 +11,10 @@ import (
"fmt"
"internal/testenv"
"os"
+ "os/exec"
"path/filepath"
"runtime"
+ "sort"
"strings"
"testing"
)
@@ -194,11 +196,25 @@ func TestElfBindNow(t *testing.T) {
progC = `package main; import "C"; func main() {}`
)
+ // Notes:
+ // - for linux/amd64 and linux/arm64, for relro we'll always see a
+ // .got section when building with -buildmode=pie (in addition
+ // to .dynamic); for some other less mainstream archs (ppc64le,
+ // s390) this is not the case (on ppc64le for example we only
+ // see got refs from C objects). Hence we put ".dynamic" in the
+ // 'want RO' list below and ".got" in the 'want RO if present".
+ // - when using the external linker, checking for read-only ".got"
+ // is problematic since some linkers will only make the .got
+ // read-only if its size is above a specific threshold, e.g.
+ // https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=ld/scripttempl/elf.sc;h=d5022fa502f24db23f396f337a6c8978fbc8415b;hb=6fde04116b4b835fa9ec3b3497fcac4e4a0637e2#l74 . For this reason, don't try to verify read-only .got
+ // in the external linking case.
+
tests := []struct {
name string
args []string
prog string
wantSecsRO []string
+ wantSecsROIfPresent []string
mustHaveBuildModePIE bool
mustHaveCGO bool
mustInternalLink bool
@@ -214,7 +230,8 @@ func TestElfBindNow(t *testing.T) {
mustHaveBuildModePIE: true,
mustInternalLink: true,
wantDf1Pie: true,
- wantSecsRO: []string{".dynamic", ".got"},
+ wantSecsRO: []string{".dynamic"},
+ wantSecsROIfPresent: []string{".got"},
},
{
name: "bindnow-linkmode-internal",
@@ -234,7 +251,8 @@ func TestElfBindNow(t *testing.T) {
wantDfBindNow: true,
wantDf1Now: true,
wantDf1Pie: true,
- wantSecsRO: []string{".dynamic", ".got", ".got.plt"},
+ wantSecsRO: []string{".dynamic"},
+ wantSecsROIfPresent: []string{".got", ".got.plt"},
},
{
name: "bindnow-pie-linkmode-external",
@@ -245,8 +263,7 @@ func TestElfBindNow(t *testing.T) {
wantDfBindNow: true,
wantDf1Now: true,
wantDf1Pie: true,
- // NB: external linker produces .plt.got, not .got.plt
- wantSecsRO: []string{".dynamic", ".got"},
+ wantSecsRO: []string{".dynamic"},
},
}
@@ -339,10 +356,9 @@ func TestElfBindNow(t *testing.T) {
t.Fatalf("DT_FLAGS_1 DF_1_PIE got: %v, want: %v", gotDf1Pie, test.wantDf1Pie)
}
- // Skipping this newer portion of the test temporarily pending resolution of problems on ppc64le, loonpg64, possibly others.
- if false {
-
- for _, wsroname := range test.wantSecsRO {
+ wsrolists := [][]string{test.wantSecsRO, test.wantSecsROIfPresent}
+ for k, wsrolist := range wsrolists {
+ for _, wsroname := range wsrolist {
// Locate section of interest.
var wsro *elf.Section
for _, s := range elfFile.Sections {
@@ -352,8 +368,11 @@ func TestElfBindNow(t *testing.T) {
}
}
if wsro == nil {
- t.Fatalf("test %s: can't locate %q section",
- test.name, wsroname)
+ if k == 0 {
+ t.Fatalf("test %s: can't locate %q section",
+ test.name, wsroname)
+ }
+ continue
}
// Now walk the program headers. Section should be part of
@@ -392,3 +411,198 @@ func TestElfBindNow(t *testing.T) {
})
}
}
+
+// This program is intended to be just big/complicated enough that
+// we wind up with decent-sized .data.rel.ro.{typelink,itablink,gopclntab}
+// sections.
+const ifacecallsProg = `
+package main
+
+import "reflect"
+
+type A string
+type B int
+type C float64
+
+type describer interface{ What() string }
+type timer interface{ When() int }
+type rationale interface{ Why() error }
+
+func (a *A) What() string { return "string" }
+func (b *B) What() string { return "int" }
+func (b *B) When() int { return int(*b) }
+func (b *B) Why() error { return nil }
+func (c *C) What() string { return "float64" }
+
+func i_am_dead(c C) {
+ var d describer = &c
+ println(d.What())
+}
+
+func example(a A, b B) describer {
+ if b == 1 {
+ return &a
+ }
+ return &b
+}
+
+func ouch(a any, what string) string {
+ cv := reflect.ValueOf(a).MethodByName(what).Call(nil)
+ return cv[0].String()
+}
+
+func main() {
+ println(example("", 1).What())
+ println(ouch(example("", 1), "What"))
+}
+
+`
+
+func TestRelroSectionOverlapIssue67261(t *testing.T) {
+ t.Parallel()
+ testenv.MustHaveGoBuild(t)
+ testenv.MustHaveBuildMode(t, "pie")
+ testenv.MustInternalLinkPIE(t)
+
+ // This test case inspired by issue 67261, in which the linker
+ // produces a set of sections for -buildmode=pie that confuse the
+ // "strip" command, due to overlapping extents. The test first
+ // verifies that we don't have any overlapping PROGBITS/DYNAMIC
+ // sections, then runs "strip" on the resulting binary.
+
+ dir := t.TempDir()
+ src := filepath.Join(dir, "e.go")
+ binFile := filepath.Join(dir, "e.exe")
+
+ if err := os.WriteFile(src, []byte(ifacecallsProg), 0666); err != nil {
+ t.Fatal(err)
+ }
+
+ cmdArgs := []string{"build", "-o", binFile, "-buildmode=pie", "-ldflags=linkmode=internal", src}
+ cmd := testenv.Command(t, testenv.GoToolPath(t), cmdArgs...)
+
+ if out, err := cmd.CombinedOutput(); err != nil {
+ t.Fatalf("failed to build %v: %v:\n%s", cmd.Args, err, out)
+ }
+
+ fi, err := os.Open(binFile)
+ if err != nil {
+ t.Fatalf("failed to open built file: %v", err)
+ }
+ defer fi.Close()
+
+ elfFile, err := elf.NewFile(fi)
+ if err != nil {
+ t.Skip("The system may not support ELF, skipped.")
+ }
+ defer elfFile.Close()
+
+ // List of interesting sections. Here "interesting" means progbits/dynamic
+ // and loadable (has an address), nonzero size.
+ secs := []*elf.Section{}
+ for _, s := range elfFile.Sections {
+ if s.Type != elf.SHT_PROGBITS && s.Type != elf.SHT_DYNAMIC {
+ continue
+ }
+ if s.Addr == 0 || s.Size == 0 {
+ continue
+ }
+ secs = append(secs, s)
+ }
+
+ secOverlaps := func(s1, s2 *elf.Section) bool {
+ st1 := s1.Addr
+ st2 := s2.Addr
+ en1 := s1.Addr + s1.Size
+ en2 := s2.Addr + s2.Size
+ return max(st1, st2) < min(en1, en2)
+ }
+
+ // Sort by address
+ sort.SliceStable(secs, func(i, j int) bool {
+ return secs[i].Addr < secs[j].Addr
+ })
+
+ // Check to make sure we don't have any overlaps.
+ foundOverlap := false
+ for i := 0; i < len(secs)-1; i++ {
+ for j := i + 1; j < len(secs); j++ {
+ s := secs[i]
+ sn := secs[j]
+ if secOverlaps(s, sn) {
+ t.Errorf("unexpected: section %d:%q (addr=%x size=%x) overlaps section %d:%q (addr=%x size=%x)", i, s.Name, s.Addr, s.Size, i+1, sn.Name, sn.Addr, sn.Size)
+ foundOverlap = true
+ }
+ }
+ }
+ if foundOverlap {
+ // Print some additional info for human inspection.
+ t.Logf("** section list follows\n")
+ for i := range secs {
+ s := secs[i]
+ fmt.Printf(" | %2d: ad=0x%08x en=0x%08x sz=0x%08x t=%s %q\n",
+ i, s.Addr, s.Addr+s.Size, s.Size, s.Type, s.Name)
+ }
+ }
+
+ // We need CGO / c-compiler for the next bit.
+ testenv.MustHaveCGO(t)
+
+ // Make sure that the resulting binary can be put through strip.
+ // Try both "strip" and "llvm-strip"; in each case ask out CC
+ // command where to find the tool with "-print-prog-name" (meaning
+ // that if CC is gcc, we typically won't be able to find llvm-strip).
+ //
+ // Interestingly, binutils version of strip will (unfortunately)
+ // print error messages if there is a problem but will not return
+ // a non-zero exit status (?why?), so we consider any output a
+ // failure here.
+ stripExecs := []string{}
+ ecmd := testenv.Command(t, testenv.GoToolPath(t), "env", "CC")
+ if out, err := ecmd.CombinedOutput(); err != nil {
+ t.Fatalf("go env CC failed: %v:\n%s", err, out)
+ } else {
+ ccprog := strings.TrimSpace(string(out))
+ tries := []string{"strip", "llvm-strip"}
+ for _, try := range tries {
+ cmd := testenv.Command(t, ccprog, "-print-prog-name="+try)
+ if out, err := cmd.CombinedOutput(); err != nil {
+ t.Fatalf("print-prog-name failed: %+v %v:\n%s",
+ cmd.Args, err, out)
+ } else {
+ sprog := strings.TrimSpace(string(out))
+ stripExecs = append(stripExecs, sprog)
+ }
+ }
+ }
+
+ // Run strip on our Go PIE binary, making sure that the strip
+ // succeeds and we get no output from strip, then run the resulting
+ // stripped binary.
+ for k, sprog := range stripExecs {
+ if _, err := os.Stat(sprog); err != nil {
+ sp1, err := exec.LookPath(sprog)
+ if err != nil || sp1 == "" {
+ continue
+ }
+ sprog = sp1
+ }
+ targ := fmt.Sprintf("p%d.exe", k)
+ scmd := testenv.Command(t, sprog, "-o", targ, binFile)
+ scmd.Dir = dir
+ if sout, serr := scmd.CombinedOutput(); serr != nil {
+ t.Fatalf("failed to strip %v: %v:\n%s", scmd.Args, serr, sout)
+ } else {
+ // Non-empty output indicates failure, as mentioned above.
+ if len(string(sout)) != 0 {
+ t.Errorf("unexpected outut from %s:\n%s\n", sprog, string(sout))
+ }
+ }
+ rcmd := testenv.Command(t, filepath.Join(dir, targ))
+ if out, err := rcmd.CombinedOutput(); err != nil {
+ t.Errorf("binary stripped by %s failed: %v:\n%s",
+ scmd.Args, err, string(out))
+ }
+ }
+
+}