Commit 8396015e authored by Russ Cox's avatar Russ Cox

cmd/link: set runtime.GOROOT default during link

Suppose you build the Go toolchain in directory A,
move the whole thing to directory B, and then use
it from B to build a new program hello.exe, and then
run hello.exe, and hello.exe crashes with a stack
trace into the standard library.

Long ago, you'd have seen hello.exe print file names
in the A directory tree, even though the files had moved
to the B directory tree. About two years ago we changed
the compiler to write down these files with the name
"$GOROOT" (that literal string) instead of A, so that the
final link from B could replace "$GOROOT" with B,
so that hello.exe's crash would show the correct source
file paths in the stack trace. (golang.org/cl/18200)

Now suppose that you do the same thing but hello.exe
doesn't crash: it prints fmt.Println(runtime.GOROOT()).
And you run hello.exe after clearing $GOROOT from the
environment.

Long ago, you'd have seen hello.exe print A instead of B.
Before this CL, you'd still see hello.exe print A instead of B.
This case is the one instance where a moved toolchain
still divulges its origin. Not anymore. After this CL, hello.exe
will print B, because the linker sets runtime/internal/sys.DefaultGoroot
with the effective GOROOT from link time.
This makes the default result of runtime.GOROOT once again
match the file names recorded in the binary, after two years
of divergence.

With that cleared up, we can reintroduce GOROOT into the
link action ID and also reenable TestExecutableGOROOT/RelocatedExe.

When $GOROOT_FINAL is set during link, it is used
in preference to $GOROOT, as always, but it was easier
to explain the behavior above without introducing that
complication.

Fixes #22155.
Fixes #20284.
Fixes #22475.

