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