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