From: Than McIntosh Date: Mon, 7 Feb 2022 20:00:46 +0000 (-0500) Subject: cmd/link/internal/loadpe: add rudimentary COMDAT support X-Git-Tag: go1.19beta1~846 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=1edc2769ca5eb60293747c37e765bc56ce04d2da;p=gostls13.git cmd/link/internal/loadpe: add rudimentary COMDAT support 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 Trust: Than McIntosh Run-TryBot: Than McIntosh TryBot-Result: Gopher Robot --- diff --git a/src/cmd/link/internal/loadpe/ldpe.go b/src/cmd/link/internal/loadpe/ldpe.go index 871ec73e01..bfe2e837c9 100644 --- a/src/cmd/link/internal/loadpe/ldpe.go +++ b/src/cmd/link/internal/loadpe/ldpe.go @@ -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 {