github.com/gagliardetto/golang-go@v0.0.0-20201020153340-53909ea70814/cmd/link/elf_test.go (about) 1 // Copyright 2019 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // +build dragonfly freebsd linux netbsd openbsd 6 7 package main 8 9 import ( 10 "github.com/gagliardetto/golang-go/cmd/internal/sys" 11 "debug/elf" 12 "fmt" 13 "github.com/gagliardetto/golang-go/not-internal/testenv" 14 "io/ioutil" 15 "os" 16 "os/exec" 17 "path/filepath" 18 "runtime" 19 "strings" 20 "sync" 21 "testing" 22 "text/template" 23 ) 24 25 func getCCAndCCFLAGS(t *testing.T, env []string) (string, []string) { 26 goTool := testenv.GoToolPath(t) 27 cmd := exec.Command(goTool, "env", "CC") 28 cmd.Env = env 29 ccb, err := cmd.Output() 30 if err != nil { 31 t.Fatal(err) 32 } 33 cc := strings.TrimSpace(string(ccb)) 34 35 cmd = exec.Command(goTool, "env", "GOGCCFLAGS") 36 cmd.Env = env 37 cflagsb, err := cmd.Output() 38 if err != nil { 39 t.Fatal(err) 40 } 41 cflags := strings.Fields(string(cflagsb)) 42 43 return cc, cflags 44 } 45 46 var asmSource = ` 47 .section .text1,"ax" 48 s1: 49 .byte 0 50 .section .text2,"ax" 51 s2: 52 .byte 0 53 ` 54 55 var goSource = ` 56 package main 57 func main() {} 58 ` 59 60 // The linker used to crash if an ELF input file had multiple text sections 61 // with the same name. 62 func TestSectionsWithSameName(t *testing.T) { 63 testenv.MustHaveGoBuild(t) 64 testenv.MustHaveCGO(t) 65 t.Parallel() 66 67 objcopy, err := exec.LookPath("objcopy") 68 if err != nil { 69 t.Skipf("can't find objcopy: %v", err) 70 } 71 72 dir, err := ioutil.TempDir("", "go-link-TestSectionsWithSameName") 73 if err != nil { 74 t.Fatal(err) 75 } 76 defer os.RemoveAll(dir) 77 78 gopath := filepath.Join(dir, "GOPATH") 79 env := append(os.Environ(), "GOPATH="+gopath) 80 81 if err := ioutil.WriteFile(filepath.Join(dir, "go.mod"), []byte("module elf_test\n"), 0666); err != nil { 82 t.Fatal(err) 83 } 84 85 asmFile := filepath.Join(dir, "x.s") 86 if err := ioutil.WriteFile(asmFile, []byte(asmSource), 0444); err != nil { 87 t.Fatal(err) 88 } 89 90 goTool := testenv.GoToolPath(t) 91 cc, cflags := getCCAndCCFLAGS(t, env) 92 93 asmObj := filepath.Join(dir, "x.o") 94 t.Logf("%s %v -c -o %s %s", cc, cflags, asmObj, asmFile) 95 if out, err := exec.Command(cc, append(cflags, "-c", "-o", asmObj, asmFile)...).CombinedOutput(); err != nil { 96 t.Logf("%s", out) 97 t.Fatal(err) 98 } 99 100 asm2Obj := filepath.Join(dir, "x2.syso") 101 t.Logf("%s --rename-section .text2=.text1 %s %s", objcopy, asmObj, asm2Obj) 102 if out, err := exec.Command(objcopy, "--rename-section", ".text2=.text1", asmObj, asm2Obj).CombinedOutput(); err != nil { 103 t.Logf("%s", out) 104 t.Fatal(err) 105 } 106 107 for _, s := range []string{asmFile, asmObj} { 108 if err := os.Remove(s); err != nil { 109 t.Fatal(err) 110 } 111 } 112 113 goFile := filepath.Join(dir, "main.go") 114 if err := ioutil.WriteFile(goFile, []byte(goSource), 0444); err != nil { 115 t.Fatal(err) 116 } 117 118 cmd := exec.Command(goTool, "build") 119 cmd.Dir = dir 120 cmd.Env = env 121 t.Logf("%s build", goTool) 122 if out, err := cmd.CombinedOutput(); err != nil { 123 t.Logf("%s", out) 124 t.Fatal(err) 125 } 126 } 127 128 var cSources35779 = []string{` 129 static int blah() { return 42; } 130 int Cfunc1() { return blah(); } 131 `, ` 132 static int blah() { return 42; } 133 int Cfunc2() { return blah(); } 134 `, 135 } 136 137 // TestMinusRSymsWithSameName tests a corner case in the new 138 // loader. Prior to the fix this failed with the error 'loadelf: 139 // $WORK/b001/_pkg_.a(ldr.syso): duplicate symbol reference: blah in 140 // both main(.text) and main(.text)'. See issue #35779. 141 func TestMinusRSymsWithSameName(t *testing.T) { 142 testenv.MustHaveGoBuild(t) 143 testenv.MustHaveCGO(t) 144 t.Parallel() 145 146 dir, err := ioutil.TempDir("", "go-link-TestMinusRSymsWithSameName") 147 if err != nil { 148 t.Fatal(err) 149 } 150 defer os.RemoveAll(dir) 151 152 gopath := filepath.Join(dir, "GOPATH") 153 env := append(os.Environ(), "GOPATH="+gopath) 154 155 if err := ioutil.WriteFile(filepath.Join(dir, "go.mod"), []byte("module elf_test\n"), 0666); err != nil { 156 t.Fatal(err) 157 } 158 159 goTool := testenv.GoToolPath(t) 160 cc, cflags := getCCAndCCFLAGS(t, env) 161 162 objs := []string{} 163 csrcs := []string{} 164 for i, content := range cSources35779 { 165 csrcFile := filepath.Join(dir, fmt.Sprintf("x%d.c", i)) 166 csrcs = append(csrcs, csrcFile) 167 if err := ioutil.WriteFile(csrcFile, []byte(content), 0444); err != nil { 168 t.Fatal(err) 169 } 170 171 obj := filepath.Join(dir, fmt.Sprintf("x%d.o", i)) 172 objs = append(objs, obj) 173 t.Logf("%s %v -c -o %s %s", cc, cflags, obj, csrcFile) 174 if out, err := exec.Command(cc, append(cflags, "-c", "-o", obj, csrcFile)...).CombinedOutput(); err != nil { 175 t.Logf("%s", out) 176 t.Fatal(err) 177 } 178 } 179 180 sysoObj := filepath.Join(dir, "ldr.syso") 181 t.Logf("%s %v -nostdlib -r -o %s %v", cc, cflags, sysoObj, objs) 182 if out, err := exec.Command(cc, append(cflags, "-nostdlib", "-r", "-o", sysoObj, objs[0], objs[1])...).CombinedOutput(); err != nil { 183 t.Logf("%s", out) 184 t.Fatal(err) 185 } 186 187 cruft := [][]string{objs, csrcs} 188 for _, sl := range cruft { 189 for _, s := range sl { 190 if err := os.Remove(s); err != nil { 191 t.Fatal(err) 192 } 193 } 194 } 195 196 goFile := filepath.Join(dir, "main.go") 197 if err := ioutil.WriteFile(goFile, []byte(goSource), 0444); err != nil { 198 t.Fatal(err) 199 } 200 201 t.Logf("%s build", goTool) 202 cmd := exec.Command(goTool, "build") 203 cmd.Dir = dir 204 cmd.Env = env 205 if out, err := cmd.CombinedOutput(); err != nil { 206 t.Logf("%s", out) 207 t.Fatal(err) 208 } 209 } 210 211 const pieSourceTemplate = ` 212 package main 213 214 import "fmt" 215 216 // Force the creation of a lot of type descriptors that will go into 217 // the .data.rel.ro section. 218 {{range $index, $element := .}}var V{{$index}} interface{} = [{{$index}}]int{} 219 {{end}} 220 221 func main() { 222 {{range $index, $element := .}} fmt.Println(V{{$index}}) 223 {{end}} 224 } 225 ` 226 227 func TestPIESize(t *testing.T) { 228 testenv.MustHaveGoBuild(t) 229 if !sys.BuildModeSupported(runtime.Compiler, "pie", runtime.GOOS, runtime.GOARCH) { 230 t.Skip("-buildmode=pie not supported") 231 } 232 233 tmpl := template.Must(template.New("pie").Parse(pieSourceTemplate)) 234 235 writeGo := func(t *testing.T, dir string) { 236 f, err := os.Create(filepath.Join(dir, "pie.go")) 237 if err != nil { 238 t.Fatal(err) 239 } 240 241 // Passing a 100-element slice here will cause 242 // pieSourceTemplate to create 100 variables with 243 // different types. 244 if err := tmpl.Execute(f, make([]byte, 100)); err != nil { 245 t.Fatal(err) 246 } 247 248 if err := f.Close(); err != nil { 249 t.Fatal(err) 250 } 251 } 252 253 for _, external := range []bool{false, true} { 254 external := external 255 256 name := "TestPieSize-" 257 if external { 258 name += "external" 259 } else { 260 name += "internal" 261 } 262 t.Run(name, func(t *testing.T) { 263 t.Parallel() 264 265 dir, err := ioutil.TempDir("", "go-link-"+name) 266 if err != nil { 267 t.Fatal(err) 268 } 269 defer os.RemoveAll(dir) 270 271 writeGo(t, dir) 272 273 binexe := filepath.Join(dir, "exe") 274 binpie := filepath.Join(dir, "pie") 275 if external { 276 binexe += "external" 277 binpie += "external" 278 } 279 280 build := func(bin, mode string) error { 281 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", bin, "-buildmode="+mode) 282 if external { 283 cmd.Args = append(cmd.Args, "-ldflags=-linkmode=external") 284 } 285 cmd.Args = append(cmd.Args, "pie.go") 286 cmd.Dir = dir 287 t.Logf("%v", cmd.Args) 288 out, err := cmd.CombinedOutput() 289 if len(out) > 0 { 290 t.Logf("%s", out) 291 } 292 if err != nil { 293 t.Error(err) 294 } 295 return err 296 } 297 298 var errexe, errpie error 299 var wg sync.WaitGroup 300 wg.Add(2) 301 go func() { 302 defer wg.Done() 303 errexe = build(binexe, "exe") 304 }() 305 go func() { 306 defer wg.Done() 307 errpie = build(binpie, "pie") 308 }() 309 wg.Wait() 310 if errexe != nil || errpie != nil { 311 t.Fatal("link failed") 312 } 313 314 var sizeexe, sizepie uint64 315 if fi, err := os.Stat(binexe); err != nil { 316 t.Fatal(err) 317 } else { 318 sizeexe = uint64(fi.Size()) 319 } 320 if fi, err := os.Stat(binpie); err != nil { 321 t.Fatal(err) 322 } else { 323 sizepie = uint64(fi.Size()) 324 } 325 326 elfexe, err := elf.Open(binexe) 327 if err != nil { 328 t.Fatal(err) 329 } 330 defer elfexe.Close() 331 332 elfpie, err := elf.Open(binpie) 333 if err != nil { 334 t.Fatal(err) 335 } 336 defer elfpie.Close() 337 338 // The difference in size between exe and PIE 339 // should be approximately the difference in 340 // size of the .text section plus the size of 341 // the PIE dynamic data sections plus the 342 // difference in size of the .got and .plt 343 // sections if they exist. 344 // We ignore unallocated sections. 345 // There may be gaps between non-writeable and 346 // writable PT_LOAD segments. We also skip those 347 // gaps (see issue #36023). 348 349 textsize := func(ef *elf.File, name string) uint64 { 350 for _, s := range ef.Sections { 351 if s.Name == ".text" { 352 return s.Size 353 } 354 } 355 t.Fatalf("%s: no .text section", name) 356 return 0 357 } 358 textexe := textsize(elfexe, binexe) 359 textpie := textsize(elfpie, binpie) 360 361 dynsize := func(ef *elf.File) uint64 { 362 var ret uint64 363 for _, s := range ef.Sections { 364 if s.Flags&elf.SHF_ALLOC == 0 { 365 continue 366 } 367 switch s.Type { 368 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: 369 ret += s.Size 370 } 371 if s.Flags&elf.SHF_WRITE != 0 && (strings.Contains(s.Name, ".got") || strings.Contains(s.Name, ".plt")) { 372 ret += s.Size 373 } 374 } 375 return ret 376 } 377 378 dynexe := dynsize(elfexe) 379 dynpie := dynsize(elfpie) 380 381 extrasize := func(ef *elf.File) uint64 { 382 var ret uint64 383 // skip unallocated sections 384 for _, s := range ef.Sections { 385 if s.Flags&elf.SHF_ALLOC == 0 { 386 ret += s.Size 387 } 388 } 389 // also skip gaps between PT_LOAD segments 390 for i := range ef.Progs { 391 if i == 0 { 392 continue 393 } 394 p1 := ef.Progs[i-1] 395 p2 := ef.Progs[i] 396 if p1.Type == elf.PT_LOAD && p2.Type == elf.PT_LOAD { 397 ret += p2.Off - p1.Off - p1.Filesz 398 } 399 } 400 return ret 401 } 402 403 extraexe := extrasize(elfexe) 404 extrapie := extrasize(elfpie) 405 406 diffReal := (sizepie - extrapie) - (sizeexe - extraexe) 407 diffExpected := (textpie + dynpie) - (textexe + dynexe) 408 409 t.Logf("real size difference %#x, expected %#x", diffReal, diffExpected) 410 411 if diffReal > (diffExpected + diffExpected/10) { 412 t.Errorf("PIE unexpectedly large: got difference of %d (%d - %d), expected difference %d", diffReal, sizepie, sizeexe, diffExpected) 413 } 414 }) 415 } 416 }