1 // Copyright 2016 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 5 package carchive_test 6 7 import ( 8 "bufio" 9 "debug/elf" 10 "fmt" 11 "io/ioutil" 12 "os" 13 "os/exec" 14 "path/filepath" 15 "strings" 16 "syscall" 17 "testing" 18 "time" 19 "unicode" 20 ) 21 22 // Program to run. 23 var bin []string 24 25 // C compiler with args (from $(go env CC) $(go env GOGCCFLAGS)). 26 var cc []string 27 28 // An environment with GOPATH=$(pwd). 29 var gopathEnv []string 30 31 // ".exe" on Windows. 32 var exeSuffix string 33 34 var GOOS, GOARCH string 35 var libgodir string 36 37 func init() { 38 GOOS = goEnv("GOOS") 39 GOARCH = goEnv("GOARCH") 40 bin = cmdToRun("./testp") 41 42 ccOut := goEnv("CC") 43 cc = []string{string(ccOut)} 44 45 out := goEnv("GOGCCFLAGS") 46 quote := '\000' 47 start := 0 48 lastSpace := true 49 backslash := false 50 s := string(out) 51 for i, c := range s { 52 if quote == '\000' && unicode.IsSpace(c) { 53 if !lastSpace { 54 cc = append(cc, s[start:i]) 55 lastSpace = true 56 } 57 } else { 58 if lastSpace { 59 start = i 60 lastSpace = false 61 } 62 if quote == '\000' && !backslash && (c == '"' || c == '\'') { 63 quote = c 64 backslash = false 65 } else if !backslash && quote == c { 66 quote = '\000' 67 } else if (quote == '\000' || quote == '"') && !backslash && c == '\\' { 68 backslash = true 69 } else { 70 backslash = false 71 } 72 } 73 } 74 if !lastSpace { 75 cc = append(cc, s[start:]) 76 } 77 78 if GOOS == "darwin" { 79 // For Darwin/ARM. 80 // TODO(crawshaw): can we do better? 81 cc = append(cc, []string{"-framework", "CoreFoundation", "-framework", "Foundation"}...) 82 } 83 libgodir = GOOS + "_" + GOARCH 84 switch GOOS { 85 case "darwin": 86 if GOARCH == "arm" || GOARCH == "arm64" { 87 libgodir += "_shared" 88 } 89 case "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris": 90 libgodir += "_shared" 91 } 92 cc = append(cc, "-I", filepath.Join("pkg", libgodir)) 93 94 // Build an environment with GOPATH=$(pwd) 95 env := os.Environ() 96 var n []string 97 for _, e := range env { 98 if !strings.HasPrefix(e, "GOPATH=") { 99 n = append(n, e) 100 } 101 } 102 dir, err := os.Getwd() 103 if err != nil { 104 fmt.Fprintln(os.Stderr, err) 105 os.Exit(2) 106 } 107 n = append(n, "GOPATH="+dir) 108 gopathEnv = n 109 110 if GOOS == "windows" { 111 exeSuffix = ".exe" 112 } 113 } 114 115 func goEnv(key string) string { 116 out, err := exec.Command("go", "env", key).Output() 117 if err != nil { 118 fmt.Fprintf(os.Stderr, "go env %s failed:\n%s", key, err) 119 fmt.Fprintf(os.Stderr, "%s", err.(*exec.ExitError).Stderr) 120 os.Exit(2) 121 } 122 return strings.TrimSpace(string(out)) 123 } 124 125 func cmdToRun(name string) []string { 126 execScript := "go_" + goEnv("GOOS") + "_" + goEnv("GOARCH") + "_exec" 127 executor, err := exec.LookPath(execScript) 128 if err != nil { 129 return []string{name} 130 } 131 return []string{executor, name} 132 } 133 134 func testInstall(t *testing.T, exe, libgoa, libgoh string, buildcmd ...string) { 135 cmd := exec.Command(buildcmd[0], buildcmd[1:]...) 136 cmd.Env = gopathEnv 137 if out, err := cmd.CombinedOutput(); err != nil { 138 t.Logf("%s", out) 139 t.Fatal(err) 140 } 141 defer func() { 142 os.Remove(libgoa) 143 os.Remove(libgoh) 144 }() 145 146 ccArgs := append(cc, "-o", exe, "main.c") 147 if GOOS == "windows" { 148 ccArgs = append(ccArgs, "main_windows.c", libgoa, "-lntdll", "-lws2_32", "-lwinmm") 149 } else { 150 ccArgs = append(ccArgs, "main_unix.c", libgoa) 151 } 152 t.Log(ccArgs) 153 if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil { 154 t.Logf("%s", out) 155 t.Fatal(err) 156 } 157 defer os.Remove(exe) 158 159 binArgs := append(cmdToRun(exe), "arg1", "arg2") 160 if out, err := exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput(); err != nil { 161 t.Logf("%s", out) 162 t.Fatal(err) 163 } 164 } 165 166 func TestInstall(t *testing.T) { 167 defer os.RemoveAll("pkg") 168 169 testInstall(t, "./testp1"+exeSuffix, 170 filepath.Join("pkg", libgodir, "libgo.a"), 171 filepath.Join("pkg", libgodir, "libgo.h"), 172 "go", "install", "-buildmode=c-archive", "libgo") 173 174 // Test building libgo other than installing it. 175 // Header files are now present. 176 testInstall(t, "./testp2"+exeSuffix, "libgo.a", "libgo.h", 177 "go", "build", "-buildmode=c-archive", filepath.Join("src", "libgo", "libgo.go")) 178 179 testInstall(t, "./testp3"+exeSuffix, "libgo.a", "libgo.h", 180 "go", "build", "-buildmode=c-archive", "-o", "libgo.a", "libgo") 181 } 182 183 func TestEarlySignalHandler(t *testing.T) { 184 switch GOOS { 185 case "darwin": 186 switch GOARCH { 187 case "arm", "arm64": 188 t.Skipf("skipping on %s/%s; see https://golang.org/issue/13701", GOOS, GOARCH) 189 } 190 case "windows": 191 t.Skip("skipping signal test on Windows") 192 } 193 194 defer func() { 195 os.Remove("libgo2.a") 196 os.Remove("libgo2.h") 197 os.Remove("testp") 198 os.RemoveAll("pkg") 199 }() 200 201 cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "libgo2") 202 cmd.Env = gopathEnv 203 if out, err := cmd.CombinedOutput(); err != nil { 204 t.Logf("%s", out) 205 t.Fatal(err) 206 } 207 208 ccArgs := append(cc, "-o", "testp"+exeSuffix, "main2.c", "libgo2.a") 209 if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil { 210 t.Logf("%s", out) 211 t.Fatal(err) 212 } 213 214 if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil { 215 t.Logf("%s", out) 216 t.Fatal(err) 217 } 218 } 219 220 func TestSignalForwarding(t *testing.T) { 221 switch GOOS { 222 case "darwin": 223 switch GOARCH { 224 case "arm", "arm64": 225 t.Skipf("skipping on %s/%s; see https://golang.org/issue/13701", GOOS, GOARCH) 226 } 227 case "windows": 228 t.Skip("skipping signal test on Windows") 229 } 230 231 defer func() { 232 os.Remove("libgo2.a") 233 os.Remove("libgo2.h") 234 os.Remove("testp") 235 os.RemoveAll("pkg") 236 }() 237 238 cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "libgo2") 239 cmd.Env = gopathEnv 240 if out, err := cmd.CombinedOutput(); err != nil { 241 t.Logf("%s", out) 242 t.Fatal(err) 243 } 244 245 ccArgs := append(cc, "-o", "testp"+exeSuffix, "main5.c", "libgo2.a") 246 if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil { 247 t.Logf("%s", out) 248 t.Fatal(err) 249 } 250 251 cmd = exec.Command(bin[0], append(bin[1:], "1")...) 252 253 out, err := cmd.CombinedOutput() 254 255 if err == nil { 256 t.Logf("%s", out) 257 t.Error("test program succeeded unexpectedly") 258 } else if ee, ok := err.(*exec.ExitError); !ok { 259 t.Logf("%s", out) 260 t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err) 261 } else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok { 262 t.Logf("%s", out) 263 t.Errorf("error.Sys (%v) has type %T; expected syscall.WaitStatus", ee.Sys(), ee.Sys()) 264 } else if !ws.Signaled() || ws.Signal() != syscall.SIGSEGV { 265 t.Logf("%s", out) 266 t.Errorf("got %v; expected SIGSEGV", ee) 267 } 268 } 269 270 func TestSignalForwardingExternal(t *testing.T) { 271 switch GOOS { 272 case "darwin": 273 switch GOARCH { 274 case "arm", "arm64": 275 t.Skipf("skipping on %s/%s; see https://golang.org/issue/13701", GOOS, GOARCH) 276 } 277 case "windows": 278 t.Skip("skipping signal test on Windows") 279 } 280 281 defer func() { 282 os.Remove("libgo2.a") 283 os.Remove("libgo2.h") 284 os.Remove("testp") 285 os.RemoveAll("pkg") 286 }() 287 288 cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "libgo2") 289 cmd.Env = gopathEnv 290 if out, err := cmd.CombinedOutput(); err != nil { 291 t.Logf("%s", out) 292 t.Fatal(err) 293 } 294 295 ccArgs := append(cc, "-o", "testp"+exeSuffix, "main5.c", "libgo2.a") 296 if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil { 297 t.Logf("%s", out) 298 t.Fatal(err) 299 } 300 301 // We want to send the process a signal and see if it dies. 302 // Normally the signal goes to the C thread, the Go signal 303 // handler picks it up, sees that it is running in a C thread, 304 // and the program dies. Unfortunately, occasionally the 305 // signal is delivered to a Go thread, which winds up 306 // discarding it because it was sent by another program and 307 // there is no Go handler for it. To avoid this, run the 308 // program several times in the hopes that it will eventually 309 // fail. 310 const tries = 20 311 for i := 0; i < tries; i++ { 312 cmd = exec.Command(bin[0], append(bin[1:], "2")...) 313 314 stderr, err := cmd.StderrPipe() 315 if err != nil { 316 t.Fatal(err) 317 } 318 defer stderr.Close() 319 320 r := bufio.NewReader(stderr) 321 322 err = cmd.Start() 323 324 if err != nil { 325 t.Fatal(err) 326 } 327 328 // Wait for trigger to ensure that the process is started. 329 ok, err := r.ReadString('\n') 330 331 // Verify trigger. 332 if err != nil || ok != "OK\n" { 333 t.Fatalf("Did not receive OK signal") 334 } 335 336 // Give the program a chance to enter the sleep function. 337 time.Sleep(time.Millisecond) 338 339 cmd.Process.Signal(syscall.SIGSEGV) 340 341 err = cmd.Wait() 342 343 if err == nil { 344 continue 345 } 346 347 if ee, ok := err.(*exec.ExitError); !ok { 348 t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err) 349 } else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok { 350 t.Errorf("error.Sys (%v) has type %T; expected syscall.WaitStatus", ee.Sys(), ee.Sys()) 351 } else if !ws.Signaled() || ws.Signal() != syscall.SIGSEGV { 352 t.Errorf("got %v; expected SIGSEGV", ee) 353 } else { 354 // We got the error we expected. 355 return 356 } 357 } 358 359 t.Errorf("program succeeded unexpectedly %d times", tries) 360 } 361 362 func TestOsSignal(t *testing.T) { 363 switch GOOS { 364 case "windows": 365 t.Skip("skipping signal test on Windows") 366 } 367 368 defer func() { 369 os.Remove("libgo3.a") 370 os.Remove("libgo3.h") 371 os.Remove("testp") 372 os.RemoveAll("pkg") 373 }() 374 375 cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo3.a", "libgo3") 376 cmd.Env = gopathEnv 377 if out, err := cmd.CombinedOutput(); err != nil { 378 t.Logf("%s", out) 379 t.Fatal(err) 380 } 381 382 ccArgs := append(cc, "-o", "testp"+exeSuffix, "main3.c", "libgo3.a") 383 if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil { 384 t.Logf("%s", out) 385 t.Fatal(err) 386 } 387 388 if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil { 389 t.Logf("%s", out) 390 t.Fatal(err) 391 } 392 } 393 394 func TestSigaltstack(t *testing.T) { 395 switch GOOS { 396 case "windows": 397 t.Skip("skipping signal test on Windows") 398 } 399 400 defer func() { 401 os.Remove("libgo4.a") 402 os.Remove("libgo4.h") 403 os.Remove("testp") 404 os.RemoveAll("pkg") 405 }() 406 407 cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo4.a", "libgo4") 408 cmd.Env = gopathEnv 409 if out, err := cmd.CombinedOutput(); err != nil { 410 t.Logf("%s", out) 411 t.Fatal(err) 412 } 413 414 ccArgs := append(cc, "-o", "testp"+exeSuffix, "main4.c", "libgo4.a") 415 if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil { 416 t.Logf("%s", out) 417 t.Fatal(err) 418 } 419 420 if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil { 421 t.Logf("%s", out) 422 t.Fatal(err) 423 } 424 } 425 426 const testar = `#!/usr/bin/env bash 427 while expr $1 : '[-]' >/dev/null; do 428 shift 429 done 430 echo "testar" > $1 431 echo "testar" > PWD/testar.ran 432 ` 433 434 func TestExtar(t *testing.T) { 435 switch GOOS { 436 case "windows": 437 t.Skip("skipping signal test on Windows") 438 } 439 440 defer func() { 441 os.Remove("libgo4.a") 442 os.Remove("libgo4.h") 443 os.Remove("testar") 444 os.Remove("testar.ran") 445 os.RemoveAll("pkg") 446 }() 447 448 os.Remove("testar") 449 dir, err := os.Getwd() 450 if err != nil { 451 t.Fatal(err) 452 } 453 s := strings.Replace(testar, "PWD", dir, 1) 454 if err := ioutil.WriteFile("testar", []byte(s), 0777); err != nil { 455 t.Fatal(err) 456 } 457 458 cmd := exec.Command("go", "build", "-buildmode=c-archive", "-ldflags=-extar="+filepath.Join(dir, "testar"), "-o", "libgo4.a", "libgo4") 459 cmd.Env = gopathEnv 460 if out, err := cmd.CombinedOutput(); err != nil { 461 t.Logf("%s", out) 462 t.Fatal(err) 463 } 464 465 if _, err := os.Stat("testar.ran"); err != nil { 466 if os.IsNotExist(err) { 467 t.Error("testar does not exist after go build") 468 } else { 469 t.Errorf("error checking testar: %v", err) 470 } 471 } 472 } 473 474 func TestPIE(t *testing.T) { 475 switch GOOS { 476 case "windows", "darwin", "plan9": 477 t.Skipf("skipping PIE test on %s", GOOS) 478 } 479 480 defer func() { 481 os.Remove("testp" + exeSuffix) 482 os.RemoveAll("pkg") 483 }() 484 485 cmd := exec.Command("go", "install", "-buildmode=c-archive", "libgo") 486 cmd.Env = gopathEnv 487 if out, err := cmd.CombinedOutput(); err != nil { 488 t.Logf("%s", out) 489 t.Fatal(err) 490 } 491 492 ccArgs := append(cc, "-fPIE", "-pie", "-o", "testp"+exeSuffix, "main.c", "main_unix.c", filepath.Join("pkg", libgodir, "libgo.a")) 493 if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil { 494 t.Logf("%s", out) 495 t.Fatal(err) 496 } 497 498 binArgs := append(bin, "arg1", "arg2") 499 if out, err := exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput(); err != nil { 500 t.Logf("%s", out) 501 t.Fatal(err) 502 } 503 504 f, err := elf.Open("testp" + exeSuffix) 505 if err != nil { 506 t.Fatal("elf.Open failed: ", err) 507 } 508 defer f.Close() 509 if hasDynTag(t, f, elf.DT_TEXTREL) { 510 t.Errorf("%s has DT_TEXTREL flag", "testp"+exeSuffix) 511 } 512 } 513 514 func hasDynTag(t *testing.T, f *elf.File, tag elf.DynTag) bool { 515 ds := f.SectionByType(elf.SHT_DYNAMIC) 516 if ds == nil { 517 t.Error("no SHT_DYNAMIC section") 518 return false 519 } 520 d, err := ds.Data() 521 if err != nil { 522 t.Errorf("can't read SHT_DYNAMIC contents: %v", err) 523 return false 524 } 525 for len(d) > 0 { 526 var t elf.DynTag 527 switch f.Class { 528 case elf.ELFCLASS32: 529 t = elf.DynTag(f.ByteOrder.Uint32(d[:4])) 530 d = d[8:] 531 case elf.ELFCLASS64: 532 t = elf.DynTag(f.ByteOrder.Uint64(d[:8])) 533 d = d[16:] 534 } 535 if t == tag { 536 return true 537 } 538 } 539 return false 540 } 541