diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go
index 5b6dabb..faab424 100644
--- a/src/cmd/link/internal/ld/data.go
+++ b/src/cmd/link/internal/ld/data.go
@@ -1828,7 +1828,7 @@
sname = ".go." + sname[len("go:"):]
}
sect := addsection(ldr, state.ctxt.Arch, seg, sname, rwx)
- sect.Align = symalign(ldr, s)
+ sect.Align = peSectionAlign(state.ctxt, symalign(ldr, s))
state.datsize = Rnd(state.datsize, int64(sect.Align))
sect.Vaddr = uint64(state.datsize)
return sect
@@ -1853,6 +1853,7 @@
}
}
}
+ sect.Align = peSectionAlign(state.ctxt, sect.Align)
state.datsize = Rnd(state.datsize, int64(sect.Align))
sect.Vaddr = uint64(state.datsize)
return sect
@@ -1946,7 +1947,7 @@
}
s := state.data[sym.SMODULEDATA][0]
sect := addsection(ldr, ctxt.Arch, &Segdata, ".go.module", 06)
- sect.Align = symalign(ldr, s)
+ sect.Align = peSectionAlign(ctxt, symalign(ldr, s))
state.datsize = Rnd(state.datsize, int64(sect.Align))
sect.Vaddr = uint64(state.datsize)
ldr.SetSymSect(s, sect)
@@ -2523,9 +2524,7 @@
// 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 = peSectionAlign(ctxt, int32(Funcalign))
ldr := ctxt.loader
if *flagRandLayout != 0 {
@@ -2757,7 +2756,7 @@
sect = addsection(ctxt.loader, ctxt.Arch, &Segtext, ".text", 05)
sect.Vaddr = va
- sect.Align = sectAlign
+ sect.Align = peSectionAlign(ctxt, 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 5cd39fb..a29063b 100644
--- a/src/cmd/link/internal/ld/dwarf.go
+++ b/src/cmd/link/internal/ld/dwarf.go
@@ -2458,7 +2458,7 @@
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 = peSectionAlign(ctxt, int32(ctxt.Arch.Alignment))
sect.Length = uint64(len(z.compressed))
sect.Compressed = true
newSym := ldr.MakeSymbolBuilder(compressedSegName)
@@ -2488,7 +2488,11 @@
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/pe.go b/src/cmd/link/internal/ld/pe.go
index b49da42..cbf6993 100644
--- a/src/cmd/link/internal/ld/pe.go
+++ b/src/cmd/link/internal/ld/pe.go
@@ -63,6 +63,20 @@
PEFILEALIGN int64 = 2 << 8
)
+// peSectionAlign returns the appropriate section alignment for PE files.
+// For Windows internal linking, sections must be aligned to PESECTALIGN.
+func peSectionAlign(ctxt *Link, align int32) int32 {
+ if ctxt.HeadType == objabi.Hwindows && ctxt.LinkMode == LinkInternal {
+ if peAlign := int32(PESECTALIGN); align < peAlign {
+ return peAlign
+ }
+ }
+ return align
+}
+
+// peSectionHeaderReserve reserves header space for section headers.
+const peSectionHeaderReserve = 64
+
const (
IMAGE_SCN_CNT_CODE = 0x00000020
IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040
@@ -379,6 +393,27 @@
}
}
+// 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 +480,52 @@
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.peshbits(ctxt, sect, &Segdwarf)
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 +533,60 @@
}
}
+// peshbits creates a PE section for the given sym.Section.
+func (f *peFile) peshbits(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 {
+ h.characteristics |= IMAGE_SCN_ALIGN_32BYTES
+ }
+ if fileSize == 0 {
+ h.pointerToRawData = 0
+ h.virtualSize = uint32(sect.Length)
+ h.sizeOfRawData = 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,27 +594,64 @@
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.
@@ -623,30 +745,43 @@
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)
+ })
}
- sects := []relsect{
- {f.textSect, &Segtext, ctxt.Textp},
- {f.rdataSect, &Segrodata, ctxt.datap},
- {f.dataSect, &Segdata, ctxt.datap},
+
+ for _, sect := range Segtext.Sections {
+ emitForSection(sect, ctxt.Textp)
}
+ 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 +792,11 @@
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 +852,14 @@
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 pesect, ok := f.sectMap[sect]; ok {
+ offset = int64(uint64(ldr.SymValue(s)) - sect.Vaddr)
+ return pesect.index, offset, 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
- }
- 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 +1071,39 @@
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 +1111,23 @@
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 +1272,7 @@
}
}
- 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 +1425,14 @@
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 +1487,10 @@
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 +1516,7 @@
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 +1847,52 @@
}
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
+ pefile.sectMap = make(map[*sym.Section]*peSection)
+
+ for _, sect := range Segtext.Sections {
+ h := pefile.peshbits(ctxt, sect, &Segtext)
+ if h == nil {
+ continue
+ }
+ if sect.Name == ".text" && pefile.textSect == nil {
+ pefile.textSect = h
+ h.checkSection(sect, ctxt.LinkMode)
+ }
}
- t.checkSegment(&Segtext)
- pefile.textSect = t
-
- 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 Segrodata.Sections {
+ h := pefile.peshbits(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
+ }
}
- 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 Segrelrodata.Sections {
+ _ = pefile.peshbits(ctxt, sect, &Segrelrodata)
+ }
+ for _, sect := range Segdata.Sections {
+ h := pefile.peshbits(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 +1900,7 @@
ctxt.Out.SeekSet(int64(pefile.nextFileOffset))
if ctxt.LinkMode != LinkExternal {
- addimports(ctxt, d)
+ addimports(ctxt)
addexports(ctxt)
addPEBaseReloc(ctxt)
}
@@ -1748,6 +1909,9 @@
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/pe_test.go b/src/cmd/link/internal/ld/pe_test.go
new file mode 100644
index 0000000..2e15ce6
--- /dev/null
+++ b/src/cmd/link/internal/ld/pe_test.go
@@ -0,0 +1,460 @@
+// 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 ld
+
+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
+ 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,
+ wantSecsRO: []string{".rodata", ".gopclntab"},
+ wantSecsROIfPresent: []string{
+ ".typelink",
+ ".itablink",
+ },
+ },
+ }
+
+ 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 := testenv.Command(t, testenv.GoToolPath(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 := func(name string, required bool) {
+ sec := secByName[name]
+ if sec == nil {
+ if required {
+ t.Fatalf("test %s: can't locate %q section", test.name, name)
+ }
+ return
+ }
+ if sec.Characteristics&pe.IMAGE_SCN_MEM_READ == 0 {
+ t.Errorf("section %s missing IMAGE_SCN_MEM_READ", name)
+ }
+ if sec.Characteristics&pe.IMAGE_SCN_MEM_WRITE != 0 {
+ t.Errorf("section %s unexpectedly writable", name)
+ }
+ }
+
+ for _, name := range test.wantSecsRO {
+ checkRO(name, true)
+ }
+ for _, name := range test.wantSecsROIfPresent {
+ checkRO(name, 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 := testenv.Command(t, testenv.GoToolPath(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 := testenv.Command(t, testenv.GoToolPath(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 := testenv.Command(t, testenv.GoToolPath(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 := testenv.Command(t, testenv.GoToolPath(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 := testenv.Command(t, testenv.GoToolPath(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")
+ }
+}
diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go
index bc7504e..98437a8 100644
--- a/src/cmd/link/link_test.go
+++ b/src/cmd/link/link_test.go
@@ -2205,16 +2205,77 @@
}
}
- 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)
}
- // On Windows and AIX all the Go specific sections
+ case xf != nil:
+ defer xf.Close()
+
+ // 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)
+ }
+}