diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index 5b6dabb62b58cb..5a9312447cadbd 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -1828,7 +1828,7 @@ func (state *dodataState) allocateDataSectionForSym(seg *sym.Segment, s loader.S sname = ".go." + sname[len("go:"):] } sect := addsection(ldr, state.ctxt.Arch, seg, sname, rwx) - sect.Align = symalign(ldr, s) + sect.Align = state.ctxt.Target.SectAlign(symalign(ldr, s)) state.datsize = Rnd(state.datsize, int64(sect.Align)) sect.Vaddr = uint64(state.datsize) return sect @@ -1853,6 +1853,7 @@ func (state *dodataState) allocateNamedDataSection(seg *sym.Segment, sName strin } } } + sect.Align = state.ctxt.Target.SectAlign(sect.Align) state.datsize = Rnd(state.datsize, int64(sect.Align)) sect.Vaddr = uint64(state.datsize) return sect @@ -1946,7 +1947,7 @@ func (state *dodataState) allocateDataSections(ctxt *Link) { } s := state.data[sym.SMODULEDATA][0] sect := addsection(ldr, ctxt.Arch, &Segdata, ".go.module", 06) - sect.Align = symalign(ldr, s) + sect.Align = ctxt.Target.SectAlign(symalign(ldr, s)) state.datsize = Rnd(state.datsize, int64(sect.Align)) sect.Vaddr = uint64(state.datsize) ldr.SetSymSect(s, sect) @@ -2523,9 +2524,7 @@ func (ctxt *Link) textaddress() { // Could parallelize, by assigning to text // and then letting threads copy down, but probably not worth it. sect := Segtext.Sections[0] - - sect.Align = int32(Funcalign) - + sect.Align = ctxt.Target.SectAlign(int32(Funcalign)) ldr := ctxt.loader if *flagRandLayout != 0 { @@ -2757,7 +2756,7 @@ func assignAddress(ctxt *Link, sect *sym.Section, n int, s loader.Sym, va uint64 sect = addsection(ctxt.loader, ctxt.Arch, &Segtext, ".text", 05) sect.Vaddr = va - sect.Align = sectAlign + sect.Align = ctxt.Target.SectAlign(sectAlign) ldr.SetSymSect(s, sect) // Create a symbol for the start of the secondary text sections diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go index 5cd39fbc53eb90..0f785632a96bd3 100644 --- a/src/cmd/link/internal/ld/dwarf.go +++ b/src/cmd/link/internal/ld/dwarf.go @@ -2458,7 +2458,7 @@ func dwarfcompress(ctxt *Link) { compressedSegName = ".zdebug_" + ldr.SymSect(s).Name[len(".debug_"):] } sect := addsection(ctxt.loader, ctxt.Arch, &Segdwarf, compressedSegName, 04) - sect.Align = int32(ctxt.Arch.Alignment) + sect.Align = ctxt.Target.SectAlign(int32(ctxt.Arch.Alignment)) sect.Length = uint64(len(z.compressed)) sect.Compressed = true newSym := ldr.MakeSymbolBuilder(compressedSegName) @@ -2488,7 +2488,11 @@ func dwarfcompress(ctxt *Link) { sect := ldr.SymSect(s) if sect != prevSect { if ctxt.IsWindows() { - pos = uint64(Rnd(int64(pos), PEFILEALIGN)) + align := int64(PEFILEALIGN) + if ctxt.LinkMode == LinkInternal { + align = PESECTALIGN + } + pos = uint64(Rnd(int64(pos), align)) } sect.Vaddr = pos prevSect = sect diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index bcad5add4abe19..eff58c65c4bca8 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -583,6 +583,11 @@ func (ctxt *Link) loadlib() { // We now have enough information to determine the link mode. determineLinkMode(ctxt) + // Set minimum section alignment for PE files. + if ctxt.HeadType == objabi.Hwindows && ctxt.LinkMode == LinkInternal { + ctxt.Target.MinimumSectAlign = int32(PESECTALIGN) + } + if ctxt.LinkMode == LinkExternal && !iscgo && !(buildcfg.GOOS == "darwin" && ctxt.BuildMode != BuildModePlugin && ctxt.Arch.Family == sys.AMD64) { // This indicates a user requested -linkmode=external. // The startup code uses an import of runtime/cgo to decide diff --git a/src/cmd/link/internal/ld/pe.go b/src/cmd/link/internal/ld/pe.go index b49da42c4cf0c7..561e2fab547275 100644 --- a/src/cmd/link/internal/ld/pe.go +++ b/src/cmd/link/internal/ld/pe.go @@ -63,6 +63,9 @@ var ( PEFILEALIGN int64 = 2 << 8 ) +// peSectionHeaderReserve reserves header space for section headers. +const peSectionHeaderReserve = 64 + const ( IMAGE_SCN_CNT_CODE = 0x00000020 IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040 @@ -379,6 +382,27 @@ func (sect *peSection) checkSegment(seg *sym.Segment) { } } +// checkSection verifies COFF section sect matches sym.Section s. +func (sect *peSection) checkSection(s *sym.Section, linkmode LinkMode) { + wantVA := s.Vaddr - uint64(PEBASE) + if wantVA != uint64(sect.virtualAddress) { + Errorf("%s.VirtualAddress = %#x, want %#x", sect.name, uint64(int64(sect.virtualAddress)), uint64(int64(wantVA))) + errorexit() + } + if s.Vaddr < s.Seg.Vaddr+s.Seg.Filelen { + wantOff := s.Seg.Fileoff + s.Vaddr - s.Seg.Vaddr + if wantOff != uint64(sect.pointerToRawData) { + Errorf("%s.PointerToRawData = %#x, want %#x", sect.name, uint64(int64(sect.pointerToRawData)), uint64(int64(wantOff))) + errorexit() + } + return + } + if sect.pointerToRawData != 0 && linkmode != LinkExternal { + Errorf("%s.PointerToRawData = %#x, want 0 for BSS", sect.name, uint64(int64(sect.pointerToRawData))) + errorexit() + } +} + // pad adds zeros to the section sect. It writes as many bytes // as necessary to make section sect.SizeOfRawData bytes long. // It assumes that n bytes are already written to the file. @@ -445,56 +469,56 @@ type peFile struct { symtabOffset int64 // offset to the start of symbol table symbolCount int // number of symbol table records written dataDirectory [16]pe.DataDirectory + sectMap map[*sym.Section]*peSection // maps sym.Section to peSection } -// addSection adds section to the COFF file f. +// addSection adds a dynamically created section to the COFF file f. +// The section's virtual address and file offset are allocated from +// f.nextSectOffset and f.nextFileOffset. func (f *peFile) addSection(name string, sectsize int, filesize int) *peSection { + return f.appendSection(name, sectsize, filesize, f.nextSectOffset, f.nextFileOffset) +} + +// appendSection is the internal implementation for adding sections. +func (f *peFile) appendSection(name string, sectsize int, filesize int, vaddr uint32, fileoff uint32) *peSection { sect := &peSection{ name: name, shortName: name, index: len(f.sections) + 1, - virtualAddress: f.nextSectOffset, - pointerToRawData: f.nextFileOffset, + virtualAddress: vaddr, + pointerToRawData: fileoff, } - f.nextSectOffset = uint32(Rnd(int64(f.nextSectOffset)+int64(sectsize), PESECTALIGN)) if filesize > 0 { sect.virtualSize = uint32(sectsize) sect.sizeOfRawData = uint32(Rnd(int64(filesize), PEFILEALIGN)) - f.nextFileOffset += sect.sizeOfRawData } else { sect.sizeOfRawData = uint32(sectsize) } + endVA := uint32(Rnd(int64(vaddr)+int64(sectsize), PESECTALIGN)) + if endVA > f.nextSectOffset { + f.nextSectOffset = endVA + } + if filesize > 0 { + endFile := fileoff + sect.sizeOfRawData + if endFile > f.nextFileOffset { + f.nextFileOffset = endFile + } + } f.sections = append(f.sections, sect) return sect } -// addDWARFSection adds DWARF section to the COFF file f. -// This function is similar to addSection, but DWARF section names are -// longer than 8 characters, so they need to be stored in the string table. -func (f *peFile) addDWARFSection(name string, size int) *peSection { - if size == 0 { - Exitf("DWARF section %q is empty", name) - } - // DWARF section names are longer than 8 characters. - // PE format requires such names to be stored in string table, - // and section names replaced with slash (/) followed by - // correspondent string table index. - // see http://www.microsoft.com/whdc/system/platform/firmware/PECOFFdwn.mspx - // for details - off := f.stringTable.add(name) - h := f.addSection(name, size, size) - h.shortName = fmt.Sprintf("/%d", off) - h.characteristics = IMAGE_SCN_ALIGN_1BYTES | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_DISCARDABLE | IMAGE_SCN_CNT_INITIALIZED_DATA - return h -} - // addDWARF adds DWARF information to the COFF file f. -func (f *peFile) addDWARF() { +func (f *peFile) addDWARF(ctxt *Link) { if *FlagW { // disable dwarf return } for _, sect := range Segdwarf.Sections { - h := f.addDWARFSection(sect.Name, int(sect.Length)) + h := f.createSection(ctxt, sect, &Segdwarf) + if h == nil { + // createSection returns nil for empty sections + continue + } fileoff := sect.Vaddr - Segdwarf.Vaddr + Segdwarf.Fileoff if uint64(h.pointerToRawData) != fileoff { Exitf("%s.PointerToRawData = %#x, want %#x", sect.Name, h.pointerToRawData, fileoff) @@ -502,6 +526,64 @@ func (f *peFile) addDWARF() { } } +// createSection creates a PE section for the given sym.Section. +func (f *peFile) createSection(ctxt *Link, sect *sym.Section, seg *sym.Segment) *peSection { + if sect.Length == 0 { + return nil + } + + name := sect.Name + shortName := name + if len(name) > 8 { + off := f.stringTable.add(name) + shortName = fmt.Sprintf("/%d", off) + } + + fileSize := int(sect.Length) + if sect.Vaddr >= seg.Vaddr+seg.Filelen { + fileSize = 0 // BSS + } + fileoff := uint32(sect.Seg.Fileoff + sect.Vaddr - sect.Seg.Vaddr) + h := f.appendSection(name, int(sect.Length), fileSize, uint32(sect.Vaddr-uint64(PEBASE)), fileoff) + h.shortName = shortName + + switch seg { + case &Segtext: + h.characteristics = IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ + case &Segrodata: + h.characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ + case &Segrelrodata: + h.characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ + case &Segdata: + if fileSize > 0 { + h.characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE + } else { + h.characteristics = IMAGE_SCN_CNT_UNINITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE + } + case &Segdwarf: + h.characteristics = IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_DISCARDABLE | IMAGE_SCN_CNT_INITIALIZED_DATA + if ctxt.LinkMode == LinkExternal { + h.characteristics |= IMAGE_SCN_ALIGN_1BYTES + } + } + + if ctxt.LinkMode == LinkExternal && seg != &Segdwarf { + // For external linking, set explicit 32-byte alignment in COFF object. + // This ensures the external linker honors our alignment requirements. + // DWARF sections use 1-byte alignment as they don't need special alignment. + h.characteristics |= IMAGE_SCN_ALIGN_32BYTES + } + if fileSize == 0 { + // BSS section: no file data, but size is stored in sizeOfRawData + // (which was correctly set by appendSection). + // GNU ld and lld expect BSS size in sizeOfRawData, not virtualSize. + h.pointerToRawData = 0 + } + + f.sectMap[sect] = h + return h +} + // addSEH adds SEH information to the COFF file f. func (f *peFile) addSEH(ctxt *Link) { // .pdata section can exist without the .xdata section. @@ -509,29 +591,66 @@ func (f *peFile) addSEH(ctxt *Link) { if Segpdata.Length == 0 { return } - d := pefile.addSection(".pdata", int(Segpdata.Length), int(Segpdata.Length)) - d.characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ - if ctxt.LinkMode == LinkExternal { - // Some gcc versions don't honor the default alignment for the .pdata section. - d.characteristics |= IMAGE_SCN_ALIGN_4BYTES - } - pefile.pdataSect = d - d.checkSegment(&Segpdata) - pefile.dataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress = d.virtualAddress - pefile.dataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size = d.virtualSize - - if Segxdata.Length > 0 { - d = pefile.addSection(".xdata", int(Segxdata.Length), int(Segxdata.Length)) + for _, sect := range Segpdata.Sections { + fileoff := sect.Vaddr - Segpdata.Vaddr + Segpdata.Fileoff + d := f.appendSection(sect.Name, int(sect.Length), int(sect.Length), uint32(sect.Vaddr-uint64(PEBASE)), uint32(fileoff)) d.characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ if ctxt.LinkMode == LinkExternal { - // Some gcc versions don't honor the default alignment for the .xdata section. + // Some gcc versions don't honor the default alignment for the .pdata section. d.characteristics |= IMAGE_SCN_ALIGN_4BYTES } - pefile.xdataSect = d - d.checkSegment(&Segxdata) + f.sectMap[sect] = d + if sect.Name == ".pdata" { + f.pdataSect = d + } + } + if f.pdataSect == nil { + return + } + f.dataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress = f.pdataSect.virtualAddress + f.dataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size = f.pdataSect.virtualSize + + if Segxdata.Length > 0 { + for _, sect := range Segxdata.Sections { + fileoff := sect.Vaddr - Segxdata.Vaddr + Segxdata.Fileoff + d := f.appendSection(sect.Name, int(sect.Length), int(sect.Length), uint32(sect.Vaddr-uint64(PEBASE)), uint32(fileoff)) + d.characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ + if ctxt.LinkMode == LinkExternal { + // Some gcc versions don't honor the default alignment for the .xdata section. + d.characteristics |= IMAGE_SCN_ALIGN_4BYTES + } + f.sectMap[sect] = d + if sect.Name == ".xdata" { + f.xdataSect = d + } + } } } +// syncNextOffsets sets next offsets to the end of the segment layout. +func (f *peFile) syncNextOffsets() { + var maxFile uint64 + var maxVA uint64 + for _, seg := range Segments { + if seg.Length == 0 && seg.Filelen == 0 { + continue + } + fileEnd := seg.Fileoff + uint64(Rnd(int64(seg.Filelen), PEFILEALIGN)) + if fileEnd > maxFile { + maxFile = fileEnd + } + vaEnd := seg.Vaddr + uint64(Rnd(int64(seg.Length), PESECTALIGN)) + if vaEnd > maxVA { + maxVA = vaEnd + } + } + if maxVA < uint64(PEBASE) { + maxVA = uint64(PEBASE) + } + f.nextFileOffset = uint32(maxFile) + f.nextSectOffset = uint32(maxVA - uint64(PEBASE)) +} + // addInitArray adds .ctors COFF section to the file f. func (f *peFile) addInitArray(ctxt *Link) *peSection { // The size below was determined by the specification for array relocations, @@ -623,30 +742,43 @@ func (f *peFile) emitRelocations(ctxt *Link) { return int(sect.Rellen / relocLen) } - type relsect struct { - peSect *peSection - seg *sym.Segment - syms []loader.Sym + emitForSection := func(sect *sym.Section, syms []loader.Sym) { + pesect := f.sectMap[sect] + if pesect == nil { + if sect.Length == 0 { + return + } + Errorf("emitRelocations: could not find %q section", sect.Name) + return + } + pesect.emitRelocations(ctxt.Out, func() int { + return relocsect(sect, syms, sect.Vaddr) + }) + } + + for _, sect := range Segtext.Sections { + emitForSection(sect, ctxt.Textp) } - sects := []relsect{ - {f.textSect, &Segtext, ctxt.Textp}, - {f.rdataSect, &Segrodata, ctxt.datap}, - {f.dataSect, &Segdata, ctxt.datap}, + for _, sect := range Segrodata.Sections { + emitForSection(sect, ctxt.datap) } + for _, sect := range Segrelrodata.Sections { + emitForSection(sect, ctxt.datap) + } + for _, sect := range Segdata.Sections { + emitForSection(sect, ctxt.datap) + } + + // Handle SEH sections if len(sehp.pdata) != 0 { - sects = append(sects, relsect{f.pdataSect, &Segpdata, sehp.pdata}) + for _, sect := range Segpdata.Sections { + emitForSection(sect, sehp.pdata) + } } if len(sehp.xdata) != 0 { - sects = append(sects, relsect{f.xdataSect, &Segxdata, sehp.xdata}) - } - for _, s := range sects { - s.peSect.emitRelocations(ctxt.Out, func() int { - var n int - for _, sect := range s.seg.Sections { - n += relocsect(sect, s.syms, s.seg.Vaddr) - } - return n - }) + for _, sect := range Segxdata.Sections { + emitForSection(sect, sehp.xdata) + } } dwarfLoop: @@ -657,13 +789,11 @@ dwarfLoop: ldr.SymSect(si.secSym()) != sect { panic("inconsistency between dwarfp and Segdwarf") } - for _, pesect := range f.sections { - if sect.Name == pesect.name { - pesect.emitRelocations(ctxt.Out, func() int { - return relocsect(sect, si.syms, sect.Vaddr) - }) - continue dwarfLoop - } + if pesect := f.sectMap[sect]; pesect != nil { + pesect.emitRelocations(ctxt.Out, func() int { + return relocsect(sect, si.syms, sect.Vaddr) + }) + continue dwarfLoop } Errorf("emitRelocations: could not find %q section", sect.Name) } @@ -719,28 +849,14 @@ func (f *peFile) mapToPESection(ldr *loader.Loader, s loader.Sym, linkmode LinkM if sect == nil { return 0, 0, fmt.Errorf("could not map %s symbol with no section", ldr.SymName(s)) } - if sect.Seg == &Segtext { - return f.textSect.index, int64(uint64(ldr.SymValue(s)) - Segtext.Vaddr), nil - } - if sect.Seg == &Segrodata { - return f.rdataSect.index, int64(uint64(ldr.SymValue(s)) - Segrodata.Vaddr), nil - } - if sect.Seg != &Segdata { - return 0, 0, fmt.Errorf("could not map %s symbol with non .text or .rdata or .data section", ldr.SymName(s)) - } - v := uint64(ldr.SymValue(s)) - Segdata.Vaddr - if linkmode != LinkExternal { - return f.dataSect.index, int64(v), nil - } - if ldr.SymType(s).IsDATA() { - return f.dataSect.index, int64(v), nil - } - // Note: although address of runtime.edata (type sym.SDATA) is at the start of .bss section - // it still belongs to the .data section, not the .bss section. - if v < Segdata.Filelen { - return f.dataSect.index, int64(v), nil + + if pesect, ok := f.sectMap[sect]; ok { + offset = int64(uint64(ldr.SymValue(s)) - sect.Vaddr) + return pesect.index, offset, nil } - return f.bssSect.index, int64(v - Segdata.Filelen), nil + + return 0, 0, fmt.Errorf("could not map %s symbol: section %s not in sectMap", + ldr.SymName(s), sect.Name) } var isLabel = make(map[loader.Sym]bool) @@ -952,12 +1068,39 @@ func (f *peFile) writeFileHeader(ctxt *Link) { func (f *peFile) writeOptionalHeader(ctxt *Link) { var oh pe.OptionalHeader32 var oh64 pe.OptionalHeader64 + sumSections := func(mask uint32) uint32 { + var total uint32 + for _, sect := range f.sections { + if sect.characteristics&mask == mask { + total += sect.sizeOfRawData + } + } + return total + } + minVA := func(mask uint32) (uint32, bool) { + var min uint32 + var ok bool + for _, sect := range f.sections { + if sect.characteristics&mask != mask { + continue + } + if !ok || sect.virtualAddress < min { + min = sect.virtualAddress + ok = true + } + } + return min, ok + } if pe64 { oh64.Magic = 0x20b // PE32+ } else { oh.Magic = 0x10b // PE32 - oh.BaseOfData = f.dataSect.virtualAddress + if f.dataSect != nil { + oh.BaseOfData = f.dataSect.virtualAddress + } else if base, ok := minVA(IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_WRITE); ok { + oh.BaseOfData = base + } } // Fill out both oh64 and oh. We only use one. Oh well. @@ -965,18 +1108,23 @@ func (f *peFile) writeOptionalHeader(ctxt *Link) { oh.MajorLinkerVersion = 3 oh64.MinorLinkerVersion = 0 oh.MinorLinkerVersion = 0 - oh64.SizeOfCode = f.textSect.sizeOfRawData - oh.SizeOfCode = f.textSect.sizeOfRawData - oh64.SizeOfInitializedData = f.dataSect.sizeOfRawData - oh.SizeOfInitializedData = f.dataSect.sizeOfRawData + oh64.SizeOfCode = sumSections(IMAGE_SCN_CNT_CODE) + oh.SizeOfCode = oh64.SizeOfCode + oh64.SizeOfInitializedData = sumSections(IMAGE_SCN_CNT_INITIALIZED_DATA) + oh.SizeOfInitializedData = oh64.SizeOfInitializedData oh64.SizeOfUninitializedData = 0 oh.SizeOfUninitializedData = 0 if ctxt.LinkMode != LinkExternal { oh64.AddressOfEntryPoint = uint32(Entryvalue(ctxt) - PEBASE) oh.AddressOfEntryPoint = uint32(Entryvalue(ctxt) - PEBASE) } - oh64.BaseOfCode = f.textSect.virtualAddress - oh.BaseOfCode = f.textSect.virtualAddress + if f.textSect != nil { + oh64.BaseOfCode = f.textSect.virtualAddress + oh.BaseOfCode = f.textSect.virtualAddress + } else if base, ok := minVA(IMAGE_SCN_CNT_CODE); ok { + oh64.BaseOfCode = base + oh.BaseOfCode = base + } oh64.ImageBase = uint64(PEBASE) oh.ImageBase = uint32(PEBASE) oh64.SectionAlignment = uint32(PESECTALIGN) @@ -1121,7 +1269,7 @@ func Peinit(ctxt *Link) { } } - var sh [16]pe.SectionHeader32 + var sh [peSectionHeaderReserve]pe.SectionHeader32 var fh pe.FileHeader PEFILEHEADR = int32(Rnd(int64(len(dosstub)+binary.Size(&fh)+l+binary.Size(&sh)), PEFILEALIGN)) if ctxt.LinkMode != LinkExternal { @@ -1274,10 +1422,14 @@ func peimporteddlls() []string { return dlls } -func addimports(ctxt *Link, datsect *peSection) { +func addimports(ctxt *Link) { ldr := ctxt.loader startoff := ctxt.Out.Offset() dynamic := ldr.LookupOrCreateSym(".windynamic", 0) + dynsect := pefile.sectMap[ldr.SymSect(dynamic)] + if dynsect == nil { + Exitf("missing PE section for .windynamic") + } // skip import descriptor table (will write it later) n := uint64(0) @@ -1332,10 +1484,10 @@ func addimports(ctxt *Link, datsect *peSection) { isect.pad(ctxt.Out, uint32(n)) endoff := ctxt.Out.Offset() - // write FirstThunks (allocated in .data section) - ftbase := uint64(ldr.SymValue(dynamic)) - uint64(datsect.virtualAddress) - uint64(PEBASE) + // write FirstThunks (allocated in .windynamic section) + ftbase := uint64(ldr.SymValue(dynamic)) - uint64(dynsect.virtualAddress) - uint64(PEBASE) - ctxt.Out.SeekSet(int64(uint64(datsect.pointerToRawData) + ftbase)) + ctxt.Out.SeekSet(int64(uint64(dynsect.pointerToRawData) + ftbase)) for d := dr; d != nil; d = d.next { for m := d.ms; m != nil; m = m.next { if pe64 { @@ -1361,7 +1513,7 @@ func addimports(ctxt *Link, datsect *peSection) { out.Write32(0) out.Write32(0) out.Write32(uint32(uint64(isect.virtualAddress) + d.nameoff)) - out.Write32(uint32(uint64(datsect.virtualAddress) + ftbase + d.thunkoff)) + out.Write32(uint32(uint64(dynsect.virtualAddress) + ftbase + d.thunkoff)) } out.Write32(0) //end @@ -1692,46 +1844,52 @@ func addpersrc(ctxt *Link) { } func asmbPe(ctxt *Link) { - t := pefile.addSection(".text", int(Segtext.Length), int(Segtext.Length)) - t.characteristics = IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ - if ctxt.LinkMode == LinkExternal { - // some data symbols (e.g. masks) end up in the .text section, and they normally - // expect larger alignment requirement than the default text section alignment. - t.characteristics |= IMAGE_SCN_ALIGN_32BYTES - } - t.checkSegment(&Segtext) - pefile.textSect = t + pefile.sectMap = make(map[*sym.Section]*peSection) - ro := pefile.addSection(".rdata", int(Segrodata.Length), int(Segrodata.Length)) - ro.characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ - if ctxt.LinkMode == LinkExternal { - // some data symbols (e.g. masks) end up in the .rdata section, and they normally - // expect larger alignment requirement than the default text section alignment. - ro.characteristics |= IMAGE_SCN_ALIGN_32BYTES + for _, sect := range Segtext.Sections { + h := pefile.createSection(ctxt, sect, &Segtext) + if h == nil { + continue + } + if sect.Name == ".text" && pefile.textSect == nil { + pefile.textSect = h + h.checkSection(sect, ctxt.LinkMode) + } } - ro.checkSegment(&Segrodata) - pefile.rdataSect = ro - - var d *peSection - if ctxt.LinkMode != LinkExternal { - d = pefile.addSection(".data", int(Segdata.Length), int(Segdata.Filelen)) - d.characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE - d.checkSegment(&Segdata) - pefile.dataSect = d - } else { - d = pefile.addSection(".data", int(Segdata.Filelen), int(Segdata.Filelen)) - d.characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE | IMAGE_SCN_ALIGN_32BYTES - d.checkSegment(&Segdata) - pefile.dataSect = d - - b := pefile.addSection(".bss", int(Segdata.Length-Segdata.Filelen), 0) - b.characteristics = IMAGE_SCN_CNT_UNINITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE | IMAGE_SCN_ALIGN_32BYTES - b.pointerToRawData = 0 - pefile.bssSect = b + for _, sect := range Segrodata.Sections { + h := pefile.createSection(ctxt, sect, &Segrodata) + if h == nil { + continue + } + if sect.Name == ".rodata" { + pefile.rdataSect = h + h.checkSection(sect, ctxt.LinkMode) + } else if pefile.rdataSect == nil { + pefile.rdataSect = h + } + } + for _, sect := range Segrelrodata.Sections { + _ = pefile.createSection(ctxt, sect, &Segrelrodata) + } + for _, sect := range Segdata.Sections { + h := pefile.createSection(ctxt, sect, &Segdata) + if h == nil { + continue + } + if sect.Name == ".data" { + pefile.dataSect = h + h.checkSection(sect, ctxt.LinkMode) + } else if pefile.dataSect == nil && h.characteristics&IMAGE_SCN_MEM_WRITE != 0 && h.characteristics&IMAGE_SCN_CNT_INITIALIZED_DATA != 0 { + pefile.dataSect = h + } + if sect.Name == ".bss" { + pefile.bssSect = h + } } pefile.addSEH(ctxt) - pefile.addDWARF() + pefile.addDWARF(ctxt) + pefile.syncNextOffsets() if ctxt.LinkMode == LinkExternal { pefile.ctorsSect = pefile.addInitArray(ctxt) @@ -1739,7 +1897,7 @@ func asmbPe(ctxt *Link) { ctxt.Out.SeekSet(int64(pefile.nextFileOffset)) if ctxt.LinkMode != LinkExternal { - addimports(ctxt, d) + addimports(ctxt) addexports(ctxt) addPEBaseReloc(ctxt) } @@ -1748,6 +1906,9 @@ func asmbPe(ctxt *Link) { if ctxt.LinkMode == LinkExternal { pefile.emitRelocations(ctxt) } + if len(pefile.sections) > peSectionHeaderReserve { + Exitf("too many PE sections (%d), increase peSectionHeaderReserve", len(pefile.sections)) + } pewrite(ctxt) } diff --git a/src/cmd/link/internal/ld/target.go b/src/cmd/link/internal/ld/target.go index d0ce99f3e9f9ef..afd7e36497ce1c 100644 --- a/src/cmd/link/internal/ld/target.go +++ b/src/cmd/link/internal/ld/target.go @@ -19,9 +19,10 @@ type Target struct { LinkMode LinkMode BuildMode BuildMode - linkShared bool - canUsePlugins bool - IsELF bool + linkShared bool + canUsePlugins bool + IsELF bool + MinimumSectAlign int32 // minimum section alignment for the target } // @@ -204,3 +205,9 @@ func (t *Target) UsesLibc() bool { } return false } + +// SectAlign returns the alignment adjusted to meet the target's minimum +// section alignment requirement. +func (t *Target) SectAlign(align int32) int32 { + return max(align, t.MinimumSectAlign) +} diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go index bc7504e5b1c39a..98437a86af9d91 100644 --- a/src/cmd/link/link_test.go +++ b/src/cmd/link/link_test.go @@ -2205,16 +2205,77 @@ func TestModuledataPlacement(t *testing.T) { } } - case pf != nil, xf != nil: - if pf != nil { - defer pf.Close() + case pf != nil: + defer pf.Close() + + var moddataSym *pe.Symbol + for _, sym := range pf.Symbols { + if sym.Name == moddataSymName || sym.Name == "_"+moddataSymName { + moddataSym = sym + break + } } - if xf != nil { - defer xf.Close() + if moddataSym == nil { + t.Fatalf("could not find symbol %s", moddataSymName) } + if moddataSym.SectionNumber <= 0 { + t.Fatalf("moduledata not in a section (section number %d)", moddataSym.SectionNumber) + } + sec := pf.Sections[moddataSym.SectionNumber-1] + if sec.Name != ".go.module" { + t.Errorf("moduledata in section %s, not .go.module", sec.Name) + } + if moddataSym.Value != 0 { + t.Errorf("moduledata offset %#x != 0", moddataSym.Value) + } + + case xf != nil: + defer xf.Close() - // On Windows and AIX all the Go specific sections + // On AIX all the Go specific sections // get stuffed into a few sections, // so there is nothing to test here. } } + +func TestPEEdataSection(t *testing.T) { + testenv.MustHaveGoBuild(t) + t.Parallel() + + tmpdir := t.TempDir() + src := filepath.Join(tmpdir, "x.go") + if err := os.WriteFile(src, []byte(trivialSrc), 0o444); err != nil { + t.Fatal(err) + } + + exe := filepath.Join(tmpdir, "x.exe") + cmd := goCmd(t, "build", "-o", exe, src) + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("build failed; %v, output:\n%s", err, out) + } + + pf, err := pe.Open(exe) + if err != nil { + t.Skip("not a PE executable") + } + defer pf.Close() + + var edataSym *pe.Symbol + for _, sym := range pf.Symbols { + if sym.Name == "runtime.edata" || sym.Name == "_runtime.edata" { + edataSym = sym + break + } + } + if edataSym == nil { + t.Fatal("could not find symbol runtime.edata") + } + if edataSym.SectionNumber <= 0 { + t.Fatalf("runtime.edata not in a section (section number %d)", edataSym.SectionNumber) + } + + sec := pf.Sections[edataSym.SectionNumber-1] + if sec.Name != ".data" { + t.Errorf("runtime.edata in section %s, not .data", sec.Name) + } +} diff --git a/src/cmd/link/pe_test.go b/src/cmd/link/pe_test.go new file mode 100644 index 00000000000000..a99523c5526c5f --- /dev/null +++ b/src/cmd/link/pe_test.go @@ -0,0 +1,472 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build windows + +package main + +import ( + "debug/pe" + "fmt" + "internal/testenv" + "os" + "path/filepath" + "sort" + "testing" +) + +func TestPESectionsReadOnly(t *testing.T) { + t.Parallel() + testenv.MustHaveGoBuild(t) + + const ( + prog = `package main; func main() {}` + progC = `package main; import "C"; func main() {}` + ) + + tests := []struct { + name string + args []string + prog string + wantSecsRO [][]string // each entry is a list of alternative names (first found wins) + wantSecsROIfPresent [][]string + mustHaveCGO bool + mustInternalLink bool + }{ + { + name: "linkmode-internal", + args: []string{"-ldflags", "-linkmode=internal"}, + prog: prog, + mustInternalLink: true, + wantSecsRO: [][]string{{".rodata"}, {".gopclntab"}}, + wantSecsROIfPresent: [][]string{ + {".typelink"}, + {".itablink"}, + }, + }, + { + name: "linkmode-external", + args: []string{"-ldflags", "-linkmode=external"}, + prog: progC, + mustHaveCGO: true, + // External linkers may truncate section names to 8 characters (lld) + // or preserve long names via string table (GNU ld). + wantSecsRO: [][]string{{".rodata"}, {".gopclntab", ".gopclnt"}}, + wantSecsROIfPresent: [][]string{ + {".typelink", ".typelin"}, + {".itablink", ".itablin"}, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if test.mustInternalLink { + testenv.MustInternalLink(t, testenv.SpecialBuildTypes{Cgo: test.mustHaveCGO}) + } + if test.mustHaveCGO { + testenv.MustHaveCGO(t) + } + + dir := t.TempDir() + src := filepath.Join(dir, fmt.Sprintf("pe_%s.go", test.name)) + binFile := filepath.Join(dir, test.name) + + if err := os.WriteFile(src, []byte(test.prog), 0666); err != nil { + t.Fatal(err) + } + + cmdArgs := append([]string{"build", "-o", binFile}, append(test.args, src)...) + cmd := goCmd(t, cmdArgs...) + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("failed to build %v: %v:\n%s", cmd.Args, err, out) + } + + pf, err := pe.Open(binFile) + if err != nil { + t.Fatalf("failed to open PE file: %v", err) + } + defer pf.Close() + + secByName := make(map[string]*pe.Section, len(pf.Sections)) + for _, sec := range pf.Sections { + secByName[sec.Name] = sec + } + + // checkRO checks that one of the alternative section names exists and is read-only. + // names is a list of alternative names (first found wins). + checkRO := func(names []string, required bool) { + var sec *pe.Section + var foundName string + for _, name := range names { + if s := secByName[name]; s != nil { + sec = s + foundName = name + break + } + } + if sec == nil { + if required { + t.Fatalf("test %s: can't locate any of %q sections", test.name, names) + } + return + } + if sec.Characteristics&pe.IMAGE_SCN_MEM_READ == 0 { + t.Errorf("section %s missing IMAGE_SCN_MEM_READ", foundName) + } + if sec.Characteristics&pe.IMAGE_SCN_MEM_WRITE != 0 { + t.Errorf("section %s unexpectedly writable", foundName) + } + } + + for _, names := range test.wantSecsRO { + checkRO(names, true) + } + for _, names := range test.wantSecsROIfPresent { + checkRO(names, false) + } + }) + } +} + +func TestPESectionAlignment(t *testing.T) { + t.Parallel() + testenv.MustHaveGoBuild(t) + testenv.MustInternalLink(t, testenv.SpecialBuildTypes{}) + + const prog = `package main; func main() { println("hello") }` + + dir := t.TempDir() + src := filepath.Join(dir, "align.go") + binFile := filepath.Join(dir, "align.exe") + + if err := os.WriteFile(src, []byte(prog), 0666); err != nil { + t.Fatal(err) + } + + cmd := goCmd(t, "build", "-o", binFile, "-ldflags", "-linkmode=internal", src) + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("failed to build: %v:\n%s", err, out) + } + + pf, err := pe.Open(binFile) + if err != nil { + t.Fatalf("failed to open PE file: %v", err) + } + defer pf.Close() + + // Get section alignment from optional header + var sectionAlignment uint32 + switch oh := pf.OptionalHeader.(type) { + case *pe.OptionalHeader32: + sectionAlignment = oh.SectionAlignment + case *pe.OptionalHeader64: + sectionAlignment = oh.SectionAlignment + default: + t.Fatal("unknown optional header type") + } + + if sectionAlignment != 0x1000 { + t.Errorf("unexpected section alignment: got %#x, want %#x", sectionAlignment, 0x1000) + } + + // Verify all sections are aligned to section alignment + for _, sec := range pf.Sections { + if sec.VirtualAddress%sectionAlignment != 0 { + t.Errorf("section %s virtual address %#x not aligned to %#x", + sec.Name, sec.VirtualAddress, sectionAlignment) + } + } +} + +func TestPESectionCharacteristics(t *testing.T) { + t.Parallel() + testenv.MustHaveGoBuild(t) + testenv.MustInternalLink(t, testenv.SpecialBuildTypes{}) + + const prog = ` +package main + +var globalData = []int{1, 2, 3} +var globalBss [1024]byte + +func main() { + println(globalData[0]) + globalBss[0] = 1 +} +` + + dir := t.TempDir() + src := filepath.Join(dir, "chars.go") + binFile := filepath.Join(dir, "chars.exe") + + if err := os.WriteFile(src, []byte(prog), 0666); err != nil { + t.Fatal(err) + } + + cmd := goCmd(t, "build", "-o", binFile, "-ldflags", "-linkmode=internal", src) + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("failed to build: %v:\n%s", err, out) + } + + pf, err := pe.Open(binFile) + if err != nil { + t.Fatalf("failed to open PE file: %v", err) + } + defer pf.Close() + + tests := []struct { + name string + wantFlags uint32 + wantClear uint32 + }{ + { + name: ".text", + wantFlags: pe.IMAGE_SCN_CNT_CODE | pe.IMAGE_SCN_MEM_EXECUTE | pe.IMAGE_SCN_MEM_READ, + wantClear: pe.IMAGE_SCN_MEM_WRITE, + }, + { + name: ".rodata", + wantFlags: pe.IMAGE_SCN_CNT_INITIALIZED_DATA | pe.IMAGE_SCN_MEM_READ, + wantClear: pe.IMAGE_SCN_MEM_WRITE | pe.IMAGE_SCN_MEM_EXECUTE, + }, + { + name: ".data", + wantFlags: pe.IMAGE_SCN_CNT_INITIALIZED_DATA | pe.IMAGE_SCN_MEM_READ | pe.IMAGE_SCN_MEM_WRITE, + wantClear: pe.IMAGE_SCN_MEM_EXECUTE, + }, + } + + for _, test := range tests { + sec := pf.Section(test.name) + if sec == nil { + t.Errorf("section %s not found", test.name) + continue + } + if sec.Characteristics&test.wantFlags != test.wantFlags { + t.Errorf("section %s: want flags %#x set, got characteristics %#x", + test.name, test.wantFlags, sec.Characteristics) + } + if sec.Characteristics&test.wantClear != 0 { + t.Errorf("section %s: want flags %#x clear, got characteristics %#x", + test.name, test.wantClear, sec.Characteristics) + } + } +} + +func TestPESectionNoOverlap(t *testing.T) { + t.Parallel() + testenv.MustHaveGoBuild(t) + testenv.MustInternalLink(t, testenv.SpecialBuildTypes{}) + + const prog = ` +package main + +import "reflect" + +type A string +type B int + +type describer interface{ What() string } + +func (a *A) What() string { return "string" } +func (b *B) What() string { return "int" } + +func example(a A, b B) describer { + if b == 1 { + return &a + } + return &b +} + +func main() { + println(example("", 1).What()) + println(reflect.TypeOf(example("", 1)).String()) +} +` + + dir := t.TempDir() + src := filepath.Join(dir, "overlap.go") + binFile := filepath.Join(dir, "overlap.exe") + + if err := os.WriteFile(src, []byte(prog), 0666); err != nil { + t.Fatal(err) + } + + cmd := goCmd(t, "build", "-o", binFile, "-ldflags", "-linkmode=internal", src) + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("failed to build: %v:\n%s", err, out) + } + + pf, err := pe.Open(binFile) + if err != nil { + t.Fatalf("failed to open PE file: %v", err) + } + defer pf.Close() + + // Collect sections with non-zero virtual address and size + type secInfo struct { + name string + start uint32 + end uint32 + } + var secs []secInfo + for _, s := range pf.Sections { + if s.VirtualAddress == 0 || s.VirtualSize == 0 { + continue + } + secs = append(secs, secInfo{ + name: s.Name, + start: s.VirtualAddress, + end: s.VirtualAddress + s.VirtualSize, + }) + } + + // Sort by start address + sort.Slice(secs, func(i, j int) bool { + return secs[i].start < secs[j].start + }) + + // Check for overlaps + for i := 0; i < len(secs)-1; i++ { + for j := i + 1; j < len(secs); j++ { + s1, s2 := secs[i], secs[j] + // Check if they overlap: max(start1, start2) < min(end1, end2) + maxStart := s1.start + if s2.start > maxStart { + maxStart = s2.start + } + minEnd := s1.end + if s2.end < minEnd { + minEnd = s2.end + } + if maxStart < minEnd { + t.Errorf("sections overlap: %s [%#x-%#x] and %s [%#x-%#x]", + s1.name, s1.start, s1.end, s2.name, s2.start, s2.end) + } + } + } +} + +func TestPEDWARFSections(t *testing.T) { + t.Parallel() + testenv.MustHaveGoBuild(t) + testenv.MustInternalLink(t, testenv.SpecialBuildTypes{}) + + const prog = `package main; func main() { println("hello") }` + + dir := t.TempDir() + src := filepath.Join(dir, "dwarf.go") + binFile := filepath.Join(dir, "dwarf.exe") + + if err := os.WriteFile(src, []byte(prog), 0666); err != nil { + t.Fatal(err) + } + + // Build without -w to include DWARF + cmd := goCmd(t, "build", "-o", binFile, "-ldflags", "-linkmode=internal", src) + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("failed to build: %v:\n%s", err, out) + } + + pf, err := pe.Open(binFile) + if err != nil { + t.Fatalf("failed to open PE file: %v", err) + } + defer pf.Close() + + // DWARF sections should be present and have correct characteristics + dwarfSections := []string{".debug_abbrev", ".debug_info", ".debug_line"} + for _, name := range dwarfSections { + sec := pf.Section(name) + if sec == nil { + // DWARF section names > 8 chars are stored in string table + // and section name becomes /N where N is offset + // Try to find by checking DWARF data + continue + } + + // DWARF sections should be readable and discardable + if sec.Characteristics&pe.IMAGE_SCN_MEM_READ == 0 { + t.Errorf("DWARF section %s missing IMAGE_SCN_MEM_READ", name) + } + if sec.Characteristics&pe.IMAGE_SCN_MEM_DISCARDABLE == 0 { + t.Errorf("DWARF section %s missing IMAGE_SCN_MEM_DISCARDABLE", name) + } + if sec.Characteristics&pe.IMAGE_SCN_MEM_WRITE != 0 { + t.Errorf("DWARF section %s unexpectedly writable", name) + } + } + + // Verify DWARF data is accessible + dwarfData, err := pf.DWARF() + if err != nil { + t.Fatalf("failed to get DWARF data: %v", err) + } + if dwarfData == nil { + t.Error("DWARF data is nil") + } +} + +func TestPEOptionalHeaderSizes(t *testing.T) { + t.Parallel() + testenv.MustHaveGoBuild(t) + testenv.MustInternalLink(t, testenv.SpecialBuildTypes{}) + + const prog = `package main; func main() {}` + + dir := t.TempDir() + src := filepath.Join(dir, "header.go") + binFile := filepath.Join(dir, "header.exe") + + if err := os.WriteFile(src, []byte(prog), 0666); err != nil { + t.Fatal(err) + } + + cmd := goCmd(t, "build", "-o", binFile, "-ldflags", "-linkmode=internal", src) + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("failed to build: %v:\n%s", err, out) + } + + pf, err := pe.Open(binFile) + if err != nil { + t.Fatalf("failed to open PE file: %v", err) + } + defer pf.Close() + + switch oh := pf.OptionalHeader.(type) { + case *pe.OptionalHeader32: + if oh.Magic != 0x10b { + t.Errorf("32-bit magic: got %#x, want %#x", oh.Magic, 0x10b) + } + if oh.SizeOfHeaders == 0 { + t.Error("SizeOfHeaders is 0") + } + if oh.SizeOfImage == 0 { + t.Error("SizeOfImage is 0") + } + // Verify SizeOfImage is aligned to section alignment + if oh.SizeOfImage%oh.SectionAlignment != 0 { + t.Errorf("SizeOfImage %#x not aligned to SectionAlignment %#x", + oh.SizeOfImage, oh.SectionAlignment) + } + case *pe.OptionalHeader64: + if oh.Magic != 0x20b { + t.Errorf("64-bit magic: got %#x, want %#x", oh.Magic, 0x20b) + } + if oh.SizeOfHeaders == 0 { + t.Error("SizeOfHeaders is 0") + } + if oh.SizeOfImage == 0 { + t.Error("SizeOfImage is 0") + } + // Verify SizeOfImage is aligned to section alignment + if oh.SizeOfImage%oh.SectionAlignment != 0 { + t.Errorf("SizeOfImage %#x not aligned to SectionAlignment %#x", + oh.SizeOfImage, oh.SectionAlignment) + } + default: + t.Fatal("unknown optional header type") + } +}