aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorqmuntal <quimmuntal@gmail.com>2026-04-08 11:30:10 +0200
committerQuim Muntal <quimmuntal@gmail.com>2026-04-09 05:10:16 -0700
commitb3c54d2e597ee67939f9abc2ec8aaca841fb1138 (patch)
treeab04ef0fad1806b4720dafadb08a37375c216457
parentff530b51286495e9b6189bfefe1aff8129365943 (diff)
downloadgo-b3c54d2e597ee67939f9abc2ec8aaca841fb1138.tar.xz
cmd/link: fallback to older minimum Windows target version on older C GNU toolchains
Setting Windows 10 as the minimum target version requires setting the IMAGE_GUARD_SECURITY_COOKIE_UNUSED flag in the PE load config directory, else the executable will fail to run. When using an external linker that flag is set by defining a _load_config_used symbol with the proper value. However, older versions of the GNU toolchain do not associate the _load_config_used symbol with the PE load config directory. To work around this issue, we can fallback to the previous minimum Windows target version if the external linker doesn't special-case the _load_config_used symbol. Change-Id: I36ac7a2968c3576ac788bf192e7614bbae35ad1f Reviewed-on: https://go-review.googlesource.com/c/go/+/763960 Reviewed-by: Damien Neil <dneil@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: David Chase <drchase@google.com>
-rw-r--r--src/cmd/link/internal/ld/lib.go105
1 files changed, 85 insertions, 20 deletions
diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go
index a05c8c2b48..96d25c7188 100644
--- a/src/cmd/link/internal/ld/lib.go
+++ b/src/cmd/link/internal/ld/lib.go
@@ -34,6 +34,7 @@ import (
"bytes"
"debug/elf"
"debug/macho"
+ "debug/pe"
"encoding/base64"
"encoding/binary"
"fmt"
@@ -1461,7 +1462,7 @@ func (ctxt *Link) hostlink() {
// Only macOS supports unmapped segments such as our __DWARF segment.
combineDwarf := ctxt.IsDarwin() && !*FlagW && machoPlatform == PLATFORM_MACOS
- var isMSVC bool // used on Windows
+ var isMSVC, isLLD bool // used on Windows
wlPrefix := "-Wl,--"
switch ctxt.HeadType {
@@ -1515,6 +1516,9 @@ func (ctxt *Link) hostlink() {
}
case objabi.Hwindows:
isMSVC = ctxt.isMSVC()
+ if !isMSVC {
+ isLLD = ctxt.isLLD()
+ }
if isMSVC {
// For various options, MSVC lld-link only accepts one dash.
// TODO: It seems mingw clang supports one or two dashes,
@@ -1537,10 +1541,22 @@ func (ctxt *Link) hostlink() {
argv = append(argv, wlPrefix+"nxcompat")
if !isMSVC {
- argv = append(argv, fmt.Sprintf("-Wl,--major-os-version=%d", PeMinimumTargetMajorVersion))
- argv = append(argv, fmt.Sprintf("-Wl,--minor-os-version=%d", PeMinimumTargetMinorVersion))
- argv = append(argv, fmt.Sprintf("-Wl,--major-subsystem-version=%d", PeMinimumTargetMajorVersion))
- argv = append(argv, fmt.Sprintf("-Wl,--minor-subsystem-version=%d", PeMinimumTargetMinorVersion))
+ peMajorVersion := PeMinimumTargetMajorVersion
+ peMinorVersion := PeMinimumTargetMinorVersion
+ if peMajorVersion >= 10 && !isLLD &&
+ !peHasLoadConfigDirectorySupport(ctxt.Arch, argv[0]) {
+ // The external linker doesn't support wiring up
+ // _load_config_used to the PE Load Configuration
+ // Directory. Windows 10+ validates this directory,
+ // so fall back to an older version to avoid
+ // load failures.
+ peMajorVersion = 6
+ peMinorVersion = 1
+ }
+ argv = append(argv, fmt.Sprintf("-Wl,--major-os-version=%d", peMajorVersion))
+ argv = append(argv, fmt.Sprintf("-Wl,--minor-os-version=%d", peMinorVersion))
+ argv = append(argv, fmt.Sprintf("-Wl,--major-subsystem-version=%d", peMajorVersion))
+ argv = append(argv, fmt.Sprintf("-Wl,--minor-subsystem-version=%d", peMinorVersion))
}
case objabi.Haix:
argv = append(argv, "-pthread")
@@ -1965,23 +1981,9 @@ func (ctxt *Link) hostlink() {
checkStatic(p)
}
if ctxt.HeadType == objabi.Hwindows {
- // Determine which linker we're using. Add in the extldflags in
- // case used has specified "-fuse-ld=...".
- extld := ctxt.extld()
- name, args := extld[0], extld[1:]
- args = append(args, trimLinkerArgv(flagExtldflags)...)
- args = append(args, "-Wl,--version")
- cmd := exec.Command(name, args...)
- usingLLD := false
- if out, err := cmd.CombinedOutput(); err == nil {
- if bytes.Contains(out, []byte("LLD ")) {
- usingLLD = true
- }
- }
-
// use gcc linker script to work around gcc bug
// (see https://golang.org/issue/20183 for details).
- if !usingLLD {
+ if !isLLD {
p := writeGDBLinkerScript()
argv = append(argv, "-Wl,-T,"+p)
}
@@ -2234,6 +2236,54 @@ func linkerFlagSupported(arch *sys.Arch, linker, altLinker, flag string) bool {
return err == nil && !bytes.Contains(out, []byte("unrecognized")) && !bytes.Contains(out, []byte("unknown"))
}
+// peHasLoadConfigDirectorySupport checks whether the external linker
+// populates the PE Load Configuration Directory data directory entry
+// when it encounters a _load_config_used symbol.
+//
+// GNU ld gained this support in binutils 2.45. MSVC link.exe and
+// LLVM lld-link have always supported it.
+func peHasLoadConfigDirectorySupport(arch *sys.Arch, linker string) bool {
+ src := filepath.Join(*flagTmpdir, "loadcfg_test.c")
+ if err := os.WriteFile(src, []byte(`
+#ifdef _WIN64
+typedef unsigned long long uintptr;
+#else
+typedef unsigned long uintptr;
+#endif
+const uintptr _load_config_used[2] = { sizeof(_load_config_used), 0 };
+int main() { return 0; }
+`), 0666); err != nil {
+ return false
+ }
+
+ outPath := filepath.Join(*flagTmpdir, "loadcfg_test.exe")
+ flags := hostlinkArchArgs(arch)
+ flags = append(flags, "-o", outPath, src)
+ cmd := exec.Command(linker, flags...)
+ cmd.Env = append([]string{"LC_ALL=C"}, os.Environ()...)
+ if err := cmd.Run(); err != nil {
+ return false
+ }
+
+ f, err := pe.Open(outPath)
+ if err != nil {
+ return false
+ }
+ defer f.Close()
+
+ switch oh := f.OptionalHeader.(type) {
+ case *pe.OptionalHeader64:
+ if int(pe.IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG) < len(oh.DataDirectory) {
+ return oh.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress != 0
+ }
+ case *pe.OptionalHeader32:
+ if int(pe.IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG) < len(oh.DataDirectory) {
+ return oh.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress != 0
+ }
+ }
+ return false
+}
+
// trimLinkerArgv returns a new copy of argv that does not include flags
// that are not relevant for testing whether some linker option works.
func trimLinkerArgv(argv []string) []string {
@@ -3133,3 +3183,18 @@ func (ctxt *Link) isMSVC() bool {
}
return false
}
+
+// isLLD reports whether the C toolchain is using LLD as the linker.
+func (ctxt *Link) isLLD() bool {
+ extld := ctxt.extld()
+ name, args := extld[0], extld[1:]
+ args = append(args, trimLinkerArgv(flagExtldflags)...)
+ args = append(args, "-Wl,--version")
+ cmd := exec.Command(name, args...)
+ if out, err := cmd.CombinedOutput(); err == nil {
+ if bytes.Contains(out, []byte("LLD ")) {
+ return true
+ }
+ }
+ return false
+}