1// Copyright 2018 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 5package ld 6 7import ( 8 "bytes" 9 "debug/pe" 10 "fmt" 11 "internal/testenv" 12 "os" 13 "path/filepath" 14 "runtime" 15 "strings" 16 "testing" 17) 18 19func TestUndefinedRelocErrors(t *testing.T) { 20 testenv.MustHaveGoBuild(t) 21 22 // When external linking, symbols may be defined externally, so we allow 23 // undefined symbols and let external linker resolve. Skip the test. 24 testenv.MustInternalLink(t, false) 25 26 t.Parallel() 27 28 out, err := testenv.Command(t, testenv.GoToolPath(t), "build", "./testdata/issue10978").CombinedOutput() 29 if err == nil { 30 t.Fatal("expected build to fail") 31 } 32 33 wantErrors := map[string]int{ 34 // Main function has dedicated error message. 35 "function main is undeclared in the main package": 1, 36 37 // Single error reporting per each symbol. 38 // This way, duplicated messages are not reported for 39 // multiple relocations with a same name. 40 "main.defined1: relocation target main.undefined not defined": 1, 41 "main.defined2: relocation target main.undefined not defined": 1, 42 } 43 unexpectedErrors := map[string]int{} 44 45 for _, l := range strings.Split(string(out), "\n") { 46 if strings.HasPrefix(l, "#") || l == "" { 47 continue 48 } 49 matched := "" 50 for want := range wantErrors { 51 if strings.Contains(l, want) { 52 matched = want 53 break 54 } 55 } 56 if matched != "" { 57 wantErrors[matched]-- 58 } else { 59 unexpectedErrors[l]++ 60 } 61 } 62 63 for want, n := range wantErrors { 64 switch { 65 case n > 0: 66 t.Errorf("unmatched error: %s (x%d)", want, n) 67 case n < 0: 68 if runtime.GOOS == "android" && runtime.GOARCH == "arm64" { 69 testenv.SkipFlaky(t, 58807) 70 } 71 t.Errorf("extra errors: %s (x%d)", want, -n) 72 } 73 } 74 for unexpected, n := range unexpectedErrors { 75 t.Errorf("unexpected error: %s (x%d)", unexpected, n) 76 } 77} 78 79const carchiveSrcText = ` 80package main 81 82//export GoFunc 83func GoFunc() { 84 println(42) 85} 86 87func main() { 88} 89` 90 91func TestArchiveBuildInvokeWithExec(t *testing.T) { 92 t.Parallel() 93 testenv.MustHaveGoBuild(t) 94 testenv.MustHaveCGO(t) 95 96 // run this test on just a small set of platforms (no need to test it 97 // across the board given the nature of the test). 98 pair := runtime.GOOS + "-" + runtime.GOARCH 99 switch pair { 100 case "darwin-amd64", "darwin-arm64", "linux-amd64", "freebsd-amd64": 101 default: 102 t.Skip("no need for test on " + pair) 103 } 104 switch runtime.GOOS { 105 case "openbsd", "windows": 106 t.Skip("c-archive unsupported") 107 } 108 dir := t.TempDir() 109 110 srcfile := filepath.Join(dir, "test.go") 111 arfile := filepath.Join(dir, "test.a") 112 if err := os.WriteFile(srcfile, []byte(carchiveSrcText), 0666); err != nil { 113 t.Fatal(err) 114 } 115 116 ldf := fmt.Sprintf("-ldflags=-v -tmpdir=%s", dir) 117 argv := []string{"build", "-buildmode=c-archive", "-o", arfile, ldf, srcfile} 118 out, err := testenv.Command(t, testenv.GoToolPath(t), argv...).CombinedOutput() 119 if err != nil { 120 t.Fatalf("build failure: %s\n%s\n", err, string(out)) 121 } 122 123 found := false 124 const want = "invoking archiver with syscall.Exec" 125 for _, l := range strings.Split(string(out), "\n") { 126 if strings.HasPrefix(l, want) { 127 found = true 128 break 129 } 130 } 131 132 if !found { 133 t.Errorf("expected '%s' in -v output, got:\n%s\n", want, string(out)) 134 } 135} 136 137func TestLargeTextSectionSplitting(t *testing.T) { 138 switch runtime.GOARCH { 139 case "ppc64", "ppc64le", "arm": 140 case "arm64": 141 if runtime.GOOS == "darwin" { 142 break 143 } 144 fallthrough 145 default: 146 t.Skipf("text section splitting is not done in %s/%s", runtime.GOOS, runtime.GOARCH) 147 } 148 149 testenv.MustHaveGoBuild(t) 150 testenv.MustHaveCGO(t) 151 t.Parallel() 152 dir := t.TempDir() 153 154 // NB: the use of -ldflags=-debugtextsize=1048576 tells the linker to 155 // split text sections at a size threshold of 1M instead of the 156 // architected limit of 67M or larger. The choice of building cmd/go 157 // is arbitrary; we just need something sufficiently large that uses 158 // external linking. 159 exe := filepath.Join(dir, "go.exe") 160 out, err := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe, "-ldflags=-linkmode=external -debugtextsize=1048576", "cmd/go").CombinedOutput() 161 if err != nil { 162 t.Fatalf("build failure: %s\n%s\n", err, string(out)) 163 } 164 165 // Check that we did split text sections. 166 out, err = testenv.Command(t, testenv.GoToolPath(t), "tool", "nm", exe).CombinedOutput() 167 if err != nil { 168 t.Fatalf("nm failure: %s\n%s\n", err, string(out)) 169 } 170 if !bytes.Contains(out, []byte("runtime.text.1")) { 171 t.Errorf("runtime.text.1 not found, text section not split?") 172 } 173 174 // Result should be runnable. 175 _, err = testenv.Command(t, exe, "version").CombinedOutput() 176 if err != nil { 177 t.Fatal(err) 178 } 179} 180 181func TestWindowsBuildmodeCSharedASLR(t *testing.T) { 182 platform := fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH) 183 switch platform { 184 case "windows/amd64", "windows/386": 185 default: 186 t.Skip("skipping windows amd64/386 only test") 187 } 188 189 testenv.MustHaveCGO(t) 190 191 t.Run("aslr", func(t *testing.T) { 192 testWindowsBuildmodeCSharedASLR(t, true) 193 }) 194 t.Run("no-aslr", func(t *testing.T) { 195 testWindowsBuildmodeCSharedASLR(t, false) 196 }) 197} 198 199func testWindowsBuildmodeCSharedASLR(t *testing.T, useASLR bool) { 200 t.Parallel() 201 testenv.MustHaveGoBuild(t) 202 203 dir := t.TempDir() 204 205 srcfile := filepath.Join(dir, "test.go") 206 objfile := filepath.Join(dir, "test.dll") 207 if err := os.WriteFile(srcfile, []byte(`package main; func main() { print("hello") }`), 0666); err != nil { 208 t.Fatal(err) 209 } 210 argv := []string{"build", "-buildmode=c-shared"} 211 if !useASLR { 212 argv = append(argv, "-ldflags", "-aslr=false") 213 } 214 argv = append(argv, "-o", objfile, srcfile) 215 out, err := testenv.Command(t, testenv.GoToolPath(t), argv...).CombinedOutput() 216 if err != nil { 217 t.Fatalf("build failure: %s\n%s\n", err, string(out)) 218 } 219 220 f, err := pe.Open(objfile) 221 if err != nil { 222 t.Fatal(err) 223 } 224 defer f.Close() 225 var dc uint16 226 switch oh := f.OptionalHeader.(type) { 227 case *pe.OptionalHeader32: 228 dc = oh.DllCharacteristics 229 case *pe.OptionalHeader64: 230 dc = oh.DllCharacteristics 231 hasHEVA := (dc & pe.IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA) != 0 232 if useASLR && !hasHEVA { 233 t.Error("IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA flag is not set") 234 } else if !useASLR && hasHEVA { 235 t.Error("IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA flag should not be set") 236 } 237 default: 238 t.Fatalf("unexpected optional header type of %T", f.OptionalHeader) 239 } 240 hasASLR := (dc & pe.IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) != 0 241 if useASLR && !hasASLR { 242 t.Error("IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE flag is not set") 243 } else if !useASLR && hasASLR { 244 t.Error("IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE flag should not be set") 245 } 246} 247 248// TestMemProfileCheck tests that cmd/link sets 249// runtime.disableMemoryProfiling if the runtime.MemProfile 250// symbol is unreachable after deadcode (and not dynlinking). 251// The runtime then uses that to set the default value of 252// runtime.MemProfileRate, which this test checks. 253func TestMemProfileCheck(t *testing.T) { 254 testenv.MustHaveGoBuild(t) 255 t.Parallel() 256 257 tests := []struct { 258 name string 259 prog string 260 wantOut string 261 }{ 262 { 263 "no_memprofile", 264 ` 265package main 266import "runtime" 267func main() { 268 println(runtime.MemProfileRate) 269} 270`, 271 "0", 272 }, 273 { 274 "with_memprofile", 275 ` 276package main 277import "runtime" 278func main() { 279 runtime.MemProfile(nil, false) 280 println(runtime.MemProfileRate) 281} 282`, 283 "524288", 284 }, 285 { 286 "with_memprofile_indirect", 287 ` 288package main 289import "runtime" 290var f = runtime.MemProfile 291func main() { 292 if f == nil { 293 panic("no f") 294 } 295 println(runtime.MemProfileRate) 296} 297`, 298 "524288", 299 }, 300 { 301 "with_memprofile_runtime_pprof", 302 ` 303package main 304import "runtime" 305import "runtime/pprof" 306func main() { 307 _ = pprof.Profiles() 308 println(runtime.MemProfileRate) 309} 310`, 311 "524288", 312 }, 313 { 314 "with_memprofile_runtime_pprof_writeheap", 315 ` 316package main 317import "io" 318import "runtime" 319import "runtime/pprof" 320func main() { 321 _ = pprof.WriteHeapProfile(io.Discard) 322 println(runtime.MemProfileRate) 323} 324`, 325 "524288", 326 }, 327 { 328 "with_memprofile_runtime_pprof_lookupheap", 329 ` 330package main 331import "runtime" 332import "runtime/pprof" 333func main() { 334 _ = pprof.Lookup("heap") 335 println(runtime.MemProfileRate) 336} 337`, 338 "524288", 339 }, 340 { 341 "with_memprofile_http_pprof", 342 ` 343package main 344import "runtime" 345import _ "net/http/pprof" 346func main() { 347 println(runtime.MemProfileRate) 348} 349`, 350 "524288", 351 }, 352 } 353 for _, tt := range tests { 354 tt := tt 355 t.Run(tt.name, func(t *testing.T) { 356 t.Parallel() 357 tempDir := t.TempDir() 358 src := filepath.Join(tempDir, "x.go") 359 if err := os.WriteFile(src, []byte(tt.prog), 0644); err != nil { 360 t.Fatal(err) 361 } 362 cmd := testenv.Command(t, testenv.GoToolPath(t), "run", src) 363 out, err := cmd.CombinedOutput() 364 if err != nil { 365 t.Fatal(err) 366 } 367 got := strings.TrimSpace(string(out)) 368 if got != tt.wantOut { 369 t.Errorf("got %q; want %q", got, tt.wantOut) 370 } 371 }) 372 } 373} 374 375func TestRISCVTrampolines(t *testing.T) { 376 testenv.MustHaveGoBuild(t) 377 t.Parallel() 378 379 tmpDir := t.TempDir() 380 tmpFile := filepath.Join(tmpDir, "x.s") 381 382 // Calling b from a or c should not use trampolines, however 383 // calling from d to a will require one. 384 buf := new(bytes.Buffer) 385 fmt.Fprintf(buf, "TEXT a(SB),$0-0\n") 386 for i := 0; i < 1<<17; i++ { 387 fmt.Fprintf(buf, "\tADD $0, X0, X0\n") 388 } 389 fmt.Fprintf(buf, "\tCALL b(SB)\n") 390 fmt.Fprintf(buf, "\tRET\n") 391 fmt.Fprintf(buf, "TEXT b(SB),$0-0\n") 392 fmt.Fprintf(buf, "\tRET\n") 393 fmt.Fprintf(buf, "TEXT c(SB),$0-0\n") 394 fmt.Fprintf(buf, "\tCALL b(SB)\n") 395 fmt.Fprintf(buf, "\tRET\n") 396 fmt.Fprintf(buf, "TEXT ·d(SB),0,$0-0\n") 397 for i := 0; i < 1<<17; i++ { 398 fmt.Fprintf(buf, "\tADD $0, X0, X0\n") 399 } 400 fmt.Fprintf(buf, "\tCALL a(SB)\n") 401 fmt.Fprintf(buf, "\tCALL c(SB)\n") 402 fmt.Fprintf(buf, "\tRET\n") 403 if err := os.WriteFile(tmpFile, buf.Bytes(), 0644); err != nil { 404 t.Fatalf("Failed to write assembly file: %v", err) 405 } 406 407 if err := os.WriteFile(filepath.Join(tmpDir, "go.mod"), []byte("module riscvtramp"), 0644); err != nil { 408 t.Fatalf("Failed to write file: %v\n", err) 409 } 410 main := `package main 411func main() { 412 d() 413} 414 415func d() 416` 417 if err := os.WriteFile(filepath.Join(tmpDir, "x.go"), []byte(main), 0644); err != nil { 418 t.Fatalf("failed to write main: %v\n", err) 419 } 420 cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-linkmode=internal") 421 cmd.Dir = tmpDir 422 cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux") 423 out, err := cmd.CombinedOutput() 424 if err != nil { 425 t.Fatalf("Build failed: %v, output: %s", err, out) 426 } 427 428 // Check what trampolines exist. 429 cmd = testenv.Command(t, testenv.GoToolPath(t), "tool", "nm", filepath.Join(tmpDir, "riscvtramp")) 430 cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux") 431 out, err = cmd.CombinedOutput() 432 if err != nil { 433 t.Fatalf("nm failure: %s\n%s\n", err, string(out)) 434 } 435 if !bytes.Contains(out, []byte(" T a-tramp0")) { 436 t.Errorf("Trampoline a-tramp0 is missing") 437 } 438 if bytes.Contains(out, []byte(" T b-tramp0")) { 439 t.Errorf("Trampoline b-tramp0 exists unnecessarily") 440 } 441} 442