diff options
author | Alex Brainman <alex.brainman@gmail.com> | 2020-02-25 18:42:24 +1100 |
---|---|---|
committer | Alex Brainman <alex.brainman@gmail.com> | 2020-03-01 05:00:10 +0000 |
commit | 95f382139043059a2a0780ba577b53893408f7e4 (patch) | |
tree | 6529ca6e764bf447a67f439bdb73887882c224d2 | |
parent | 91bc75b4870308b668d497ff22eada75219c3c2e (diff) | |
download | go-95f382139043059a2a0780ba577b53893408f7e4.tar.gz go-95f382139043059a2a0780ba577b53893408f7e4.zip |
cmd/go, cmd/link: implement -buildmode=pie on windows
This CL implements windows version of -buildmode=pie code in both
cmd/go and cmd/link.
Windows executables built with -buildmode=pie set (unlike the one
built with -buildmode=exe) will have extra .reloc PE section, and
will have no IMAGE_FILE_RELOCS_STRIPPED flag set. They will also
have IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE flag set, and
IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA flag set for windows/amd64.
Both cgo and non-cgo versions are implemented. And TestBuildmodePIE
is extended to test both cgo and non-cgo versions on windows and
linux.
This CL used some code from CLs 152759 and 203602.
RELNOTE=yes
Fixes #27144
Updates #35192
Change-Id: I1249e4ffbd79bd4277efefb56db321c390c0f76f
Reviewed-on: https://go-review.googlesource.com/c/go/+/214397
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
-rw-r--r-- | src/cmd/dist/test.go | 4 | ||||
-rw-r--r-- | src/cmd/go/go_test.go | 57 | ||||
-rw-r--r-- | src/cmd/go/internal/work/init.go | 8 | ||||
-rw-r--r-- | src/cmd/go/testdata/script/version.txt | 6 | ||||
-rw-r--r-- | src/cmd/internal/sys/supported.go | 3 | ||||
-rw-r--r-- | src/cmd/link/internal/ld/config.go | 3 | ||||
-rw-r--r-- | src/cmd/link/internal/ld/lib.go | 16 | ||||
-rw-r--r-- | src/cmd/link/internal/ld/pe.go | 58 |
8 files changed, 122 insertions, 33 deletions
diff --git a/src/cmd/dist/test.go b/src/cmd/dist/test.go index ca617e917e..48c36a63fc 100644 --- a/src/cmd/dist/test.go +++ b/src/cmd/dist/test.go @@ -941,6 +941,8 @@ func (t *tester) internalLinkPIE() bool { case "linux-amd64", "linux-arm64", "android-arm64": return true + case "windows-amd64", "windows-386", "windows-arm": + return true } return false } @@ -997,6 +999,8 @@ func (t *tester) supportedBuildmode(mode string) bool { return true case "darwin-amd64": return true + case "windows-amd64", "windows-386", "windows-arm": + return true } return false diff --git a/src/cmd/go/go_test.go b/src/cmd/go/go_test.go index 4d5136deea..6654bd3143 100644 --- a/src/cmd/go/go_test.go +++ b/src/cmd/go/go_test.go @@ -9,6 +9,7 @@ import ( "context" "debug/elf" "debug/macho" + "debug/pe" "flag" "fmt" "go/format" @@ -2146,19 +2147,37 @@ func TestBuildmodePIE(t *testing.T) { switch platform { case "linux/386", "linux/amd64", "linux/arm", "linux/arm64", "linux/ppc64le", "linux/s390x", "android/amd64", "android/arm", "android/arm64", "android/386", - "freebsd/amd64": + "freebsd/amd64", + "windows/386", "windows/amd64", "windows/arm": case "darwin/amd64": default: t.Skipf("skipping test because buildmode=pie is not supported on %s", platform) } + t.Run("non-cgo", func(t *testing.T) { + testBuildmodePIE(t, false) + }) + if canCgo { + switch runtime.GOOS { + case "darwin", "freebsd", "linux", "windows": + t.Run("cgo", func(t *testing.T) { + testBuildmodePIE(t, true) + }) + } + } +} +func testBuildmodePIE(t *testing.T, useCgo bool) { tg := testgo(t) defer tg.cleanup() tg.parallel() - tg.tempFile("main.go", `package main; func main() { print("hello") }`) + var s string + if useCgo { + s = `import "C";` + } + tg.tempFile("main.go", fmt.Sprintf(`package main;%s func main() { print("hello") }`, s)) src := tg.path("main.go") - obj := tg.path("main") + obj := tg.path("main.exe") tg.run("build", "-buildmode=pie", "-o", obj, src) switch runtime.GOOS { @@ -2183,6 +2202,38 @@ func TestBuildmodePIE(t *testing.T) { if f.Flags&macho.FlagPIE == 0 { t.Error("PIE must have PIE flag, but not") } + case "windows": + f, err := pe.Open(obj) + if err != nil { + t.Fatal(err) + } + defer f.Close() + const ( + IMAGE_FILE_RELOCS_STRIPPED = 0x0001 + IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA = 0x0020 + IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = 0x0040 + ) + if f.Section(".reloc") == nil { + t.Error(".reloc section is not present") + } + if (f.FileHeader.Characteristics & IMAGE_FILE_RELOCS_STRIPPED) != 0 { + t.Error("IMAGE_FILE_RELOCS_STRIPPED flag is set") + } + var dc uint16 + switch oh := f.OptionalHeader.(type) { + case *pe.OptionalHeader32: + dc = oh.DllCharacteristics + case *pe.OptionalHeader64: + dc = oh.DllCharacteristics + if (dc & IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA) == 0 { + t.Error("IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA flag is not set") + } + default: + t.Fatalf("unexpected optional header type of %T", f.OptionalHeader) + } + if (dc & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) == 0 { + t.Error("IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE flag is not set") + } default: panic("unreachable") } diff --git a/src/cmd/go/internal/work/init.go b/src/cmd/go/internal/work/init.go index 9091f98636..e970272954 100644 --- a/src/cmd/go/internal/work/init.go +++ b/src/cmd/go/internal/work/init.go @@ -161,8 +161,12 @@ func buildModeInit() { } if gccgo { codegenArg = "-fPIE" - } else if cfg.Goos != "aix" { - codegenArg = "-shared" + } else { + switch cfg.Goos { + case "aix", "windows": + default: + codegenArg = "-shared" + } } ldBuildmode = "pie" case "shared": diff --git a/src/cmd/go/testdata/script/version.txt b/src/cmd/go/testdata/script/version.txt index 0ed1194840..0123ac6d53 100644 --- a/src/cmd/go/testdata/script/version.txt +++ b/src/cmd/go/testdata/script/version.txt @@ -22,8 +22,6 @@ stdout '^\tpath\trsc.io/fortune' stdout '^\tmod\trsc.io/fortune\tv1.0.0' # Repeat the test with -buildmode=pie. -# TODO(golang.org/issue/27144): don't skip after -buildmode=pie is implemented -# on Windows. [!buildmode:pie] stop go build -buildmode=pie -o external.exe rsc.io/fortune go version external.exe @@ -33,8 +31,8 @@ stdout '^\tpath\trsc.io/fortune' stdout '^\tmod\trsc.io/fortune\tv1.0.0' # Also test PIE with internal linking. -# currently only supported on linux/amd64 and linux/arm64. -[!linux] stop +# currently only supported on linux/amd64, linux/arm64 and windows/amd64. +[!linux] [!windows] stop [!amd64] [!arm64] stop go build -buildmode=pie -ldflags=-linkmode=internal -o internal.exe rsc.io/fortune go version internal.exe diff --git a/src/cmd/internal/sys/supported.go b/src/cmd/internal/sys/supported.go index c8ab2181b5..639827be86 100644 --- a/src/cmd/internal/sys/supported.go +++ b/src/cmd/internal/sys/supported.go @@ -87,7 +87,8 @@ func BuildModeSupported(compiler, buildmode, goos, goarch string) bool { "android/amd64", "android/arm", "android/arm64", "android/386", "freebsd/amd64", "darwin/amd64", - "aix/ppc64": + "aix/ppc64", + "windows/386", "windows/amd64", "windows/arm": return true } return false diff --git a/src/cmd/link/internal/ld/config.go b/src/cmd/link/internal/ld/config.go index 0eba4dc162..2373b500e3 100644 --- a/src/cmd/link/internal/ld/config.go +++ b/src/cmd/link/internal/ld/config.go @@ -38,7 +38,7 @@ func (mode *BuildMode) Set(s string) error { *mode = BuildModeExe case "pie": switch objabi.GOOS { - case "aix", "android", "linux": + case "aix", "android", "linux", "windows": case "darwin", "freebsd": switch objabi.GOARCH { case "amd64": @@ -209,6 +209,7 @@ func mustLinkExternal(ctxt *Link) (res bool, reason string) { case BuildModePIE: switch objabi.GOOS + "/" + objabi.GOARCH { case "linux/amd64", "linux/arm64", "android/arm64": + case "windows/386", "windows/amd64", "windows/arm": default: // Internal linking does not support TLS_IE. return true, "buildmode=pie" diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 7c5877bfbd..a4b4b60ca1 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -1258,8 +1258,20 @@ func (ctxt *Link) hostlink() { } } case BuildModePIE: - // ELF. - if ctxt.HeadType != objabi.Hdarwin && ctxt.HeadType != objabi.Haix { + switch ctxt.HeadType { + case objabi.Hdarwin, objabi.Haix: + case objabi.Hwindows: + // Enable ASLR. + argv = append(argv, "-Wl,--dynamicbase") + // enable high-entropy ASLR on 64-bit. + if ctxt.Arch.PtrSize >= 8 { + argv = append(argv, "-Wl,--high-entropy-va") + } + // Work around binutils limitation that strips relocation table for dynamicbase. + // See https://sourceware.org/bugzilla/show_bug.cgi?id=19011 + argv = append(argv, "-Wl,--export-all-symbols") + default: + // ELF. if ctxt.UseRelro() { argv = append(argv, "-Wl,-z,relro") } diff --git a/src/cmd/link/internal/ld/pe.go b/src/cmd/link/internal/ld/pe.go index 4ab346e733..2c6be2d6f3 100644 --- a/src/cmd/link/internal/ld/pe.go +++ b/src/cmd/link/internal/ld/pe.go @@ -94,6 +94,7 @@ const ( IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR = 14 IMAGE_SUBSYSTEM_WINDOWS_GUI = 2 IMAGE_SUBSYSTEM_WINDOWS_CUI = 3 + IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA = 0x0020 IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = 0x0040 IMAGE_DLLCHARACTERISTICS_NX_COMPAT = 0x0100 IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE = 0x8000 @@ -126,6 +127,7 @@ const ( IMAGE_REL_ARM_SECREL = 0x000F IMAGE_REL_BASED_HIGHLOW = 3 + IMAGE_REL_BASED_DIR64 = 10 ) const ( @@ -752,12 +754,12 @@ func (f *peFile) writeSymbolTableAndStringTable(ctxt *Link) { } // writeFileHeader writes COFF file header for peFile f. -func (f *peFile) writeFileHeader(arch *sys.Arch, out *OutBuf, linkmode LinkMode) { +func (f *peFile) writeFileHeader(ctxt *Link) { var fh pe.FileHeader - switch arch.Family { + switch ctxt.Arch.Family { default: - Exitf("unknown PE architecture: %v", arch.Family) + Exitf("unknown PE architecture: %v", ctxt.Arch.Family) case sys.AMD64: fh.Machine = IMAGE_FILE_MACHINE_AMD64 case sys.I386: @@ -772,16 +774,15 @@ func (f *peFile) writeFileHeader(arch *sys.Arch, out *OutBuf, linkmode LinkMode) // much more beneficial than having build timestamp in the header. fh.TimeDateStamp = 0 - if linkmode == LinkExternal { + if ctxt.LinkMode == LinkExternal { fh.Characteristics = IMAGE_FILE_LINE_NUMS_STRIPPED } else { - switch arch.Family { - default: - Exitf("write COFF(ext): unknown PE architecture: %v", arch.Family) + fh.Characteristics = IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_DEBUG_STRIPPED + switch ctxt.Arch.Family { case sys.AMD64, sys.I386: - fh.Characteristics = IMAGE_FILE_RELOCS_STRIPPED | IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_DEBUG_STRIPPED - case sys.ARM: - fh.Characteristics = IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_DEBUG_STRIPPED + if ctxt.BuildMode != BuildModePIE { + fh.Characteristics |= IMAGE_FILE_RELOCS_STRIPPED + } } } if pe64 != 0 { @@ -797,7 +798,7 @@ func (f *peFile) writeFileHeader(arch *sys.Arch, out *OutBuf, linkmode LinkMode) fh.PointerToSymbolTable = uint32(f.symtabOffset) fh.NumberOfSymbols = uint32(f.symbolCount) - binary.Write(out, binary.LittleEndian, &fh) + binary.Write(ctxt.Out, binary.LittleEndian, &fh) } // writeOptionalHeader writes COFF optional header for peFile f. @@ -859,12 +860,6 @@ func (f *peFile) writeOptionalHeader(ctxt *Link) { oh.Subsystem = IMAGE_SUBSYSTEM_WINDOWS_CUI } - switch ctxt.Arch.Family { - case sys.ARM: - oh64.DllCharacteristics = IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE - oh.DllCharacteristics = IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE - } - // Mark as having awareness of terminal services, to avoid ancient compatibility hacks. oh64.DllCharacteristics |= IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE oh.DllCharacteristics |= IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE @@ -873,6 +868,23 @@ func (f *peFile) writeOptionalHeader(ctxt *Link) { oh64.DllCharacteristics |= IMAGE_DLLCHARACTERISTICS_NX_COMPAT oh.DllCharacteristics |= IMAGE_DLLCHARACTERISTICS_NX_COMPAT + // The DLL can be relocated at load time. + switch ctxt.Arch.Family { + case sys.AMD64, sys.I386: + if ctxt.BuildMode == BuildModePIE { + oh64.DllCharacteristics |= IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE + oh.DllCharacteristics |= IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE + } + case sys.ARM: + oh64.DllCharacteristics |= IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE + oh.DllCharacteristics |= IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE + } + + // Image can handle a high entropy 64-bit virtual address space. + if ctxt.BuildMode == BuildModePIE { + oh64.DllCharacteristics |= IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA + } + // Disable stack growth as we don't want Windows to // fiddle with the thread stack limits, which we set // ourselves to circumvent the stack checks in the @@ -997,7 +1009,7 @@ func pewrite(ctxt *Link) { ctxt.Out.WriteStringN("PE", 4) } - pefile.writeFileHeader(ctxt.Arch, ctxt.Out, ctxt.LinkMode) + pefile.writeFileHeader(ctxt) pefile.writeOptionalHeader(ctxt) @@ -1376,6 +1388,8 @@ func (rt *peBaseRelocTable) addentry(ctxt *Link, s *sym.Symbol, r *sym.Reloc) { Exitf("unsupported relocation size %d\n", r.Siz) case 4: e.typeOff |= uint16(IMAGE_REL_BASED_HIGHLOW << 12) + case 8: + e.typeOff |= uint16(IMAGE_REL_BASED_DIR64 << 12) } b.entries = append(b.entries, e) @@ -1430,11 +1444,15 @@ func addPEBaseRelocSym(ctxt *Link, s *sym.Symbol, rt *peBaseRelocTable) { } func addPEBaseReloc(ctxt *Link) { - // We only generate base relocation table for ARM (and ... ARM64), x86, and AMD64 are marked as legacy - // archs and can use fixed base with no base relocation information + // Arm does not work without base relocation table. + // 386 and amd64 will only require the table for BuildModePIE. switch ctxt.Arch.Family { default: return + case sys.I386, sys.AMD64: + if ctxt.BuildMode != BuildModePIE { + return + } case sys.ARM: } |