Home | History | Annotate | Download | only in testcarchive
      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