From a9d13a9c230bafba64469f126202315ba4d24eea Mon Sep 17 00:00:00 2001 From: Than McIntosh Date: Thu, 3 Feb 2022 08:23:10 -0500 Subject: [PATCH] cmd/link: read crt2.o for windows internal-linking CGO For Windows internal linking with CGO, when using more modern LLVM-based compilers, we may need to read in the object file "crt2.o" so as to have a definition of "atexit" (for example when linking the runtime/cgo test), and we also need to allow for the possibility that a given host archive might have to be looked at more than once. The goal here is to get all.bash working on Windows when using an up to date mingw C compiler (including those based on clang + LLD). This patch also adds a new "hostObject" helper routine, similar to "hostArchive" but specific to individual object files. There is also a change to hostArchive to modify the pseudo-package name assigned when reading archive elements: up until this point, a package name of "libgcc" was used (even when reading a host archive like "libmingex.a"), which led to very confusing errors messages if symbols were missing or there were duplicate definitions. Updates #35006. Change-Id: I19c17dea9cfffa9e79030fc23064c7c63a612097 Reviewed-on: https://go-review.googlesource.com/c/go/+/382838 Reviewed-by: Cherry Mui Trust: Than McIntosh Run-TryBot: Than McIntosh TryBot-Result: Gopher Robot --- src/cmd/link/internal/ld/ar.go | 13 +++- src/cmd/link/internal/ld/lib.go | 119 +++++++++++++++++++++++++++----- 2 files changed, 111 insertions(+), 21 deletions(-) diff --git a/src/cmd/link/internal/ld/ar.go b/src/cmd/link/internal/ld/ar.go index 23915f9032..125a5d6fcb 100644 --- a/src/cmd/link/internal/ld/ar.go +++ b/src/cmd/link/internal/ld/ar.go @@ -38,6 +38,8 @@ import ( "internal/buildcfg" "io" "os" + "path/filepath" + "strings" ) const ( @@ -65,6 +67,9 @@ type ArHdr struct { // define them. This is used for the compiler support library // libgcc.a. func hostArchive(ctxt *Link, name string) { + if ctxt.Debugvlog > 1 { + ctxt.Logf("hostArchive(%s)\n", name) + } f, err := bio.Open(name) if err != nil { if os.IsNotExist(err) { @@ -122,8 +127,12 @@ func hostArchive(ctxt *Link, name string) { pname := fmt.Sprintf("%s(%s)", name, arhdr.name) l = atolwhex(arhdr.size) - libgcc := sym.Library{Pkg: "libgcc"} - h := ldobj(ctxt, f, &libgcc, l, pname, name) + pkname := filepath.Base(name) + if i := strings.LastIndex(pkname, ".a"); i >= 0 { + pkname = pkname[:i] + } + libar := sym.Library{Pkg: pkname} + h := ldobj(ctxt, f, &libar, l, pname, name) if h.ld == nil { Errorf(nil, "%s unrecognized object file at offset %d", name, off) continue diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 61b1fcbecf..7c9ec4e107 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -614,25 +614,7 @@ func (ctxt *Link) loadlib() { *flagLibGCC = ctxt.findLibPathCmd("--print-file-name=libcompiler_rt.a", "libcompiler_rt") } if ctxt.HeadType == objabi.Hwindows { - if p := ctxt.findLibPath("libmingwex.a"); p != "none" { - hostArchive(ctxt, p) - } - if p := ctxt.findLibPath("libmingw32.a"); p != "none" { - hostArchive(ctxt, p) - } - // Link libmsvcrt.a to resolve '__acrt_iob_func' symbol - // (see https://golang.org/issue/23649 for details). - if p := ctxt.findLibPath("libmsvcrt.a"); p != "none" { - hostArchive(ctxt, p) - } - // TODO: maybe do something similar to peimporteddlls to collect all lib names - // and try link them all to final exe just like libmingwex.a and libmingw32.a: - /* - for: - #cgo windows LDFLAGS: -lmsvcrt -lm - import: - libmsvcrt.a libm.a - */ + loadWindowsHostArchives(ctxt) } if *flagLibGCC != "none" { hostArchive(ctxt, *flagLibGCC) @@ -648,6 +630,52 @@ func (ctxt *Link) loadlib() { strictDupMsgCount = ctxt.loader.NStrictDupMsgs() } +// loadWindowsHostArchives loads in host archives and objects when +// doing internal linking on windows. Older toolchains seem to require +// just a single pass through the various archives, but some modern +// toolchains when linking a C program with mingw pass library paths +// multiple times to the linker, e.g. "... -lmingwex -lmingw32 ... +// -lmingwex -lmingw32 ...". To accommodate this behavior, we make two +// passes over the host archives below. +func loadWindowsHostArchives(ctxt *Link) { + any := true + for i := 0; any && i < 2; i++ { + // Link crt2.o (if present) to resolve "atexit" when + // using LLVM-based compilers. + isunresolved := symbolsAreUnresolved(ctxt, []string{"atexit"}) + if isunresolved[0] { + if p := ctxt.findLibPath("crt2.o"); p != "none" { + hostObject(ctxt, "crt2", p) + } + } + if p := ctxt.findLibPath("libmingwex.a"); p != "none" { + hostArchive(ctxt, p) + } + if p := ctxt.findLibPath("libmingw32.a"); p != "none" { + hostArchive(ctxt, p) + } + // Link libmsvcrt.a to resolve '__acrt_iob_func' symbol + // (see https://golang.org/issue/23649 for details). + if p := ctxt.findLibPath("libmsvcrt.a"); p != "none" { + hostArchive(ctxt, p) + } + any = false + undefs := ctxt.loader.UndefinedRelocTargets(1) + if len(undefs) > 0 { + any = true + } + } + // TODO: maybe do something similar to peimporteddlls to collect + // all lib names and try link them all to final exe just like + // libmingwex.a and libmingw32.a: + /* + for: + #cgo windows LDFLAGS: -lmsvcrt -lm + import: + libmsvcrt.a libm.a + */ +} + // loadcgodirectives reads the previously discovered cgo directives, creating // symbols in preparation for host object loading or use later in the link. func (ctxt *Link) loadcgodirectives() { @@ -2000,6 +2028,59 @@ func ldobj(ctxt *Link, f *bio.Reader, lib *sym.Library, length int64, pn string, return nil } +// symbolsAreUnresolved scans through the loader's list of unresolved +// symbols and checks to see whether any of them match the names of the +// symbols in 'want'. Return value is a list of bools, with list[K] set +// to true if there is an unresolved reference to the symbol in want[K]. +func symbolsAreUnresolved(ctxt *Link, want []string) []bool { + returnAllUndefs := -1 + undefs := ctxt.loader.UndefinedRelocTargets(returnAllUndefs) + seen := make(map[loader.Sym]struct{}) + rval := make([]bool, len(want)) + wantm := make(map[string]int) + for k, w := range want { + wantm[w] = k + } + count := 0 + for _, s := range undefs { + if _, ok := seen[s]; ok { + continue + } + seen[s] = struct{}{} + if k, ok := wantm[ctxt.loader.SymName(s)]; ok { + rval[k] = true + count++ + if count == len(want) { + return rval + } + } + } + return rval +} + +// hostObject reads a single host object file (compare to "hostArchive"). +// This is used as part of internal linking when we need to pull in +// files such as "crt?.o". +func hostObject(ctxt *Link, objname string, path string) { + if ctxt.Debugvlog > 1 { + ctxt.Logf("hostObject(%s)\n", path) + } + objlib := sym.Library{ + Pkg: objname, + } + f, err := bio.Open(path) + if err != nil { + Exitf("cannot open host object %q file %s: %v", objname, path, err) + } + defer f.Close() + h := ldobj(ctxt, f, &objlib, 0, path, path) + if h.ld == nil { + Exitf("unrecognized object file format in %s", path) + } + f.MustSeek(h.off, 0) + h.ld(ctxt, f, h.pkg, h.length, h.pn) +} + func checkFingerprint(lib *sym.Library, libfp goobj.FingerprintType, src string, srcfp goobj.FingerprintType) { if libfp != srcfp { Exitf("fingerprint mismatch: %s has %x, import from %s expecting %x", lib, libfp, src, srcfp)