Skip to content

Commit 95f3821

Browse files
committed
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 <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent 91bc75b commit 95f3821

File tree

8 files changed

+122
-33
lines changed

8 files changed

+122
-33
lines changed

src/cmd/dist/test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,6 +941,8 @@ func (t *tester) internalLinkPIE() bool {
941941
case "linux-amd64", "linux-arm64",
942942
"android-arm64":
943943
return true
944+
case "windows-amd64", "windows-386", "windows-arm":
945+
return true
944946
}
945947
return false
946948
}
@@ -997,6 +999,8 @@ func (t *tester) supportedBuildmode(mode string) bool {
997999
return true
9981000
case "darwin-amd64":
9991001
return true
1002+
case "windows-amd64", "windows-386", "windows-arm":
1003+
return true
10001004
}
10011005
return false
10021006

src/cmd/go/go_test.go

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"context"
1010
"debug/elf"
1111
"debug/macho"
12+
"debug/pe"
1213
"flag"
1314
"fmt"
1415
"go/format"
@@ -2146,19 +2147,37 @@ func TestBuildmodePIE(t *testing.T) {
21462147
switch platform {
21472148
case "linux/386", "linux/amd64", "linux/arm", "linux/arm64", "linux/ppc64le", "linux/s390x",
21482149
"android/amd64", "android/arm", "android/arm64", "android/386",
2149-
"freebsd/amd64":
2150+
"freebsd/amd64",
2151+
"windows/386", "windows/amd64", "windows/arm":
21502152
case "darwin/amd64":
21512153
default:
21522154
t.Skipf("skipping test because buildmode=pie is not supported on %s", platform)
21532155
}
2156+
t.Run("non-cgo", func(t *testing.T) {
2157+
testBuildmodePIE(t, false)
2158+
})
2159+
if canCgo {
2160+
switch runtime.GOOS {
2161+
case "darwin", "freebsd", "linux", "windows":
2162+
t.Run("cgo", func(t *testing.T) {
2163+
testBuildmodePIE(t, true)
2164+
})
2165+
}
2166+
}
2167+
}
21542168

