Skip to content

Commit cf93b25

Browse files
committed
cmd/link: revise DLL import symbol handling
This patch reworks the handling of DLL import symbols in the PE host object loader to ensure that the Go linker can deal with them properly during internal linking. Prior to this point the strategy was to immediately treat an import symbol reference of the form "__imp__XXX" as if it were a reference to the corresponding DYNIMPORT symbol XXX, except for certain special cases. This worked for the most part, but ran into problems in situations where the target ("XXX") wasn't a previously created DYNIMPORT symbol (and when these problems happened, the root cause was not always easy to see). The new strategy is to not do any renaming or forwarding immediately, but to delay handling until host object loading is complete. At that point we make a scan through the newly introduced text+data sections looking at the relocations that target import symbols, forwarding the references to the corresponding DYNIMPORT sym where appropriate and where there are direct refs to the DYNIMPORT syms, tagging them for stub generation later on. Updates #35006. Updates #53540. Change-Id: I2d42b39141ae150a9f82ecc334001749ae8a3b4a Reviewed-on: https://go-review.googlesource.com/c/go/+/451738 Reviewed-by: Cherry Mui <cherryyz@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: David Chase <drchase@google.com> Run-TryBot: Than McIntosh <thanm@google.com>
1 parent 771a98d commit cf93b25

File tree

4 files changed

+263
-46
lines changed

4 files changed

+263
-46
lines changed

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ package ld
3232

