Source file
src/cmd/link/elf_test.go
1
2
3
4
5
6
7 package main
8
9 import (
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
28 func 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
49 var asmSource = `
50 .section .text1,"ax"
51 s1:
52 .byte 0
53 .section .text2,"ax"
54 s2:
55 .byte 0
56 `
57
58 var goSource = `
59 package main
60 func main() {}
61 `
62
63
64
65 func 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
127 var cSources35779 = []string{`
128 static int blah() { return 42; }
129 int Cfunc1() { return blah(); }
130 `, `
131 static int blah() { return 42; }
132 int Cfunc2() { return blah(); }
133 `,
134 }
135
136
137
138
139
140 func 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
206 func 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
239 func 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
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
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
294 const pieSourceTemplate = `
295 package main
296
297 import "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
304 func main() {
305 {{range $index, $element := .}} fmt.Println(V{{$index}})
306 {{end}}
307 }
308 `
309
310 func TestPIESize(t *testing.T) {
311 testenv.MustHaveGoBuild(t)
312
313
314
315
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
333
334
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
429
430
431
432
433
434
435
436
437
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
474 for _, s := range ef.Sections {
475 if s.Flags&elf.SHF_ALLOC == 0 {
476 ret += s.Size
477 }
478 }
479
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
511 func 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
539 func TestFlagR(t *testing.T) {
540
541
542
543
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
View as plain text