]> Cypherpunks.ru repositories - gostls13.git/commitdiff
cmd/link/internal/loadpe: add rudimentary COMDAT support
authorThan McIntosh <thanm@google.com>
Mon, 7 Feb 2022 20:00:46 +0000 (15:00 -0500)
committerThan McIntosh <thanm@google.com>
Thu, 31 Mar 2022 15:59:05 +0000 (15:59 +0000)
Add some rudimentary support to the PE file loader for handling
sections in COMDAT when reading host object files. This is needed
in order to link programs with support libraries that are of a more
modern vintage than GCC 5.X.

If a given section XYZ is in COMDAT, the symbol for that section will
be flagged, e.g. section 'Characteristics' field will have the
IMAGE_SCN_LNK_COMDAT bit set, and the symbol will be followed by an
"aux" symbol that includes the COMDAT handling strategy that the
linker needs to use.

This patch supports two COMDAT strategies (IMAGE_COMDAT_SELECT_ANY and
IMAGE_COMDAT_SELECT_SAME_SIZE); more work will have to be done in the
future to support other flavors if it turns out that they are needed.

Updates #35006.

Change-Id: I516e825c30ed3df94ba08323b8a24fb847e10c1a
Reviewed-on: https://go-review.googlesource.com/c/go/+/383835
Reviewed-by: Cherry Mui <cherryyz@google.com>
Trust: Than McIntosh <thanm@google.com>
Run-TryBot: Than McIntosh <thanm@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>

src/cmd/link/internal/loadpe/ldpe.go

index 871ec73e01fdeac48c251a69968f0d1e96b3404b..bfe2e837c9bc71d0fbb31f13e9a4b10a599c03e0 100644 (file)
@@ -135,17 +135,6 @@ const (
        IMAGE_REL_ARM64_REL32            = 0x0011
 )
 
-// TODO(crawshaw): de-duplicate these symbols with cmd/internal/ld, ideally in debug/pe.
-const (
-       IMAGE_SCN_CNT_CODE               = 0x00000020
-       IMAGE_SCN_CNT_INITIALIZED_DATA   = 0x00000040
-       IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080
-       IMAGE_SCN_MEM_DISCARDABLE        = 0x02000000
-       IMAGE_SCN_MEM_EXECUTE            = 0x20000000
-       IMAGE_SCN_MEM_READ               = 0x40000000
-       IMAGE_SCN_MEM_WRITE              = 0x80000000
-)
-
 // TODO(brainman): maybe just add ReadAt method to bio.Reader instead of creating peBiobuf
 
 // peBiobuf makes bio.Reader look like io.ReaderAt.
@@ -179,12 +168,19 @@ type peLoaderState struct {
        l               *loader.Loader
        arch            *sys.Arch
        f               *pe.File
+       pn              string
        sectsyms        map[*pe.Section]loader.Sym
        defWithImp      map[string]struct{}
+       comdats         map[uint16]int64 // key is section index, val is size
        sectdata        map[*pe.Section][]byte
        localSymVersion int
 }
 
+// comdatDefinitions records the names of symbols for which we've
+// previously seen a definition in COMDAT. Key is symbol name, value
+// is symbol size (or -1 if we're using the "any" strategy).
+var comdatDefinitions = make(map[string]int64)
+
 // Load loads the PE file pn from input.
 // Symbols are written into syms, and a slice of the text symbols is returned.
 // If an .rsrc section or set of .rsrc$xx sections is found, its symbols are
@@ -196,6 +192,7 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read
                sectsyms:        make(map[*pe.Section]loader.Sym),
                sectdata:        make(map[*pe.Section][]byte),
                localSymVersion: localSymVersion,
+               pn:              pn,
        }
 
        // Some input files are archives containing multiple of
@@ -216,11 +213,11 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read
 
        // create symbols for mapped sections
        for _, sect := range f.Sections {
-               if sect.Characteristics&IMAGE_SCN_MEM_DISCARDABLE != 0 {
+               if sect.Characteristics&pe.IMAGE_SCN_MEM_DISCARDABLE != 0 {
                        continue
                }
 
-               if sect.Characteristics&(IMAGE_SCN_CNT_CODE|IMAGE_SCN_CNT_INITIALIZED_DATA|IMAGE_SCN_CNT_UNINITIALIZED_DATA) == 0 {
+               if sect.Characteristics&(pe.IMAGE_SCN_CNT_CODE|pe.IMAGE_SCN_CNT_INITIALIZED_DATA|pe.IMAGE_SCN_CNT_UNINITIALIZED_DATA) == 0 {
                        // This has been seen for .idata sections, which we
                        // want to ignore. See issues 5106 and 5273.
                        continue
@@ -230,17 +227,17 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read
                s := state.l.LookupOrCreateCgoExport(name, localSymVersion)
                bld := l.MakeSymbolUpdater(s)
 
-               switch sect.Characteristics & (IMAGE_SCN_CNT_UNINITIALIZED_DATA | IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE | IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE) {
-               case IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ: //.rdata
+               switch sect.Characteristics & (pe.IMAGE_SCN_CNT_UNINITIALIZED_DATA | pe.IMAGE_SCN_CNT_INITIALIZED_DATA | pe.IMAGE_SCN_MEM_READ | pe.IMAGE_SCN_MEM_WRITE | pe.IMAGE_SCN_CNT_CODE | pe.IMAGE_SCN_MEM_EXECUTE) {
+               case pe.IMAGE_SCN_CNT_INITIALIZED_DATA | pe.IMAGE_SCN_MEM_READ: //.rdata
                        bld.SetType(sym.SRODATA)
 
-               case IMAGE_SCN_CNT_UNINITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE: //.bss
+               case pe.IMAGE_SCN_CNT_UNINITIALIZED_DATA | pe.IMAGE_SCN_MEM_READ | pe.IMAGE_SCN_MEM_WRITE: //.bss
                        bld.SetType(sym.SNOPTRBSS)
 
-               case IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE: //.data
+               case pe.IMAGE_SCN_CNT_INITIALIZED_DATA | pe.IMAGE_SCN_MEM_READ | pe.IMAGE_SCN_MEM_WRITE: //.data
                        bld.SetType(sym.SNOPTRDATA)
 
-               case IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ: //.text
+               case pe.IMAGE_SCN_CNT_CODE | pe.IMAGE_SCN_MEM_EXECUTE | pe.IMAGE_SCN_MEM_READ: //.text
                        bld.SetType(sym.STEXT)
 
                default:
@@ -277,10 +274,10 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read
                if rsect.NumberOfRelocations == 0 {
                        continue
                }
-               if rsect.Characteristics&IMAGE_SCN_MEM_DISCARDABLE != 0 {
+               if rsect.Characteristics&pe.IMAGE_SCN_MEM_DISCARDABLE != 0 {
                        continue
                }
-               if rsect.Characteristics&(IMAGE_SCN_CNT_CODE|IMAGE_SCN_CNT_INITIALIZED_DATA|IMAGE_SCN_CNT_UNINITIALIZED_DATA) == 0 {
+               if rsect.Characteristics&(pe.IMAGE_SCN_CNT_CODE|pe.IMAGE_SCN_CNT_INITIALIZED_DATA|pe.IMAGE_SCN_CNT_UNINITIALIZED_DATA) == 0 {
                        // This has been seen for .idata sections, which we
                        // want to ignore. See issues 5106 and 5273.
                        continue
@@ -465,6 +462,16 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read
                        return nil, nil, nil
                }
 
+               // Check for COMDAT symbol.
+               if sz, ok1 := state.comdats[uint16(pesym.SectionNumber-1)]; ok1 {
+                       if psz, ok2 := comdatDefinitions[l.SymName(s)]; ok2 {
+                               if sz == psz {
+                                       //  OK to discard, we've seen an instance
+                                       // already.
+                                       continue
+                               }
+                       }
+               }
                if l.OuterSym(s) != 0 {
                        if l.AttrDuplicateOK(s) {
                                continue
@@ -486,6 +493,14 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read
                        }
                        bld.SetExternal(true)
                }
+               if sz, ok := state.comdats[uint16(pesym.SectionNumber-1)]; ok {
+                       // This is a COMDAT definition. Record that we're picking
+                       // this instance so that we can ignore future defs.
+                       if _, ok := comdatDefinitions[l.SymName(s)]; ok {
+                               return nil, nil, fmt.Errorf("internal error: preexisting COMDAT definition for %q", name)
+                       }
+                       comdatDefinitions[l.SymName(s)] = sz
+               }
        }
 
        // Sort outer lists by address, adding to textp.
@@ -583,8 +598,20 @@ func (state *peLoaderState) readpesym(pesym *pe.COFFSymbol) (*loader.SymbolBuild
 // reading and looks for cases where we have both a symbol definition
 // for "XXX" and an "__imp_XXX" symbol, recording these cases in a map
 // in the state struct. This information will be used in readpesym()
-// above to give such symbols special treatment.
+// above to give such symbols special treatment. This function also
+// gathers information about COMDAT sections/symbols for later use
+// in readpesym().
 func (state *peLoaderState) preprocessSymbols() error {
+
+       // Locate comdat sections.
+       state.comdats = make(map[uint16]int64)
+       for i, s := range state.f.Sections {
+               if s.Characteristics&uint32(pe.IMAGE_SCN_LNK_COMDAT) != 0 {
+                       state.comdats[uint16(i)] = int64(s.Size)
+               }
+       }
+
+       // Examine symbol defs.
        imp := make(map[string]struct{})
        def := make(map[string]struct{})
        for i, numaux := 0, 0; i < len(state.f.COFFSymbols); i += numaux + 1 {
@@ -601,6 +628,29 @@ func (state *peLoaderState) preprocessSymbols() error {
                if strings.HasPrefix(symname, "__imp_") {
                        imp[strings.TrimPrefix(symname, "__imp_")] = struct{}{}
                }
+               if _, isc := state.comdats[uint16(pesym.SectionNumber-1)]; !isc {
+                       continue
+               }
+               if pesym.StorageClass != uint8(IMAGE_SYM_CLASS_STATIC) {
+                       continue
+               }
+               // This symbol corresponds to a COMDAT section. Read the
+               // aux data for it.
+               auxsymp, err := state.f.COFFSymbolReadSectionDefAux(i)
+               if err != nil {
+                       return fmt.Errorf("unable to read aux info for section def symbol %d %s: pe.COFFSymbolReadComdatInfo returns %v", i, symname, err)
+               }
+               if auxsymp.Selection == pe.IMAGE_COMDAT_SELECT_SAME_SIZE {
+                       // This is supported.
+               } else if auxsymp.Selection == pe.IMAGE_COMDAT_SELECT_ANY {
+                       // Also supported.
+                       state.comdats[uint16(pesym.SectionNumber-1)] = int64(-1)
+               } else {
+                       // We don't support any of the other strategies at the
+                       // moment. I suspect that we may need to also support
+                       // "associative", we'll see.
+                       return fmt.Errorf("internal error: unsupported COMDAT selection strategy found in path=%s sec=%d strategy=%d idx=%d, please file a bug", state.pn, auxsymp.SecNum, auxsymp.Selection, i)
+               }
        }
        state.defWithImp = make(map[string]struct{})
        for n := range imp {