3333
import (
3434
"cmd/internal/bio"
35+
"cmd/link/internal/loader"
3536
"cmd/link/internal/sym"
3637
"encoding/binary"
3738
"fmt"
@@ -61,6 +62,39 @@ type ArHdr struct {
6162
fmag string
6263
}
6364

65+
// pruneUndefsForWindows trims the list "undefs" of currently
66+
// outstanding unresolved symbols to remove references to DLL import
67+
// symbols (e.g. "__imp_XXX"). In older versions of the linker, we
68+
// would just immediately forward references from the import sym
69+
// (__imp_XXX) to the DLL sym (XXX), but with newer compilers this
70+
// strategy falls down in certain cases. We instead now do this
71+
// forwarding later on as a post-processing step, and meaning that
72+
// during the middle part of host object loading we can see a lot of
73+
// unresolved (SXREF) import symbols. We do not, however, want to
74+
// trigger the inclusion of an object from a host archive if the
75+
// reference is going to be eventually forwarded to the corresponding
76+
// SDYNIMPORT symbol, so here we strip out such refs from the undefs
77+
// list.
78+
func pruneUndefsForWindows(ldr *loader.Loader, undefs, froms []loader.Sym) ([]loader.Sym, []loader.Sym) {
79+
var newundefs []loader.Sym
80+
var newfroms []loader.Sym
81+
for _, s := range undefs {
82+
sname := ldr.SymName(s)
83+
if strings.HasPrefix(sname, "__imp_") {
84+
dname := sname[len("__imp_"):]
85+
ds := ldr.Lookup(dname, 0)
86+
if ds != 0 && ldr.SymType(ds) == sym.SDYNIMPORT {
87+
// Don't try to pull things out of a host archive to
88+
// satisfy this symbol.
89+
continue
90+
}
91+
}
92+
newundefs = append(newundefs, s)
93+
newfroms = append(newfroms, s)
94+
}
95+
return newundefs, newfroms
96+
}
97+
6498
// hostArchive reads an archive file holding host objects and links in
6599
// required objects. The general format is the same as a Go archive
66100
// file, but it has an armap listing symbols and the objects that
@@ -111,6 +145,9 @@ func hostArchive(ctxt *Link, name string) {
111145
var load []uint64
112146
returnAllUndefs := -1
113147
undefs, froms := ctxt.loader.UndefinedRelocTargets(returnAllUndefs)
148+
if buildcfg.GOOS == "windows" {
149+
undefs, froms = pruneUndefsForWindows(ctxt.loader, undefs, froms)
150+
}
114151
for k, symIdx := range undefs {
115152
sname := ctxt.loader.SymName(symIdx)
116153
if off := armap[sname]; off != 0 && !loaded[off] {

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

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
"cmd/internal/objabi"
3838
"cmd/internal/sys"
3939
"cmd/link/internal/loader"
40+
"cmd/link/internal/loadpe"
4041
"cmd/link/internal/sym"
4142
"compress/zlib"
4243
"debug/elf"
@@ -747,7 +748,38 @@ func (ctxt *Link) makeRelocSymState() *relocSymState {
747748
}
748749
}
749750

750-
func windynrelocsym(ctxt *Link, rel *loader.SymbolBuilder, s loader.Sym) {
751+
// windynrelocsym examines a text symbol 's' and looks for relocations
752+
// from it that correspond to references to symbols defined in DLLs,
753+
// then fixes up those relocations as needed. A reference to a symbol
754+
// XYZ from some DLL will fall into one of two categories: an indirect
755+
// ref via "__imp_XYZ", or a direct ref to "XYZ". Here's an example of
756+
// an indirect ref (this is an excerpt from objdump -ldr):
757+
//
758+
// 1c1: 48 89 c6 movq %rax, %rsi
759+
// 1c4: ff 15 00 00 00 00 callq *(%rip)
760+
// 00000000000001c6: IMAGE_REL_AMD64_REL32 __imp__errno
761+
//
762+
// In the assembly above, the code loads up the value of __imp_errno
763+
// and then does an indirect call to that value.
764+
//
765+
// Here is what a direct reference might look like:
766+
//
767+
// 137: e9 20 06 00 00 jmp 0x75c <pow+0x75c>
768+
// 13c: e8 00 00 00 00 callq 0x141 <pow+0x141>
769+
// 000000000000013d: IMAGE_REL_AMD64_REL32 _errno
770+
//
771+
// The assembly below dispenses with the import symbol and just makes
772+
// a direct call to _errno.
773+
//
774+
// The code below handles indirect refs by redirecting the target of
775+
// the relocation from "__imp_XYZ" to "XYZ" (since the latter symbol
776+
// is what the Windows loader is expected to resolve). For direct refs
777+
// the call is redirected to a stub, where the stub first loads the
778+
// symbol and then direct an indirect call to that value.
779+
//
780+
// Note that for a given symbol (as above) it is perfectly legal to
781+
// have both direct and indirect references.
782+
func windynrelocsym(ctxt *Link, rel *loader.SymbolBuilder, s loader.Sym) error {
751783
var su *loader.SymbolBuilder
752784
relocs := ctxt.loader.Relocs(s)
753785
for ri := 0; ri < relocs.Count(); ri++ {
@@ -763,13 +795,43 @@ func windynrelocsym(ctxt *Link, rel *loader.SymbolBuilder, s loader.Sym) {
763795
if r.Weak() {
764796
continue
765797
}
766-
ctxt.Errorf(s, "dynamic relocation to unreachable symbol %s",
798+
return fmt.Errorf("dynamic relocation to unreachable symbol %s",
767799
ctxt.loader.SymName(targ))
768800
}
801+
tgot := ctxt.loader.SymGot(targ)
802+
if tgot == loadpe.RedirectToDynImportGotToken {
803+
804+
// Consistency check: name should be __imp_X
805+
sname := ctxt.loader.SymName(targ)
806+
if !strings.HasPrefix(sname, "__imp_") {
807+
return fmt.Errorf("internal error in windynrelocsym: redirect GOT token applied to non-import symbol %s", sname)
808+
}
809+
810+
// Locate underlying symbol (which originally had type
811+
// SDYNIMPORT but has since been retyped to SWINDOWS).
812+
ds, err := loadpe.LookupBaseFromImport(targ, ctxt.loader, ctxt.Arch)
813+
if err != nil {
814+
return err
815+
}
816+
dstyp := ctxt.loader.SymType(ds)
817+
if dstyp != sym.SWINDOWS {
818+
return fmt.Errorf("internal error in windynrelocsym: underlying sym for %q has wrong type %s", sname, dstyp.String())
819+
}
820+
821+
// Redirect relocation to the dynimport.
822+
r.SetSym(ds)
823+
continue
824+
}
769825

770826
tplt := ctxt.loader.SymPlt(targ)
771-
tgot := ctxt.loader.SymGot(targ)
772-
if tplt == -2 && tgot != -2 { // make dynimport JMP table for PE object files.
827+
if tplt == loadpe.CreateImportStubPltToken {
828+
829+
// Consistency check: don't want to see both PLT and GOT tokens.
830+
if tgot != -1 {
831+
return fmt.Errorf("internal error in windynrelocsym: invalid GOT setting %d for reloc to %s", tgot, ctxt.loader.SymName(targ))
832+
}
833+
834+
// make dynimport JMP table for PE object files.
773835
tplt := int32(rel.Size())
774836
ctxt.loader.SetPlt(targ, tplt)
775837

@@ -782,8 +844,7 @@ func windynrelocsym(ctxt *Link, rel *loader.SymbolBuilder, s loader.Sym) {
782844
// jmp *addr
783845
switch ctxt.Arch.Family {
784846
default:
785-
ctxt.Errorf(s, "unsupported arch %v", ctxt.Arch.Family)
786-
return
847+
return fmt.Errorf("internal error in windynrelocsym: unsupported arch %v", ctxt.Arch.Family)
787848
case sys.I386:
788849
rel.AddUint8(0xff)
789850
rel.AddUint8(0x25)
@@ -805,6 +866,7 @@ func windynrelocsym(ctxt *Link, rel *loader.SymbolBuilder, s loader.Sym) {
805866
r.SetAdd(int64(tplt))
806867
}
807868
}
869+
return nil
808870
}
809871

810872
// windynrelocsyms generates jump table to C library functions that will be
@@ -818,7 +880,9 @@ func (ctxt *Link) windynrelocsyms() {
818880
rel.SetType(sym.STEXT)
819881

820882
for _, s := range ctxt.Textp {
821-
windynrelocsym(ctxt, rel, s)
883+
if err := windynrelocsym(ctxt, rel, s); err != nil {
884+
ctxt.Errorf(s, "%v", err)
885+
}
822886
}
823887

824888
ctxt.Textp = append(ctxt.Textp, rel.Sym())

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,13 @@ func loadWindowsHostArchives(ctxt *Link) {
711711
ctxt.loader.SetAttrSpecial(sb.Sym(), true)
712712
}
713713
}
714+
715+
// Fix up references to DLL import symbols now that we're done
716+
// pulling in new objects.
717+
if err := loadpe.PostProcessImports(); err != nil {
718+
Errorf(nil, "%v", err)
719+
}
720+
714721
// TODO: maybe do something similar to peimporteddlls to collect
715722
// all lib names and try link them all to final exe just like
716723
// libmingwex.a and libmingw32.a:

0 commit comments

Comments
 (0)