Change-Id: Ifdaeb77fd4678fdb337cf59ee25b2cd873ec1016
Reviewed-on: https://go-review.googlesource.com/86835
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: 's avatarIan Lance Taylor <iant@golang.org>
parent 28639df1
...@@ -18,7 +18,6 @@ import ( ...@@ -18,7 +18,6 @@ import (
// mkzversion writes zversion.go: // mkzversion writes zversion.go:
// //
// package sys // package sys
// var DefaultGoroot = <goroot>
// //
// const TheVersion = <version> // const TheVersion = <version>
// const Goexperiment = <goexperiment> // const Goexperiment = <goexperiment>
...@@ -30,8 +29,6 @@ func mkzversion(dir, file string) { ...@@ -30,8 +29,6 @@ func mkzversion(dir, file string) {
fmt.Fprintln(&buf) fmt.Fprintln(&buf)
fmt.Fprintf(&buf, "package sys\n") fmt.Fprintf(&buf, "package sys\n")
fmt.Fprintln(&buf) fmt.Fprintln(&buf)
fmt.Fprintf(&buf, "var DefaultGoroot = `%s`\n", goroot_final)
fmt.Fprintln(&buf)
fmt.Fprintf(&buf, "const TheVersion = `%s`\n", findgoversion()) fmt.Fprintf(&buf, "const TheVersion = `%s`\n", findgoversion())
fmt.Fprintf(&buf, "const Goexperiment = `%s`\n", os.Getenv("GOEXPERIMENT")) fmt.Fprintf(&buf, "const Goexperiment = `%s`\n", os.Getenv("GOEXPERIMENT"))
fmt.Fprintf(&buf, "const StackGuardMultiplier = %d\n", stackGuardMultiplier()) fmt.Fprintf(&buf, "const StackGuardMultiplier = %d\n", stackGuardMultiplier())
...@@ -71,7 +68,6 @@ func mkzbootstrap(file string) { ...@@ -71,7 +68,6 @@ func mkzbootstrap(file string) {
fmt.Fprintln(&buf) fmt.Fprintln(&buf)
fmt.Fprintf(&buf, "import \"runtime\"\n") fmt.Fprintf(&buf, "import \"runtime\"\n")
fmt.Fprintln(&buf) fmt.Fprintln(&buf)
fmt.Fprintf(&buf, "const defaultGOROOT = `%s`\n", goroot_final)
fmt.Fprintf(&buf, "const defaultGO386 = `%s`\n", go386) fmt.Fprintf(&buf, "const defaultGO386 = `%s`\n", go386)
fmt.Fprintf(&buf, "const defaultGOARM = `%s`\n", goarm) fmt.Fprintf(&buf, "const defaultGOARM = `%s`\n", goarm)
fmt.Fprintf(&buf, "const defaultGOMIPS = `%s`\n", gomips) fmt.Fprintf(&buf, "const defaultGOMIPS = `%s`\n", gomips)
......
...@@ -172,9 +172,13 @@ func (t *tester) run() { ...@@ -172,9 +172,13 @@ func (t *tester) run() {
return return
} }
// we must unset GOROOT_FINAL before tests, because runtime/debug requires // We must unset GOROOT_FINAL before tests, because runtime/debug requires
// correct access to source code, so if we have GOROOT_FINAL in effect, // correct access to source code, so if we have GOROOT_FINAL in effect,
// at least runtime/debug test will fail. // at least runtime/debug test will fail.
// If GOROOT_FINAL was set before, then now all the commands will appear stale.
// Nothing we can do about that other than not checking them below.
// (We call checkNotStale but only with "std" not "cmd".)
os.Setenv("GOROOT_FINAL_OLD", os.Getenv("GOROOT_FINAL")) // for cmd/link test
os.Unsetenv("GOROOT_FINAL") os.Unsetenv("GOROOT_FINAL")
for _, name := range t.runNames { for _, name := range t.runNames {
...@@ -1044,7 +1048,7 @@ func (t *tester) cgoTest(dt *distTest) error { ...@@ -1044,7 +1048,7 @@ func (t *tester) cgoTest(dt *distTest) error {
// running in parallel with earlier tests, or if it has some other reason // running in parallel with earlier tests, or if it has some other reason
// for needing the earlier tests to be done. // for needing the earlier tests to be done.
func (t *tester) runPending(nextTest *distTest) { func (t *tester) runPending(nextTest *distTest) {
checkNotStale("go", "std", "cmd") checkNotStale("go", "std")
worklist := t.worklist worklist := t.worklist
t.worklist = nil t.worklist = nil
for _, w := range worklist { for _, w := range worklist {
...@@ -1097,7 +1101,7 @@ func (t *tester) runPending(nextTest *distTest) { ...@@ -1097,7 +1101,7 @@ func (t *tester) runPending(nextTest *distTest) {
log.Printf("Failed: %v", w.err) log.Printf("Failed: %v", w.err)
t.failed = true t.failed = true
} }
checkNotStale("go", "std", "cmd") checkNotStale("go", "std")
} }
if t.failed && !t.keepGoing { if t.failed && !t.keepGoing {
log.Fatal("FAILED") log.Fatal("FAILED")
......
...@@ -102,6 +102,7 @@ func TestMain(m *testing.M) { ...@@ -102,6 +102,7 @@ func TestMain(m *testing.M) {
fmt.Printf("SKIP\n") fmt.Printf("SKIP\n")
return return
} }
os.Unsetenv("GOROOT_FINAL")
if canRun { if canRun {
args := []string{"build", "-tags", "testgo", "-o", "testgo" + exeSuffix} args := []string{"build", "-tags", "testgo", "-o", "testgo" + exeSuffix}
...@@ -4511,19 +4512,9 @@ func TestExecutableGOROOT(t *testing.T) { ...@@ -4511,19 +4512,9 @@ func TestExecutableGOROOT(t *testing.T) {
newRoot := tg.path("new") newRoot := tg.path("new")
t.Run("RelocatedExe", func(t *testing.T) { t.Run("RelocatedExe", func(t *testing.T) {
t.Skip("TODO: skipping known broken test; see golang.org/issue/20284") // Should fall back to default location in binary,
// which is the GOROOT we used when building testgo.exe.
// Should fall back to default location in binary. check(t, newGoTool, testGOROOT)
// No way to dig out other than look at source code.
data, err := ioutil.ReadFile("../../runtime/internal/sys/zversion.go")
if err != nil {
t.Fatal(err)
}
m := regexp.MustCompile("var DefaultGoroot = `([^`]+)`").FindStringSubmatch(string(data))
if m == nil {
t.Fatal("cannot find DefaultGoroot in ../../runtime/internal/sys/zversion.go")
}
check(t, newGoTool, m[1])
}) })
// If the binary is sitting in a bin dir next to ../pkg/tool, that counts as a GOROOT, // If the binary is sitting in a bin dir next to ../pkg/tool, that counts as a GOROOT,
...@@ -4548,9 +4539,7 @@ func TestExecutableGOROOT(t *testing.T) { ...@@ -4548,9 +4539,7 @@ func TestExecutableGOROOT(t *testing.T) {
tg.must(os.RemoveAll(tg.path("new/pkg"))) tg.must(os.RemoveAll(tg.path("new/pkg")))
// Binaries built in the new tree should report the // Binaries built in the new tree should report the
// new tree when they call runtime.GOROOT(). // new tree when they call runtime.GOROOT.
// This is implemented by having the go tool pass a -X option
// to the linker setting runtime/internal/sys.DefaultGoroot.
t.Run("RuntimeGoroot", func(t *testing.T) { t.Run("RuntimeGoroot", func(t *testing.T) {
// Build a working GOROOT the easy way, with symlinks. // Build a working GOROOT the easy way, with symlinks.
testenv.MustHaveSymlink(t) testenv.MustHaveSymlink(t)
......
...@@ -76,11 +76,12 @@ func init() { ...@@ -76,11 +76,12 @@ func init() {
} }
var ( var (
GOROOT = findGOROOT() GOROOT = findGOROOT()
GOBIN = os.Getenv("GOBIN") GOBIN = os.Getenv("GOBIN")
GOROOTbin = filepath.Join(GOROOT, "bin") GOROOTbin = filepath.Join(GOROOT, "bin")
GOROOTpkg = filepath.Join(GOROOT, "pkg") GOROOTpkg = filepath.Join(GOROOT, "pkg")
GOROOTsrc = filepath.Join(GOROOT, "src") GOROOTsrc = filepath.Join(GOROOT, "src")
GOROOT_FINAL = findGOROOT_FINAL()
// Used in envcmd.MkEnv and build ID computations. // Used in envcmd.MkEnv and build ID computations.
GOARM = fmt.Sprint(objabi.GOARM) GOARM = fmt.Sprint(objabi.GOARM)
...@@ -129,6 +130,14 @@ func findGOROOT() string { ...@@ -129,6 +130,14 @@ func findGOROOT() string {
return def return def
} }
func findGOROOT_FINAL() string {
def := GOROOT
if env := os.Getenv("GOROOT_FINAL"); env != "" {
def = filepath.Clean(env)
}
return def
}
// isSameDir reports whether dir1 and dir2 are the same directory. // isSameDir reports whether dir1 and dir2 are the same directory.
func isSameDir(dir1, dir2 string) bool { func isSameDir(dir1, dir2 string) bool {
if dir1 == dir2 { if dir1 == dir2 {
......
...@@ -790,15 +790,8 @@ func (b *Builder) printLinkerConfig(h io.Writer, p *load.Package) { ...@@ -790,15 +790,8 @@ func (b *Builder) printLinkerConfig(h io.Writer, p *load.Package) {
} }
fmt.Fprintf(h, "GO$GOARCH=%s\n", os.Getenv("GO"+strings.ToUpper(cfg.BuildContext.GOARCH))) // GO386, GOARM, etc fmt.Fprintf(h, "GO$GOARCH=%s\n", os.Getenv("GO"+strings.ToUpper(cfg.BuildContext.GOARCH))) // GO386, GOARM, etc
/* // The linker writes source file paths that say GOROOT_FINAL.
// TODO(rsc): Enable this code. fmt.Fprintf(h, "GOROOT=%s\n", cfg.GOROOT_FINAL)
// golang.org/issue/22475.
goroot := cfg.BuildContext.GOROOT
if final := os.Getenv("GOROOT_FINAL"); final != "" {
goroot = final
}
fmt.Fprintf(h, "GOROOT=%s\n", goroot)
*/
// TODO(rsc): Convince linker team not to add more magic environment variables, // TODO(rsc): Convince linker team not to add more magic environment variables,
// or perhaps restrict the environment variables passed to subprocesses. // or perhaps restrict the environment variables passed to subprocesses.
......
...@@ -418,11 +418,6 @@ func (gcToolchain) ld(b *Builder, root *Action, out, importcfg, mainpkg string) ...@@ -418,11 +418,6 @@ func (gcToolchain) ld(b *Builder, root *Action, out, importcfg, mainpkg string)
ldflags = append(ldflags, "-pluginpath", pluginPath(root)) ldflags = append(ldflags, "-pluginpath", pluginPath(root))
} }
// TODO(rsc): This is probably wrong - see golang.org/issue/22155.
if cfg.GOROOT != runtime.GOROOT() {
ldflags = append(ldflags, "-X=runtime/internal/sys.DefaultGoroot="+cfg.GOROOT)
}
// Store BuildID inside toolchain binaries as a unique identifier of the // Store BuildID inside toolchain binaries as a unique identifier of the
// tool being run, for use by content-based staleness determination. // tool being run, for use by content-based staleness determination.
if root.Package.Goroot && strings.HasPrefix(root.Package.ImportPath, "cmd/") { if root.Package.Goroot && strings.HasPrefix(root.Package.ImportPath, "cmd/") {
......
...@@ -19,6 +19,8 @@ func envOr(key, value string) string { ...@@ -19,6 +19,8 @@ func envOr(key, value string) string {
} }
var ( var (
defaultGOROOT string // set by linker
GOROOT = envOr("GOROOT", defaultGOROOT) GOROOT = envOr("GOROOT", defaultGOROOT)
GOARCH = envOr("GOARCH", defaultGOARCH) GOARCH = envOr("GOARCH", defaultGOARCH)
GOOS = envOr("GOOS", defaultGOOS) GOOS = envOr("GOOS", defaultGOOS)
......
...@@ -32,6 +32,9 @@ func TestDWARF(t *testing.T) { ...@@ -32,6 +32,9 @@ func TestDWARF(t *testing.T) {
t.Fatalf("go list: %v\n%s", err, out) t.Fatalf("go list: %v\n%s", err, out)
} }
if string(out) != "false\n" { if string(out) != "false\n" {
if os.Getenv("GOROOT_FINAL_OLD") != "" {
t.Skip("cmd/link is stale, but $GOROOT_FINAL_OLD is set")
}
t.Fatalf("cmd/link is stale - run go install cmd/link") t.Fatalf("cmd/link is stale - run go install cmd/link")
} }
......
...@@ -110,6 +110,10 @@ func Main(arch *sys.Arch, theArch Arch) { ...@@ -110,6 +110,10 @@ func Main(arch *sys.Arch, theArch Arch) {
} }
} }
final := gorootFinal()
addstrdata1(ctxt, "runtime/internal/sys.DefaultGoroot="+final)
addstrdata1(ctxt, "cmd/internal/objabi.defaultGOROOT="+final)
// TODO(matloob): define these above and then check flag values here // TODO(matloob): define these above and then check flag values here
if ctxt.Arch.Family == sys.AMD64 && objabi.GOOS == "plan9" { if ctxt.Arch.Family == sys.AMD64 && objabi.GOOS == "plan9" {
flag.BoolVar(&Flag8, "8", false, "use 64-bit addresses in symbol table") flag.BoolVar(&Flag8, "8", false, "use 64-bit addresses in symbol table")
......
...@@ -421,14 +421,18 @@ func (ctxt *Link) pclntab() { ...@@ -421,14 +421,18 @@ func (ctxt *Link) pclntab() {
} }
} }
func gorootFinal() string {
root := objabi.GOROOT
if final := os.Getenv("GOROOT_FINAL"); final != "" {
root = final
}
return root
}
func expandGoroot(s string) string { func expandGoroot(s string) string {
const n = len("$GOROOT") const n = len("$GOROOT")
if len(s) >= n+1 && s[:n] == "$GOROOT" && (s[n] == '/' || s[n] == '\\') { if len(s) >= n+1 && s[:n] == "$GOROOT" && (s[n] == '/' || s[n] == '\\') {
root := objabi.GOROOT return filepath.ToSlash(filepath.Join(gorootFinal(), s[n:]))
if final := os.Getenv("GOROOT_FINAL"); final != "" {
root = final
}
return filepath.ToSlash(filepath.Join(root, s[n:]))
} }
return s return s
} }
......
...@@ -9,3 +9,5 @@ package sys ...@@ -9,3 +9,5 @@ package sys
const PtrSize = 4 << (^uintptr(0) >> 63) // unsafe.Sizeof(uintptr(0)) but an ideal const const PtrSize = 4 << (^uintptr(0) >> 63) // unsafe.Sizeof(uintptr(0)) but an ideal const
const RegSize = 4 << (^Uintreg(0) >> 63) // unsafe.Sizeof(uintreg(0)) but an ideal const const RegSize = 4 << (^Uintreg(0) >> 63) // unsafe.Sizeof(uintreg(0)) but an ideal const
const SpAlign = 1*(1-GoarchArm64) + 16*GoarchArm64 // SP alignment: 1 normally, 16 for ARM64 const SpAlign = 1*(1-GoarchArm64) + 16*GoarchArm64 // SP alignment: 1 normally, 16 for ARM64
var DefaultGoroot string // set at link time
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment