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//go:build dragonfly || freebsd || linux || netbsd || openbsd 6 7package main 8 9import ( 10 "bytes" 11 "cmd/internal/buildid" 12 "cmd/internal/notsha256" 13 "cmd/link/internal/ld" 14 "debug/elf" 15 "fmt" 16 "internal/platform" 17 "internal/testenv" 18 "os" 19 "os/exec" 20 "path/filepath" 21 "runtime" 22 "strings" 23 "sync" 24 "testing" 25 "text/template" 26) 27 28func getCCAndCCFLAGS(t *testing.T, env []string) (string, []string) { 29 goTool := testenv.GoToolPath(t) 30 cmd := testenv.Command(t, goTool, "env", "CC") 31 cmd.Env = env 32 ccb, err := cmd.Output() 33 if err != nil { 34 t.Fatal(err) 35 } 36 cc := strings.TrimSpace(string(ccb)) 37 38 cmd = testenv.Command(t, goTool, "env", "GOGCCFLAGS") 39 cmd.Env = env 40 cflagsb, err := cmd.Output() 41 if err != nil { 42 t.Fatal(err) 43 } 44 cflags := strings.Fields(string(cflagsb)) 45 46 return cc, cflags 47} 48 49var asmSource = ` 50 .section .text1,"ax" 51s1: 52 .byte 0 53 .section .text2,"ax" 54s2: 55 .byte 0 56` 57 58var goSource = ` 59package main 60func main() {} 61` 62 63// The linker used to crash if an ELF input file had multiple text sections 64// with the same name. 65func TestSectionsWithSameName(t *testing.T) { 66 testenv.MustHaveGoBuild(t) 67 testenv.MustHaveCGO(t) 68 t.Parallel() 69 70 objcopy, err := exec.LookPath("objcopy") 71 if err != nil { 72 t.Skipf("can't find objcopy: %v", err) 73 } 74 75 dir := t.TempDir() 76 77 gopath := filepath.Join(dir, "GOPATH") 78 env := append(os.Environ(), "GOPATH="+gopath) 79 80 if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module elf_test\n"), 0666); err != nil { 81 t.Fatal(err) 82 } 83 84 asmFile := filepath.Join(dir, "x.s") 85 if err := os.WriteFile(asmFile, []byte(asmSource), 0444); err != nil { 86 t.Fatal(err) 87 } 88 89 goTool := testenv.GoToolPath(t) 90 cc, cflags := getCCAndCCFLAGS(t, env) 91 92 asmObj := filepath.Join(dir, "x.o") 93 t.Logf("%s %v -c -o %s %s", cc, cflags, asmObj, asmFile) 94 if out, err := testenv.Command(t, cc, append(cflags, "-c", "-o", asmObj, asmFile)...).CombinedOutput(); err != nil { 95 t.Logf("%s", out) 96 t.Fatal(err) 97 } 98 99 asm2Obj := filepath.Join(dir, "x2.syso") 100 t.Logf("%s --rename-section .text2=.text1 %s %s", objcopy, asmObj, asm2Obj) 101 if out, err := testenv.Command(t, objcopy, "--rename-section", ".text2=.text1", asmObj, asm2Obj).CombinedOutput(); err != nil { 102 t.Logf("%s", out) 103 t.Fatal(err) 104 } 105 106 for _, s := range []string{asmFile, asmObj} { 107 if err := os.Remove(s); err != nil { 108 t.Fatal(err) 109 } 110 } 111 112 goFile := filepath.Join(dir, "main.go") 113 if err := os.WriteFile(goFile, []byte(goSource), 0444); err != nil { 114 t.Fatal(err) 115 } 116 117 cmd := testenv.Command(t, goTool, "build") 118 cmd.Dir = dir 119 cmd.Env = env 120 t.Logf("%s build", goTool) 121 if out, err := cmd.CombinedOutput(); err != nil { 122 t.Logf("%s", out) 123 t.Fatal(err) 124 } 125} 126 127var cSources35779 = []string{` 128static int blah() { return 42; } 129int Cfunc1() { return blah(); } 130`, ` 131static int blah() { return 42; } 132int Cfunc2() { return blah(); } 133`, 134} 135 136// TestMinusRSymsWithSameName tests a corner case in the new 137// loader. Prior to the fix this failed with the error 'loadelf: 138// $WORK/b001/_pkg_.a(ldr.syso): duplicate symbol reference: blah in 139// both main(.text) and main(.text)'. See issue #35779. 140func TestMinusRSymsWithSameName(t *testing.T) { 141 testenv.MustHaveGoBuild(t) 142 testenv.MustHaveCGO(t) 143 t.Parallel() 144 145 dir := t.TempDir() 146 147 gopath := filepath.Join(dir, "GOPATH") 148 env := append(os.Environ(), "GOPATH="+gopath) 149 150 if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module elf_test\n"), 0666); err != nil { 151 t.Fatal(err) 152 } 153 154 goTool := testenv.GoToolPath(t) 155 cc, cflags := getCCAndCCFLAGS(t, env) 156 157 objs := []string{} 158 csrcs := []string{} 159 for i, content := range cSources35779 { 160 csrcFile := filepath.Join(dir, fmt.Sprintf("x%d.c", i)) 161 csrcs = append(csrcs, csrcFile) 162 if err := os.WriteFile(csrcFile, []byte(content), 0444); err != nil { 163 t.Fatal(err) 164 } 165 166 obj := filepath.Join(dir, fmt.Sprintf("x%d.o", i)) 167 objs = append(objs, obj) 168 t.Logf("%s %v -c -o %s %s", cc, cflags, obj, csrcFile) 169 if out, err := testenv.Command(t, cc, append(cflags, "-c", "-o", obj, csrcFile)...).CombinedOutput(); err != nil { 170 t.Logf("%s", out) 171 t.Fatal(err) 172 } 173 } 174 175 sysoObj := filepath.Join(dir, "ldr.syso") 176 t.Logf("%s %v -nostdlib -r -o %s %v", cc, cflags, sysoObj, objs) 177 if out, err := testenv.Command(t, cc, append(cflags, "-nostdlib", "-r", "-o", sysoObj, objs[0], objs[1])...).CombinedOutput(); err != nil { 178 t.Logf("%s", out) 179 t.Fatal(err) 180 } 181 182 cruft := [][]string{objs, csrcs} 183 for _, sl := range cruft { 184 for _, s := range sl { 185 if err := os.Remove(s); err != nil { 186 t.Fatal(err) 187 } 188 } 189 } 190 191 goFile := filepath.Join(dir, "main.go") 192 if err := os.WriteFile(goFile, []byte(goSource), 0444); err != nil { 193 t.Fatal(err) 194 } 195 196 t.Logf("%s build", goTool) 197 cmd := testenv.Command(t, goTool, "build") 198 cmd.Dir = dir 199 cmd.Env = env 200 if out, err := cmd.CombinedOutput(); err != nil { 201 t.Logf("%s", out) 202 t.Fatal(err) 203 } 204} 205 206func TestGNUBuildIDDerivedFromGoBuildID(t *testing.T) { 207 testenv.MustHaveGoBuild(t) 208 209 t.Parallel() 210 211 goFile := filepath.Join(t.TempDir(), "notes.go") 212 if err := os.WriteFile(goFile, []byte(goSource), 0444); err != nil { 213 t.Fatal(err) 214 } 215 outFile := filepath.Join(t.TempDir(), "notes.exe") 216 goTool := testenv.GoToolPath(t) 217 218 cmd := testenv.Command(t, goTool, "build", "-o", outFile, "-ldflags", "-buildid 0x1234 -B gobuildid", goFile) 219 cmd.Dir = t.TempDir() 220 221 out, err := cmd.CombinedOutput() 222 if err != nil { 223 t.Logf("%s", out) 224 t.Fatal(err) 225 } 226 227 expectedGoBuildID := notsha256.Sum256([]byte("0x1234")) 228 229 gnuBuildID, err := buildid.ReadELFNote(outFile, string(ld.ELF_NOTE_BUILDINFO_NAME), ld.ELF_NOTE_BUILDINFO_TAG) 230 if err != nil || gnuBuildID == nil { 231 t.Fatalf("can't read GNU build ID") 232 } 233 234 if !bytes.Equal(gnuBuildID, expectedGoBuildID[:20]) { 235 t.Fatalf("build id not matching") 236 } 237} 238 239func TestMergeNoteSections(t *testing.T) { 240 testenv.MustHaveGoBuild(t) 241 expected := 1 242 243 switch runtime.GOOS { 244 case "linux", "dragonfly": 245 case "openbsd", "netbsd", "freebsd": 246 // These OSes require independent segment 247 expected = 2 248 default: 249 t.Skip("We should only test on elf output.") 250 } 251 t.Parallel() 252 253 goFile := filepath.Join(t.TempDir(), "notes.go") 254 if err := os.WriteFile(goFile, []byte(goSource), 0444); err != nil { 255 t.Fatal(err) 256 } 257 outFile := filepath.Join(t.TempDir(), "notes.exe") 258 goTool := testenv.GoToolPath(t) 259 // sha1sum of "gopher" 260 id := "0xf4e8cd51ce8bae2996dc3b74639cdeaa1f7fee5f" 261 cmd := testenv.Command(t, goTool, "build", "-o", outFile, "-ldflags", 262 "-B "+id, goFile) 263 cmd.Dir = t.TempDir() 264 if out, err := cmd.CombinedOutput(); err != nil { 265 t.Logf("%s", out) 266 t.Fatal(err) 267 } 268 269 ef, err := elf.Open(outFile) 270 if err != nil { 271 t.Fatalf("open elf file failed:%v", err) 272 } 273 defer ef.Close() 274 sec := ef.Section(".note.gnu.build-id") 275 if sec == nil { 276 t.Fatalf("can't find gnu build id") 277 } 278 279 sec = ef.Section(".note.go.buildid") 280 if sec == nil { 281 t.Fatalf("can't find go build id") 282 } 283 cnt := 0 284 for _, ph := range ef.Progs { 285 if ph.Type == elf.PT_NOTE { 286 cnt += 1 287 } 288 } 289 if cnt != expected { 290 t.Fatalf("want %d PT_NOTE segment, got %d", expected, cnt) 291 } 292} 293 294const pieSourceTemplate = ` 295package main 296 297import "fmt" 298 299// Force the creation of a lot of type descriptors that will go into 300// the .data.rel.ro section. 301{{range $index, $element := .}}var V{{$index}} interface{} = [{{$index}}]int{} 302{{end}} 303 304func main() { 305{{range $index, $element := .}} fmt.Println(V{{$index}}) 306{{end}} 307} 308` 309 310func TestPIESize(t *testing.T) { 311 testenv.MustHaveGoBuild(t) 312 313 // We don't want to test -linkmode=external if cgo is not supported. 314 // On some systems -buildmode=pie implies -linkmode=external, so just 315 // always skip the test if cgo is not supported. 316 testenv.MustHaveCGO(t) 317 318 if !platform.BuildModeSupported(runtime.Compiler, "pie", runtime.GOOS, runtime.GOARCH) { 319 t.Skip("-buildmode=pie not supported") 320 } 321 322 t.Parallel() 323 324 tmpl := template.Must(template.New("pie").Parse(pieSourceTemplate)) 325 326 writeGo := func(t *testing.T, dir string) { 327 f, err := os.Create(filepath.Join(dir, "pie.go")) 328 if err != nil { 329 t.Fatal(err) 330 } 331 332 // Passing a 100-element slice here will cause 333 // pieSourceTemplate to create 100 variables with 334 // different types. 335 if err := tmpl.Execute(f, make([]byte, 100)); err != nil { 336 t.Fatal(err) 337 } 338 339 if err := f.Close(); err != nil { 340 t.Fatal(err) 341 } 342 } 343 344 for _, external := range []bool{false, true} { 345 external := external 346 347 name := "TestPieSize-" 348 if external { 349 name += "external" 350 } else { 351 name += "internal" 352 } 353 t.Run(name, func(t *testing.T) { 354 t.Parallel() 355 356 dir := t.TempDir() 357 358 writeGo(t, dir) 359 360 binexe := filepath.Join(dir, "exe") 361 binpie := filepath.Join(dir, "pie") 362 if external { 363 binexe += "external" 364 binpie += "external" 365 } 366 367 build := func(bin, mode string) error { 368 cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", bin, "-buildmode="+mode) 369 if external { 370 cmd.Args = append(cmd.Args, "-ldflags=-linkmode=external") 371 } 372 cmd.Args = append(cmd.Args, "pie.go") 373 cmd.Dir = dir 374 t.Logf("%v", cmd.Args) 375 out, err := cmd.CombinedOutput() 376 if len(out) > 0 { 377 t.Logf("%s", out) 378 } 379 if err != nil { 380 t.Log(err) 381 } 382 return err 383 } 384 385 var errexe, errpie error 386 var wg sync.WaitGroup 387 wg.Add(2) 388 go func() { 389 defer wg.Done() 390 errexe = build(binexe, "exe") 391 }() 392 go func() { 393 defer wg.Done() 394 errpie = build(binpie, "pie") 395 }() 396 wg.Wait() 397 if errexe != nil || errpie != nil { 398 if runtime.GOOS == "android" && runtime.GOARCH == "arm64" { 399 testenv.SkipFlaky(t, 58806) 400 } 401 t.Fatal("link failed") 402 } 403 404 var sizeexe, sizepie uint64 405 if fi, err := os.Stat(binexe); err != nil { 406 t.Fatal(err) 407 } else { 408 sizeexe = uint64(fi.Size()) 409 } 410 if fi, err := os.Stat(binpie); err != nil { 411 t.Fatal(err) 412 } else { 413 sizepie = uint64(fi.Size()) 414 } 415 416 elfexe, err := elf.Open(binexe) 417 if err != nil { 418 t.Fatal(err) 419 } 420 defer elfexe.Close() 421 422 elfpie, err := elf.Open(binpie) 423 if err != nil { 424 t.Fatal(err) 425 } 426 defer elfpie.Close() 427 428 // The difference in size between exe and PIE 429 // should be approximately the difference in 430 // size of the .text section plus the size of 431 // the PIE dynamic data sections plus the 432 // difference in size of the .got and .plt 433 // sections if they exist. 434 // We ignore unallocated sections. 435 // There may be gaps between non-writeable and 436 // writable PT_LOAD segments. We also skip those 437 // gaps (see issue #36023). 438 439 textsize := func(ef *elf.File, name string) uint64 { 440 for _, s := range ef.Sections { 441 if s.Name == ".text" { 442 return s.Size 443 } 444 } 445 t.Fatalf("%s: no .text section", name) 446 return 0 447 } 448 textexe := textsize(elfexe, binexe) 449 textpie := textsize(elfpie, binpie) 450 451 dynsize := func(ef *elf.File) uint64 { 452 var ret uint64 453 for _, s := range ef.Sections { 454 if s.Flags&elf.SHF_ALLOC == 0 { 455 continue 456 } 457 switch s.Type { 458 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: 459 ret += s.Size 460 } 461 if s.Flags&elf.SHF_WRITE != 0 && (strings.Contains(s.Name, ".got") || strings.Contains(s.Name, ".plt")) { 462 ret += s.Size 463 } 464 } 465 return ret 466 } 467 468 dynexe := dynsize(elfexe) 469 dynpie := dynsize(elfpie) 470 471 extrasize := func(ef *elf.File) uint64 { 472 var ret uint64 473 // skip unallocated sections 474 for _, s := range ef.Sections { 475 if s.Flags&elf.SHF_ALLOC == 0 { 476 ret += s.Size 477 } 478 } 479 // also skip gaps between PT_LOAD segments 480 var prev *elf.Prog 481 for _, seg := range ef.Progs { 482 if seg.Type != elf.PT_LOAD { 483 continue 484 } 485 if prev != nil { 486 ret += seg.Off - prev.Off - prev.Filesz 487 } 488 prev = seg 489 } 490 return ret 491 } 492 493 extraexe := extrasize(elfexe) 494 extrapie := extrasize(elfpie) 495 496 if sizepie < sizeexe || sizepie-extrapie < sizeexe-extraexe { 497 return 498 } 499 diffReal := (sizepie - extrapie) - (sizeexe - extraexe) 500 diffExpected := (textpie + dynpie) - (textexe + dynexe) 501 502 t.Logf("real size difference %#x, expected %#x", diffReal, diffExpected) 503 504 if diffReal > (diffExpected + diffExpected/10) { 505 t.Errorf("PIE unexpectedly large: got difference of %d (%d - %d), expected difference %d", diffReal, sizepie, sizeexe, diffExpected) 506 } 507 }) 508 } 509} 510 511func TestIssue51939(t *testing.T) { 512 testenv.MustHaveGoBuild(t) 513 t.Parallel() 514 td := t.TempDir() 515 goFile := filepath.Join(td, "issue51939.go") 516 if err := os.WriteFile(goFile, []byte(goSource), 0444); err != nil { 517 t.Fatal(err) 518 } 519 outFile := filepath.Join(td, "issue51939.exe") 520 goTool := testenv.GoToolPath(t) 521 cmd := testenv.Command(t, goTool, "build", "-o", outFile, goFile) 522 if out, err := cmd.CombinedOutput(); err != nil { 523 t.Logf("%s", out) 524 t.Fatal(err) 525 } 526 527 ef, err := elf.Open(outFile) 528 if err != nil { 529 t.Fatal(err) 530 } 531 532 for _, s := range ef.Sections { 533 if s.Flags&elf.SHF_ALLOC == 0 && s.Addr != 0 { 534 t.Errorf("section %s should not allocated with addr %x", s.Name, s.Addr) 535 } 536 } 537} 538 539func TestFlagR(t *testing.T) { 540 // Test that using the -R flag to specify a (large) alignment generates 541 // a working binary. 542 // (Test only on ELF for now. The alignment allowed differs from platform 543 // to platform.) 544 testenv.MustHaveGoBuild(t) 545 t.Parallel() 546 tmpdir := t.TempDir() 547 src := filepath.Join(tmpdir, "x.go") 548 if err := os.WriteFile(src, []byte(goSource), 0444); err != nil { 549 t.Fatal(err) 550 } 551 exe := filepath.Join(tmpdir, "x.exe") 552 553 cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-R=0x100000", "-o", exe, src) 554 if out, err := cmd.CombinedOutput(); err != nil { 555 t.Fatalf("build failed: %v, output:\n%s", err, out) 556 } 557 558 cmd = testenv.Command(t, exe) 559 if out, err := cmd.CombinedOutput(); err != nil { 560 t.Errorf("executable failed to run: %v\n%s", err, out) 561 } 562} 563