1
2
3
4
5 package ld
6
7 import (
8 "bytes"
9 "debug/pe"
10 "fmt"
11 "internal/testenv"
12 "os"
13 "path/filepath"
14 "runtime"
15 "strings"
16 "testing"
17 )
18
19 func TestUndefinedRelocErrors(t *testing.T) {
20 testenv.MustHaveGoBuild(t)
21
22
23
24
25
26
27 testenv.MustInternalLink(t, testenv.NoSpecialBuildTypes)
28
29 t.Parallel()
30
31 out, err := testenv.Command(t, testenv.GoToolPath(t), "build", "./testdata/issue10978").CombinedOutput()
32 if err == nil {
33 t.Fatal("expected build to fail")
34 }
35
36 wantErrors := map[string]int{
37
38 "function main is undeclared in the main package": 1,
39
40
41
42
43 "main.defined1: relocation target main.undefined not defined": 1,
44 "main.defined2: relocation target main.undefined not defined": 1,
45 }
46 unexpectedErrors := map[string]int{}
47
48 for _, l := range strings.Split(string(out), "\n") {
49 if strings.HasPrefix(l, "#") || l == "" {
50 continue
51 }
52 matched := ""
53 for want := range wantErrors {
54 if strings.Contains(l, want) {
55 matched = want
56 break
57 }
58 }
59 if matched != "" {
60 wantErrors[matched]--
61 } else {
62 unexpectedErrors[l]++
63 }
64 }
65
66 for want, n := range wantErrors {
67 switch {
68 case n > 0:
69 t.Errorf("unmatched error: %s (x%d)", want, n)
70 case n < 0:
71 if runtime.GOOS == "android" && runtime.GOARCH == "arm64" {
72 testenv.SkipFlaky(t, 58807)
73 }
74 t.Errorf("extra errors: %s (x%d)", want, -n)
75 }
76 }
77 for unexpected, n := range unexpectedErrors {
78 t.Errorf("unexpected error: %s (x%d)", unexpected, n)
79 }
80 }
81
82 const carchiveSrcText = `
83 package main
84
85 //export GoFunc
86 func GoFunc() {
87 println(42)
88 }
89
90 func main() {
91 }
92 `
93
94 func TestArchiveBuildInvokeWithExec(t *testing.T) {
95 t.Parallel()
96 testenv.MustHaveGoBuild(t)
97 testenv.MustHaveCGO(t)
98
99
100
101 pair := runtime.GOOS + "-" + runtime.GOARCH
102 switch pair {
103 case "darwin-amd64", "darwin-arm64", "linux-amd64", "freebsd-amd64":
104 default:
105 t.Skip("no need for test on " + pair)
106 }
107 switch runtime.GOOS {
108 case "openbsd", "windows":
109 t.Skip("c-archive unsupported")
110 }
111 dir := t.TempDir()
112
113 srcfile := filepath.Join(dir, "test.go")
114 arfile := filepath.Join(dir, "test.a")
115 if err := os.WriteFile(srcfile, []byte(carchiveSrcText), 0666); err != nil {
116 t.Fatal(err)
117 }
118
119 ldf := fmt.Sprintf("-ldflags=-v -tmpdir=%s", dir)
120 argv := []string{"build", "-buildmode=c-archive", "-o", arfile, ldf, srcfile}
121 out, err := testenv.Command(t, testenv.GoToolPath(t), argv...).CombinedOutput()
122 if err != nil {
123 t.Fatalf("build failure: %s\n%s\n", err, string(out))
124 }
125
126 found := false
127 const want = "invoking archiver with syscall.Exec"
128 for _, l := range strings.Split(string(out), "\n") {
129 if strings.HasPrefix(l, want) {
130 found = true
131 break
132 }
133 }
134
135 if !found {
136 t.Errorf("expected '%s' in -v output, got:\n%s\n", want, string(out))
137 }
138 }
139
140 func TestLargeTextSectionSplitting(t *testing.T) {
141 switch runtime.GOARCH {
142 case "ppc64", "ppc64le", "arm":
143 case "arm64":
144 if runtime.GOOS == "darwin" {
145 break
146 }
147 fallthrough
148 default:
149 t.Skipf("text section splitting is not done in %s/%s", runtime.GOOS, runtime.GOARCH)
150 }
151
152 testenv.MustHaveGoBuild(t)
153 testenv.MustHaveCGO(t)
154 t.Parallel()
155 dir := t.TempDir()
156
157
158
159
160
161
162 exe := filepath.Join(dir, "go.exe")
163 out, err := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe, "-ldflags=-linkmode=external -debugtextsize=1048576", "cmd/go").CombinedOutput()
164 if err != nil {
165 t.Fatalf("build failure: %s\n%s\n", err, string(out))
166 }
167
168
169 out, err = testenv.Command(t, testenv.GoToolPath(t), "tool", "nm", exe).CombinedOutput()
170 if err != nil {
171 t.Fatalf("nm failure: %s\n%s\n", err, string(out))
172 }
173 if !bytes.Contains(out, []byte("runtime.text.1")) {
174 t.Errorf("runtime.text.1 not found, text section not split?")
175 }
176
177
178 _, err = testenv.Command(t, exe, "version").CombinedOutput()
179 if err != nil {
180 t.Fatal(err)
181 }
182 }
183
184
185
186 func TestTextSectionSplitOverAligned(t *testing.T) {
187
188
189
190 var funcAsm, filler string
191 switch runtime.GOARCH {
192 case "arm64":
193 if runtime.GOOS != "darwin" {
194 t.Skipf("text section splitting is not done in %s/%s", runtime.GOOS, runtime.GOARCH)
195 }
196 funcAsm = `
197 TEXT ·f%[1]d(SB), NOSPLIT, $0-8
198 %[2]s
199 MOVD $·sink(SB), R1
200 MOVD (R1), R2
201 ADD $1, R2
202 MOVD R2, (R1)
203 %[3]s
204 MOVD $%[1]d, R0
205 MOVD R0, ret+0(FP)
206 RET
207 `
208 filler = "\tNOOP\n"
209 case "ppc64", "ppc64le":
210 funcAsm = `
211 TEXT ·f%[1]d(SB), NOSPLIT, $0-8
212 %[2]s
213 MOVD $·sink(SB), R3
214 MOVD 0(R3), R4
215 ADD $1, R4
216 MOVD R4, 0(R3)
217 %[3]s
218 MOVD $%[1]d, R3
219 MOVD R3, ret+0(FP)
220 RET
221 `
222 filler = "\tADD\t$1, R5\n"
223 default:
224
225
226 t.Skipf("no over-aligned text section splitting on %s/%s", runtime.GOOS, runtime.GOARCH)
227 }
228 testenv.MustHaveGoBuild(t)
229 testenv.MustHaveCGO(t)
230 t.Parallel()
231 dir := t.TempDir()
232
233
234
235
236 const n = 128
237 var goSrc, asmSrc bytes.Buffer
238 goSrc.WriteString("package main\n\nvar sink int\n\n")
239 asmSrc.WriteString("#include \"textflag.h\"\n")
240 for i := range n {
241 fmt.Fprintf(&goSrc, "func f%d() int\n", i)
242 align := ""
243 if i%2 == 0 {
244 align = "\tPCALIGN\t$64"
245 }
246 fmt.Fprintf(&asmSrc, funcAsm, i, align, strings.Repeat(filler, 2000))
247 }
248 goSrc.WriteString("\nvar fs = []func() int{")
249 for i := range n {
250 fmt.Fprintf(&goSrc, "f%d, ", i)
251 }
252 fmt.Fprintf(&goSrc, `}
253
254 func main() {
255 s := 0
256 for _, f := range fs {
257 s += f()
258 }
259 if s != %d || sink != %d {
260 panic(s)
261 }
262 }
263 `, n*(n-1)/2, n)
264 for name, src := range map[string][]byte{
265 "go.mod": []byte("module overaligned\n"),
266 "main.go": goSrc.Bytes(),
267 "funcs_" + runtime.GOARCH + ".s": asmSrc.Bytes(),
268 } {
269 if err := os.WriteFile(filepath.Join(dir, name), src, 0o666); err != nil {
270 t.Fatal(err)
271 }
272 }
273
274
275
276
277 for _, size := range []int{256 << 10, 320 << 10, 384 << 10} {
278 exe := filepath.Join(dir, fmt.Sprintf("split%d.exe", size))
279 cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe,
280 fmt.Sprintf("-ldflags=-linkmode=external -debugtextsize=%d", size))
281 cmd.Dir = dir
282 if out, err := cmd.CombinedOutput(); err != nil {
283 t.Fatalf("build with -debugtextsize=%d: %v\n%s", size, err, out)
284 }
285 if out, err := testenv.Command(t, exe).CombinedOutput(); err != nil {
286 t.Fatalf("run with -debugtextsize=%d: %v\n%s", size, err, out)
287 }
288 }
289 }
290
291 func TestWindowsBuildmodeCSharedASLR(t *testing.T) {
292 platform := fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)
293 switch platform {
294 case "windows/amd64", "windows/386":
295 default:
296 t.Skip("skipping windows amd64/386 only test")
297 }
298
299 testenv.MustHaveCGO(t)
300
301 t.Run("aslr", func(t *testing.T) {
302 testWindowsBuildmodeCSharedASLR(t, true)
303 })
304 t.Run("no-aslr", func(t *testing.T) {
305 testWindowsBuildmodeCSharedASLR(t, false)
306 })
307 }
308
309 func TestWindowsBuildmodeCSharedTrailingDotOutput(t *testing.T) {
310 if runtime.GOOS != "windows" {
311 t.Skip("skipping windows only test")
312 }
313
314 t.Parallel()
315 testenv.MustHaveGoBuild(t)
316 testenv.MustHaveCGO(t)
317 testenv.MustHaveBuildMode(t, "c-shared")
318
319 dir := t.TempDir()
320 srcfile := filepath.Join(dir, "test.go")
321 objfile := filepath.Join(dir, "mypackage.")
322 linktmp := filepath.Join(dir, "linktmp")
323 if err := os.Mkdir(linktmp, 0777); err != nil {
324 t.Fatal(err)
325 }
326 if err := os.WriteFile(srcfile, []byte(`package main
327 import "C"
328
329 //export Hello
330 func Hello() {}
331
332 func main() {}
333 `), 0666); err != nil {
334 t.Fatal(err)
335 }
336
337 argv := []string{"build", "-buildmode=c-shared", "-o", objfile, "-ldflags", "-tmpdir=" + linktmp, srcfile}
338 out, err := testenv.Command(t, testenv.GoToolPath(t), argv...).CombinedOutput()
339 if err != nil {
340 t.Fatalf("build failure: %s\n%s\n", err, string(out))
341 }
342
343 def, err := os.ReadFile(filepath.Join(linktmp, "export_file.def"))
344 if err != nil {
345 t.Fatal(err)
346 }
347 if want := []byte("LIBRARY \"mypackage.\"\n"); !bytes.HasPrefix(def, want) {
348 t.Fatalf("export_file.def begins with %q, want %q", def, want)
349 }
350 }
351
352 func testWindowsBuildmodeCSharedASLR(t *testing.T, useASLR bool) {
353 t.Parallel()
354 testenv.MustHaveGoBuild(t)
355
356 dir := t.TempDir()
357
358 srcfile := filepath.Join(dir, "test.go")
359 objfile := filepath.Join(dir, "test.dll")
360 if err := os.WriteFile(srcfile, []byte(`package main; func main() { print("hello") }`), 0666); err != nil {
361 t.Fatal(err)
362 }
363 argv := []string{"build", "-buildmode=c-shared"}
364 if !useASLR {
365 argv = append(argv, "-ldflags", "-aslr=false")
366 }
367 argv = append(argv, "-o", objfile, srcfile)
368 out, err := testenv.Command(t, testenv.GoToolPath(t), argv...).CombinedOutput()
369 if err != nil {
370 t.Fatalf("build failure: %s\n%s\n", err, string(out))
371 }
372
373 f, err := pe.Open(objfile)
374 if err != nil {
375 t.Fatal(err)
376 }
377 defer f.Close()
378 var dc uint16
379 switch oh := f.OptionalHeader.(type) {
380 case *pe.OptionalHeader32:
381 dc = oh.DllCharacteristics
382 case *pe.OptionalHeader64:
383 dc = oh.DllCharacteristics
384 hasHEVA := (dc & pe.IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA) != 0
385 if useASLR && !hasHEVA {
386 t.Error("IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA flag is not set")
387 } else if !useASLR && hasHEVA {
388 t.Error("IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA flag should not be set")
389 }
390 default:
391 t.Fatalf("unexpected optional header type of %T", f.OptionalHeader)
392 }
393 hasASLR := (dc & pe.IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) != 0
394 if useASLR && !hasASLR {
395 t.Error("IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE flag is not set")
396 } else if !useASLR && hasASLR {
397 t.Error("IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE flag should not be set")
398 }
399 }
400
401
402
403
404
405
406 func TestMemProfileCheck(t *testing.T) {
407 testenv.MustHaveGoBuild(t)
408 t.Parallel()
409
410 tests := []struct {
411 name string
412 prog string
413 wantOut string
414 }{
415 {
416 "no_memprofile",
417 `
418 package main
419 import "runtime"
420 func main() {
421 println(runtime.MemProfileRate)
422 }
423 `,
424 "0",
425 },
426 {
427 "with_memprofile",
428 `
429 package main
430 import "runtime"
431 func main() {
432 runtime.MemProfile(nil, false)
433 println(runtime.MemProfileRate)
434 }
435 `,
436 "524288",
437 },
438 {
439 "with_memprofile_indirect",
440 `
441 package main
442 import "runtime"
443 var f = runtime.MemProfile
444 func main() {
445 if f == nil {
446 panic("no f")
447 }
448 println(runtime.MemProfileRate)
449 }
450 `,
451 "524288",
452 },
453 {
454 "with_memprofile_runtime_pprof",
455 `
456 package main
457 import "runtime"
458 import "runtime/pprof"
459 func main() {
460 _ = pprof.Profiles()
461 println(runtime.MemProfileRate)
462 }
463 `,
464 "524288",
465 },
466 {
467 "with_memprofile_runtime_pprof_writeheap",
468 `
469 package main
470 import "io"
471 import "runtime"
472 import "runtime/pprof"
473 func main() {
474 _ = pprof.WriteHeapProfile(io.Discard)
475 println(runtime.MemProfileRate)
476 }
477 `,
478 "524288",
479 },
480 {
481 "with_memprofile_runtime_pprof_lookupheap",
482 `
483 package main
484 import "runtime"
485 import "runtime/pprof"
486 func main() {
487 _ = pprof.Lookup("heap")
488 println(runtime.MemProfileRate)
489 }
490 `,
491 "524288",
492 },
493 {
494 "with_memprofile_http_pprof",
495 `
496 package main
497 import "runtime"
498 import _ "net/http/pprof"
499 func main() {
500 println(runtime.MemProfileRate)
501 }
502 `,
503 "524288",
504 },
505 }
506 for _, tt := range tests {
507 t.Run(tt.name, func(t *testing.T) {
508 t.Parallel()
509 tempDir := t.TempDir()
510 src := filepath.Join(tempDir, "x.go")
511 if err := os.WriteFile(src, []byte(tt.prog), 0644); err != nil {
512 t.Fatal(err)
513 }
514 cmd := testenv.Command(t, testenv.GoToolPath(t), "run", src)
515 out, err := cmd.CombinedOutput()
516 if err != nil {
517 t.Fatal(err)
518 }
519 got := strings.TrimSpace(string(out))
520 if got != tt.wantOut {
521 t.Errorf("got %q; want %q", got, tt.wantOut)
522 }
523 })
524 }
525 }
526
527 func TestRISCVTrampolines(t *testing.T) {
528
529 if testing.Short() && (runtime.GOOS != "linux" || runtime.GOARCH != "riscv64") {
530 t.Skipf("skipping on %s/%s in short mode", runtime.GOOS, runtime.GOARCH)
531 }
532
533 testenv.MustHaveGoBuild(t)
534 t.Parallel()
535
536 tmpDir := t.TempDir()
537 tmpFile := filepath.Join(tmpDir, "x.s")
538
539
540
541 buf := new(bytes.Buffer)
542 fmt.Fprintf(buf, "TEXT a(SB),$0-0\n")
543 for i := 0; i < 1<<17; i++ {
544 fmt.Fprintf(buf, "\tADD $0, X5, X0\n")
545 }
546 fmt.Fprintf(buf, "\tCALL b(SB)\n")
547 fmt.Fprintf(buf, "\tRET\n")
548 fmt.Fprintf(buf, "TEXT b(SB),$0-0\n")
549 fmt.Fprintf(buf, "\tRET\n")
550 fmt.Fprintf(buf, "TEXT c(SB),$0-0\n")
551 fmt.Fprintf(buf, "\tCALL b(SB)\n")
552 fmt.Fprintf(buf, "\tRET\n")
553 fmt.Fprintf(buf, "TEXT ·d(SB),0,$0-0\n")
554 for i := 0; i < 1<<17; i++ {
555 fmt.Fprintf(buf, "\tADD $0, X5, X0\n")
556 }
557 fmt.Fprintf(buf, "\tCALL a(SB)\n")
558 fmt.Fprintf(buf, "\tCALL c(SB)\n")
559 fmt.Fprintf(buf, "\tRET\n")
560 if err := os.WriteFile(tmpFile, buf.Bytes(), 0644); err != nil {
561 t.Fatalf("Failed to write assembly file: %v", err)
562 }
563
564 if err := os.WriteFile(filepath.Join(tmpDir, "go.mod"), []byte("module riscvtramp"), 0644); err != nil {
565 t.Fatalf("Failed to write file: %v\n", err)
566 }
567 main := `package main
568 func main() {
569 d()
570 }
571
572 func d()
573 `
574 if err := os.WriteFile(filepath.Join(tmpDir, "x.go"), []byte(main), 0644); err != nil {
575 t.Fatalf("failed to write main: %v\n", err)
576 }
577 cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-linkmode=internal")
578 cmd.Dir = tmpDir
579 cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
580 out, err := cmd.CombinedOutput()
581 if err != nil {
582 t.Fatalf("Build failed: %v, output: %s", err, out)
583 }
584
585
586 cmd = testenv.Command(t, testenv.GoToolPath(t), "tool", "nm", filepath.Join(tmpDir, "riscvtramp"))
587 cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
588 out, err = cmd.CombinedOutput()
589 if err != nil {
590 t.Fatalf("nm failure: %s\n%s\n", err, string(out))
591 }
592 if !bytes.Contains(out, []byte(" T a-tramp0")) {
593 t.Errorf("Trampoline a-tramp0 is missing")
594 }
595 if bytes.Contains(out, []byte(" T b-tramp0")) {
596 t.Errorf("Trampoline b-tramp0 exists unnecessarily")
597 }
598 }
599
600 func TestRounding(t *testing.T) {
601 testCases := []struct {
602 input int64
603 quantum int64
604 expected int64
605 }{
606 {0x30000000, 0x2000, 0x30000000},
607 {0x30002000, 0x2000, 0x30002000},
608 {0x30001234, 0x2000, 0x30002000},
609 {0x30001000, 0x2000, 0x30002000},
610 {0x30001fff, 0x2000, 0x30002000},
611 }
612
613 for _, tc := range testCases {
614 result := Rnd(tc.input, tc.quantum)
615 if result != tc.expected {
616 t.Errorf("Rnd(0x%x, 0x%x) = 0x%x, expected 0x%x",
617 tc.input, tc.quantum, result, tc.expected)
618 }
619 }
620 }
621
View as plain text