2169+
func testBuildmodePIE(t *testing.T, useCgo bool) {
21552170
tg := testgo(t)
21562171
defer tg.cleanup()
21572172
tg.parallel()
21582173

2159-
tg.tempFile("main.go", `package main; func main() { print("hello") }`)
2174+
var s string
2175+
if useCgo {
2176+
s = `import "C";`
2177+
}
2178+
tg.tempFile("main.go", fmt.Sprintf(`package main;%s func main() { print("hello") }`, s))
21602179
src := tg.path("main.go")
2161-
obj := tg.path("main")
2180+
obj := tg.path("main.exe")
21622181
tg.run("build", "-buildmode=pie", "-o", obj, src)
21632182

21642183
switch runtime.GOOS {
@@ -2183,6 +2202,38 @@ func TestBuildmodePIE(t *testing.T) {
21832202
if f.Flags&macho.FlagPIE == 0 {
21842203
t.Error("PIE must have PIE flag, but not")
21852204
}
2205+
case "windows":
2206+
f, err := pe.Open(obj)
2207+
if err != nil {
2208+
t.Fatal(err)
2209+
}
2210+
defer f.Close()
2211+
const (
2212+
IMAGE_FILE_RELOCS_STRIPPED = 0x0001
2213+
IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA = 0x0020
2214+
IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = 0x0040
2215+
)
2216+
if f.Section(".reloc") == nil {
2217+
t.Error(".reloc section is not present")
2218+
}
2219+
if (f.FileHeader.Characteristics & IMAGE_FILE_RELOCS_STRIPPED) != 0 {
2220+
t.Error("IMAGE_FILE_RELOCS_STRIPPED flag is set")
2221+
}
2222+
var dc uint16
2223+
switch oh := f.OptionalHeader.(type) {
2224+
case *pe.OptionalHeader32:
2225+
dc = oh.DllCharacteristics
2226+
case *pe.OptionalHeader64:
2227+
dc = oh.DllCharacteristics
2228+
if (dc & IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA) == 0 {
2229+
t.Error("IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA flag is not set")
2230+
}
2231+
default:
2232+
t.Fatalf("unexpected optional header type of %T", f.OptionalHeader)
2233+
}
2234+
if (dc & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE) == 0 {
2235+
t.Error("IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE flag is not set")
2236+
}
21862237
default:
21872238
panic("unreachable")
21882239
}

src/cmd/go/internal/work/init.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,12 @@ func buildModeInit() {
161161
}
162162
if gccgo {
163163
codegenArg = "-fPIE"
164-
} else if cfg.Goos != "aix" {
165-
codegenArg = "-shared"
164+
} else {
165+
switch cfg.Goos {
166+
case "aix", "windows":
167+
default:
168+
codegenArg = "-shared"
169+
}
166170
}
167171
ldBuildmode = "pie"
168172
case "shared":

src/cmd/go/testdata/script/version.txt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ stdout '^\tpath\trsc.io/fortune'
2222
stdout '^\tmod\trsc.io/fortune\tv1.0.0'
2323

2424
# Repeat the test with -buildmode=pie.
25-
# TODO(golang.org/issue/27144): don't skip after -buildmode=pie is implemented
26-
# on Windows.
2725
[!buildmode:pie] stop
2826
go build -buildmode=pie -o external.exe rsc.io/fortune
2927
go version external.exe
@@ -33,8 +31,8 @@ stdout '^\tpath\trsc.io/fortune'
3331
stdout '^\tmod\trsc.io/fortune\tv1.0.0'
3432

3533
# Also test PIE with internal linking.
36-
# currently only supported on linux/amd64 and linux/arm64.
37-
[!linux] stop
34+
# currently only supported on linux/amd64, linux/arm64 and windows/amd64.
35+
[!linux] [!windows] stop
3836
[!amd64] [!arm64] stop
3937
go build -buildmode=pie -ldflags=-linkmode=internal -o internal.exe rsc.io/fortune
4038
go version internal.exe

src/cmd/internal/sys/supported.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ func BuildModeSupported(compiler, buildmode, goos, goarch string) bool {
8787
"android/amd64", "android/arm", "android/arm64", "android/386",
8888
"freebsd/amd64",
8989
"darwin/amd64",
90-
"aix/ppc64":
90+
"aix/ppc64",
91+
"windows/386", "windows/amd64", "windows/arm":
9192
return true
9293
}
9394
return false

src/cmd/link/internal/ld/config.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func (mode *BuildMode) Set(s string) error {
3838
*mode = BuildModeExe
3939
case "pie":
4040
switch objabi.GOOS {
41-
case "aix", "android", "linux":
41+
case "aix", "android", "linux", "windows":
4242
case "darwin", "freebsd":
4343
switch objabi.GOARCH {
4444
case "amd64":
@@ -209,6 +209,7 @@ func mustLinkExternal(ctxt *Link) (res bool, reason string) {
209209
case BuildModePIE:
210210
switch objabi.GOOS + "/" + objabi.GOARCH {
211211
case "linux/amd64", "linux/arm64", "android/arm64":
212+
case "windows/386", "windows/amd64", "windows/arm":
212213
default:
213214
// Internal linking does not support TLS_IE.
214215
return true, "buildmode=pie"

src/cmd/link/internal/ld/lib.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1258,8 +1258,20 @@ func (ctxt *Link) hostlink() {
12581258
}
12591259
}
12601260
case BuildModePIE:
1261-
// ELF.
1262-
if ctxt.HeadType != objabi.Hdarwin && ctxt.HeadType != objabi.Haix {
1261+
switch ctxt.HeadType {
1262+
case objabi.Hdarwin, objabi.Haix:
1263+
case objabi.Hwindows:
1264+
// Enable ASLR.
1265+
argv = append(argv, "-Wl,--dynamicbase")
1266+
// enable high-entropy ASLR on 64-bit.
1267+
if ctxt.Arch.PtrSize >= 8 {
1268+
argv = append(argv, "-Wl,--high-entropy-va")
1269+
}
1270+
// Work around binutils limitation that strips relocation table for dynamicbase.
1271+
// See https://sourceware.org/bugzilla/show_bug.cgi?id=19011
1272+
argv = append(argv, "-Wl,--export-all-symbols")
1273+
default:
1274+
// ELF.
12631275
if ctxt.UseRelro() {
12641276
argv = append(argv, "-Wl,-z,relro")
12651277
}

src/cmd/link/internal/ld/pe.go

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ const (
9494
IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR = 14
9595
IMAGE_SUBSYSTEM_WINDOWS_GUI = 2
9696
IMAGE_SUBSYSTEM_WINDOWS_CUI = 3
97+
IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA = 0x0020
9798
IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = 0x0040
9899
IMAGE_DLLCHARACTERISTICS_NX_COMPAT = 0x0100
99100
IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE = 0x8000
@@ -126,6 +127,7 @@ const (
126127
IMAGE_REL_ARM_SECREL = 0x000F
127128

128129
IMAGE_REL_BASED_HIGHLOW = 3
130+
IMAGE_REL_BASED_DIR64 = 10
129131
)
130132

131133
const (
@@ -752,12 +754,12 @@ func (f *peFile) writeSymbolTableAndStringTable(ctxt *Link) {
752754
}
753755

754756
// writeFileHeader writes COFF file header for peFile f.
755-
func (f *peFile) writeFileHeader(arch *sys.Arch, out *OutBuf, linkmode LinkMode) {
757+
func (f *peFile) writeFileHeader(ctxt *Link) {
756758
var fh pe.FileHeader
757759

758-
switch arch.Family {
760+
switch ctxt.Arch.Family {
759761
default:
760-
Exitf("unknown PE architecture: %v", arch.Family)
762+
Exitf("unknown PE architecture: %v", ctxt.Arch.Family)
761763
case sys.AMD64:
762764
fh.Machine = IMAGE_FILE_MACHINE_AMD64
763765
case sys.I386:
@@ -772,16 +774,15 @@ func (f *peFile) writeFileHeader(arch *sys.Arch, out *OutBuf, linkmode LinkMode)
772774
// much more beneficial than having build timestamp in the header.
773775
fh.TimeDateStamp = 0
774776

775-
if linkmode == LinkExternal {
777+
if ctxt.LinkMode == LinkExternal {
776778
fh.Characteristics = IMAGE_FILE_LINE_NUMS_STRIPPED
777779
} else {
778-
switch arch.Family {
779-
default:
780-
Exitf("write COFF(ext): unknown PE architecture: %v", arch.Family)
780+
fh.Characteristics = IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_DEBUG_STRIPPED
781+
switch ctxt.Arch.Family {
781782
case sys.AMD64, sys.I386:
782-
fh.Characteristics = IMAGE_FILE_RELOCS_STRIPPED | IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_DEBUG_STRIPPED
783-
case sys.ARM:
784-
fh.Characteristics = IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_DEBUG_STRIPPED
783+
if ctxt.BuildMode != BuildModePIE {
784+
fh.Characteristics |= IMAGE_FILE_RELOCS_STRIPPED
785+
}
785786
}
786787
}
787788
if pe64 != 0 {
@@ -797,7 +798,7 @@ func (f *peFile) writeFileHeader(arch *sys.Arch, out *OutBuf, linkmode LinkMode)
797798
fh.PointerToSymbolTable = uint32(f.symtabOffset)
798799
fh.NumberOfSymbols = uint32(f.symbolCount)
799800

800-
binary.Write(out, binary.LittleEndian, &fh)
801+
binary.Write(ctxt.Out, binary.LittleEndian, &fh)
801802
}
802803

803804
// writeOptionalHeader writes COFF optional header for peFile f.
@@ -859,12 +860,6 @@ func (f *peFile) writeOptionalHeader(ctxt *Link) {
859860
oh.Subsystem = IMAGE_SUBSYSTEM_WINDOWS_CUI
860861
}
861862

862-
switch ctxt.Arch.Family {
863-
case sys.ARM:
864-
oh64.DllCharacteristics = IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
865-
oh.DllCharacteristics = IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
866-
}
867-
868863
// Mark as having awareness of terminal services, to avoid ancient compatibility hacks.
869864
oh64.DllCharacteristics |= IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE
870865
oh.DllCharacteristics |= IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE
@@ -873,6 +868,23 @@ func (f *peFile) writeOptionalHeader(ctxt *Link) {
873868
oh64.DllCharacteristics |= IMAGE_DLLCHARACTERISTICS_NX_COMPAT
874869
oh.DllCharacteristics |= IMAGE_DLLCHARACTERISTICS_NX_COMPAT
875870

871+
// The DLL can be relocated at load time.
872+
switch ctxt.Arch.Family {
873+
case sys.AMD64, sys.I386:
874+
if ctxt.BuildMode == BuildModePIE {
875+
oh64.DllCharacteristics |= IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
876+
oh.DllCharacteristics |= IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
877+
}
878+
case sys.ARM:
879+
oh64.DllCharacteristics |= IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
880+
oh.DllCharacteristics |= IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
881+
}
882+
883+
// Image can handle a high entropy 64-bit virtual address space.
884+
if ctxt.BuildMode == BuildModePIE {
885+
oh64.DllCharacteristics |= IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA
886+
}
887+
876888
// Disable stack growth as we don't want Windows to
877889
// fiddle with the thread stack limits, which we set
878890
// ourselves to circumvent the stack checks in the
@@ -997,7 +1009,7 @@ func pewrite(ctxt *Link) {
9971009
ctxt.Out.WriteStringN("PE", 4)
9981010
}
9991011

1000-
pefile.writeFileHeader(ctxt.Arch, ctxt.Out, ctxt.LinkMode)
1012+
pefile.writeFileHeader(ctxt)
10011013

10021014
pefile.writeOptionalHeader(ctxt)
10031015

@@ -1376,6 +1388,8 @@ func (rt *peBaseRelocTable) addentry(ctxt *Link, s *sym.Symbol, r *sym.Reloc) {
13761388
Exitf("unsupported relocation size %d\n", r.Siz)
13771389
case 4:
13781390
e.typeOff |= uint16(IMAGE_REL_BASED_HIGHLOW << 12)
1391+
case 8:
1392+
e.typeOff |= uint16(IMAGE_REL_BASED_DIR64 << 12)
13791393
}
13801394

13811395
b.entries = append(b.entries, e)
@@ -1430,11 +1444,15 @@ func addPEBaseRelocSym(ctxt *Link, s *sym.Symbol, rt *peBaseRelocTable) {
14301444
}
14311445

14321446
func addPEBaseReloc(ctxt *Link) {
1433-
// We only generate base relocation table for ARM (and ... ARM64), x86, and AMD64 are marked as legacy
1434-
// archs and can use fixed base with no base relocation information
1447+
// Arm does not work without base relocation table.
1448+
// 386 and amd64 will only require the table for BuildModePIE.
14351449
switch ctxt.Arch.Family {
14361450
default:
14371451
return
1452+
case sys.I386, sys.AMD64:
1453+
if ctxt.BuildMode != BuildModePIE {
1454+
return
1455+
}
14381456
case sys.ARM:
14391457
}
14401458

0 commit comments

Comments
 (0)