From 22ce4823926d527396690d0c09af1633fb9f25bc Mon Sep 17 00:00:00 2001 From: Zxilly Date: Sun, 21 Dec 2025 03:25:44 +0800 Subject: [PATCH 01/10] cmd/link: improve PE section handling This change improves how the PE linker handles sections: - Unify addSection and appendSection APIs following ELF pattern - Merge addDWARFSection into peshbits for consistent handling - Add peSectionAlign helper for section alignment - Add comprehensive unit tests for PE sections --- src/cmd/link/internal/ld/data.go | 11 +- src/cmd/link/internal/ld/dwarf.go | 8 +- src/cmd/link/internal/ld/pe.go | 442 +++++++++++++++++--------- src/cmd/link/internal/ld/pe_test.go | 460 ++++++++++++++++++++++++++++ src/cmd/link/link_test.go | 73 ++++- 5 files changed, 841 insertions(+), 153 deletions(-) create mode 100644 src/cmd/link/internal/ld/pe_test.go diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index 5b6dabb62b58cb..faab424751baf1 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 = 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 @@ func (state *dodataState) allocateNamedDataSection(seg *sym.Segment, sName strin } } } + 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 @@ 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 = 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 @@ 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 = peSectionAlign(ctxt, 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 = 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 5cd39fbc53eb90..a29063bc6de9c9 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 = peSectionAlign(ctxt, 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/pe.go b/src/cmd/link/internal/ld/pe.go index b49da42c4cf0c7..cbf6993b1ba914 100644 --- a/src/cmd/link/internal/ld/pe.go +++ b/src/cmd/link/internal/ld/pe.go @@ -63,6 +63,20 @@ var ( 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 @@ 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 +480,52 @@ 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.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 @@ func (f *peFile) addDWARF() { } } +// 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,29 +594,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 +745,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 +792,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 +852,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 +1071,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 +1111,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 +1272,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 +1425,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 +1487,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 +1516,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 +1847,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.peshbits(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.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 + } + } + 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 @@ 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 +1909,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/pe_test.go b/src/cmd/link/internal/ld/pe_test.go new file mode 100644 index 00000000000000..2e15ce6c05e436 --- /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 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) + } +} From ce29298fd0146262a4a0f5d041de48896a965bc0 Mon Sep 17 00:00:00 2001 From: Zxilly Date: Sun, 21 Dec 2025 17:10:11 +0800 Subject: [PATCH 02/10] fix llvm link --- src/cmd/link/internal/ld/pe.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cmd/link/internal/ld/pe.go b/src/cmd/link/internal/ld/pe.go index cbf6993b1ba914..2db411beb30879 100644 --- a/src/cmd/link/internal/ld/pe.go +++ b/src/cmd/link/internal/ld/pe.go @@ -578,9 +578,10 @@ func (f *peFile) peshbits(ctxt *Link, sect *sym.Section, seg *sym.Segment) *peSe 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 - h.virtualSize = uint32(sect.Length) - h.sizeOfRawData = 0 } f.sectMap[sect] = h From b4c33e78f69bfe9874345f83fdf6c54af7cd9348 Mon Sep 17 00:00:00 2001 From: Zxilly Date: Sun, 21 Dec 2025 20:04:48 +0800 Subject: [PATCH 03/10] also check truncated name --- src/cmd/link/internal/ld/pe_test.go | 50 ++++++++++++++++++----------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/src/cmd/link/internal/ld/pe_test.go b/src/cmd/link/internal/ld/pe_test.go index 2e15ce6c05e436..036c2c04d7d679 100644 --- a/src/cmd/link/internal/ld/pe_test.go +++ b/src/cmd/link/internal/ld/pe_test.go @@ -29,8 +29,8 @@ func TestPESectionsReadOnly(t *testing.T) { name string args []string prog string - wantSecsRO []string - wantSecsROIfPresent []string + wantSecsRO [][]string // each entry is a list of alternative names (first found wins) + wantSecsROIfPresent [][]string mustHaveCGO bool mustInternalLink bool }{ @@ -39,10 +39,10 @@ func TestPESectionsReadOnly(t *testing.T) { args: []string{"-ldflags", "-linkmode=internal"}, prog: prog, mustInternalLink: true, - wantSecsRO: []string{".rodata", ".gopclntab"}, - wantSecsROIfPresent: []string{ - ".typelink", - ".itablink", + wantSecsRO: [][]string{{".rodata"}, {".gopclntab"}}, + wantSecsROIfPresent: [][]string{ + {".typelink"}, + {".itablink"}, }, }, { @@ -50,10 +50,12 @@ func TestPESectionsReadOnly(t *testing.T) { args: []string{"-ldflags", "-linkmode=external"}, prog: progC, mustHaveCGO: true, - wantSecsRO: []string{".rodata", ".gopclntab"}, - wantSecsROIfPresent: []string{ - ".typelink", - ".itablink", + // 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"}, }, }, } @@ -92,27 +94,37 @@ func TestPESectionsReadOnly(t *testing.T) { secByName[sec.Name] = sec } - checkRO := func(name string, required bool) { - sec := secByName[name] + // 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 %q section", test.name, name) + 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", name) + 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", name) + t.Errorf("section %s unexpectedly writable", foundName) } } - for _, name := range test.wantSecsRO { - checkRO(name, true) + for _, names := range test.wantSecsRO { + checkRO(names, true) } - for _, name := range test.wantSecsROIfPresent { - checkRO(name, false) + for _, names := range test.wantSecsROIfPresent { + checkRO(names, false) } }) } From de8228ce4942851485f4b391d74f7e4ea31767e4 Mon Sep 17 00:00:00 2001 From: Zxilly Date: Mon, 22 Dec 2025 01:34:32 +0800 Subject: [PATCH 04/10] add comment --- src/cmd/link/internal/ld/pe.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/cmd/link/internal/ld/pe.go b/src/cmd/link/internal/ld/pe.go index 2db411beb30879..fbd7624add8f27 100644 --- a/src/cmd/link/internal/ld/pe.go +++ b/src/cmd/link/internal/ld/pe.go @@ -526,6 +526,10 @@ func (f *peFile) addDWARF(ctxt *Link) { } for _, sect := range Segdwarf.Sections { h := f.peshbits(ctxt, sect, &Segdwarf) + if h == nil { + // peshbits 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) @@ -575,6 +579,9 @@ func (f *peFile) peshbits(ctxt *Link, sect *sym.Section, seg *sym.Segment) *peSe } 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 { From 6ddeebf10944257ac78ef4f3343972b185756569 Mon Sep 17 00:00:00 2001 From: Zxilly Date: Tue, 23 Dec 2025 01:51:24 +0800 Subject: [PATCH 05/10] cmd/link: update PE section alignment handling --- src/cmd/link/internal/ld/data.go | 10 +++++----- src/cmd/link/internal/ld/dwarf.go | 2 +- src/cmd/link/internal/ld/lib.go | 5 +++++ src/cmd/link/internal/ld/pe.go | 11 ----------- src/cmd/link/internal/ld/target.go | 13 ++++++++++--- 5 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index faab424751baf1..53bc10ecaa879a 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 = peSectionAlign(state.ctxt, symalign(ldr, s)) + sect.Align = state.ctxt.Target.Align(symalign(ldr, s)) state.datsize = Rnd(state.datsize, int64(sect.Align)) sect.Vaddr = uint64(state.datsize) return sect @@ -1853,7 +1853,7 @@ func (state *dodataState) allocateNamedDataSection(seg *sym.Segment, sName strin } } } - sect.Align = peSectionAlign(state.ctxt, sect.Align) + sect.Align = state.ctxt.Target.Align(sect.Align) state.datsize = Rnd(state.datsize, int64(sect.Align)) sect.Vaddr = uint64(state.datsize) return sect @@ -1947,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 = peSectionAlign(ctxt, symalign(ldr, s)) + sect.Align = ctxt.Target.Align(symalign(ldr, s)) state.datsize = Rnd(state.datsize, int64(sect.Align)) sect.Vaddr = uint64(state.datsize) ldr.SetSymSect(s, sect) @@ -2524,7 +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 = peSectionAlign(ctxt, int32(Funcalign)) + sect.Align = ctxt.Target.Align(int32(Funcalign)) ldr := ctxt.loader if *flagRandLayout != 0 { @@ -2756,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 = peSectionAlign(ctxt, sectAlign) + sect.Align = ctxt.Target.Align(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 a29063bc6de9c9..ec571ad13340d4 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 = peSectionAlign(ctxt, int32(ctxt.Arch.Alignment)) + sect.Align = ctxt.Target.Align(int32(ctxt.Arch.Alignment)) sect.Length = uint64(len(z.compressed)) sect.Compressed = true newSym := ldr.MakeSymbolBuilder(compressedSegName) diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index bcad5add4abe19..5c3ae4807a4c41 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.MinimumAlignment = 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 fbd7624add8f27..39b75ec6756e6c 100644 --- a/src/cmd/link/internal/ld/pe.go +++ b/src/cmd/link/internal/ld/pe.go @@ -63,17 +63,6 @@ var ( 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 diff --git a/src/cmd/link/internal/ld/target.go b/src/cmd/link/internal/ld/target.go index d0ce99f3e9f9ef..30d9852e8aa6d3 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 + MinimumAlignment int32 // minimum section alignment for the target } // @@ -204,3 +205,9 @@ func (t *Target) UsesLibc() bool { } return false } + +// Align returns the alignment adjusted to meet the target's minimum +// section alignment requirement. +func (t *Target) Align(align int32) int32 { + return max(align, t.MinimumAlignment) +} From 7302f96ee404daac5d6683360a09fab6ab5c7876 Mon Sep 17 00:00:00 2001 From: Zxilly Date: Tue, 23 Dec 2025 01:59:34 +0800 Subject: [PATCH 06/10] cmd/link: refactor PE section creation method --- src/cmd/link/internal/ld/pe.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/cmd/link/internal/ld/pe.go b/src/cmd/link/internal/ld/pe.go index 39b75ec6756e6c..561e2fab547275 100644 --- a/src/cmd/link/internal/ld/pe.go +++ b/src/cmd/link/internal/ld/pe.go @@ -514,9 +514,9 @@ func (f *peFile) addDWARF(ctxt *Link) { return } for _, sect := range Segdwarf.Sections { - h := f.peshbits(ctxt, sect, &Segdwarf) + h := f.createSection(ctxt, sect, &Segdwarf) if h == nil { - // peshbits returns nil for empty sections + // createSection returns nil for empty sections continue } fileoff := sect.Vaddr - Segdwarf.Vaddr + Segdwarf.Fileoff @@ -526,8 +526,8 @@ func (f *peFile) addDWARF(ctxt *Link) { } } -// peshbits creates a PE section for the given sym.Section. -func (f *peFile) peshbits(ctxt *Link, sect *sym.Section, seg *sym.Segment) *peSection { +// 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 } @@ -1847,7 +1847,7 @@ func asmbPe(ctxt *Link) { pefile.sectMap = make(map[*sym.Section]*peSection) for _, sect := range Segtext.Sections { - h := pefile.peshbits(ctxt, sect, &Segtext) + h := pefile.createSection(ctxt, sect, &Segtext) if h == nil { continue } @@ -1857,7 +1857,7 @@ func asmbPe(ctxt *Link) { } } for _, sect := range Segrodata.Sections { - h := pefile.peshbits(ctxt, sect, &Segrodata) + h := pefile.createSection(ctxt, sect, &Segrodata) if h == nil { continue } @@ -1869,10 +1869,10 @@ func asmbPe(ctxt *Link) { } } for _, sect := range Segrelrodata.Sections { - _ = pefile.peshbits(ctxt, sect, &Segrelrodata) + _ = pefile.createSection(ctxt, sect, &Segrelrodata) } for _, sect := range Segdata.Sections { - h := pefile.peshbits(ctxt, sect, &Segdata) + h := pefile.createSection(ctxt, sect, &Segdata) if h == nil { continue } From 90537718e4fc462d460786e045ef32f455c1cd66 Mon Sep 17 00:00:00 2001 From: Zxilly Date: Tue, 23 Dec 2025 02:05:00 +0800 Subject: [PATCH 07/10] cmd/link: add tests for PE section characteristics and alignment --- src/cmd/link/{internal/ld => }/pe_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) rename src/cmd/link/{internal/ld => }/pe_test.go (94%) diff --git a/src/cmd/link/internal/ld/pe_test.go b/src/cmd/link/pe_test.go similarity index 94% rename from src/cmd/link/internal/ld/pe_test.go rename to src/cmd/link/pe_test.go index 036c2c04d7d679..a99523c5526c5f 100644 --- a/src/cmd/link/internal/ld/pe_test.go +++ b/src/cmd/link/pe_test.go @@ -4,7 +4,7 @@ //go:build windows -package ld +package main import ( "debug/pe" @@ -78,7 +78,7 @@ func TestPESectionsReadOnly(t *testing.T) { } cmdArgs := append([]string{"build", "-o", binFile}, append(test.args, src)...) - cmd := testenv.Command(t, testenv.GoToolPath(t), cmdArgs...) + cmd := goCmd(t, cmdArgs...) if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("failed to build %v: %v:\n%s", cmd.Args, err, out) } @@ -145,7 +145,7 @@ func TestPESectionAlignment(t *testing.T) { t.Fatal(err) } - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", binFile, "-ldflags", "-linkmode=internal", src) + 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) } @@ -205,7 +205,7 @@ func main() { t.Fatal(err) } - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", binFile, "-ldflags", "-linkmode=internal", src) + 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) } @@ -294,7 +294,7 @@ func main() { t.Fatal(err) } - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", binFile, "-ldflags", "-linkmode=internal", src) + 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) } @@ -365,7 +365,7 @@ func TestPEDWARFSections(t *testing.T) { } // Build without -w to include DWARF - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", binFile, "-ldflags", "-linkmode=internal", src) + 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) } @@ -424,7 +424,7 @@ func TestPEOptionalHeaderSizes(t *testing.T) { t.Fatal(err) } - cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", binFile, "-ldflags", "-linkmode=internal", src) + 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) } From ba62ab46cd84cae3d5c61983e44264eb4a6f54fb Mon Sep 17 00:00:00 2001 From: Zxilly Date: Tue, 23 Dec 2025 02:10:22 +0800 Subject: [PATCH 08/10] cmd/link: adjust symbol name checks for 386 architecture --- src/cmd/link/link_test.go | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go index 98437a86af9d91..9221ae93b720e4 100644 --- a/src/cmd/link/link_test.go +++ b/src/cmd/link/link_test.go @@ -1755,9 +1755,13 @@ func TestLinknameBSS(t *testing.T) { if err != nil { t.Fatalf("fail to get symbols: %v", err) } + prefix := "" + if runtime.GOOS == "windows" && runtime.GOARCH == "386" { + prefix = "_" + } found := false for _, s := range syms { - if s.Name == "runtime.sched" || s.Name == "_runtime.sched" { + if s.Name == prefix+"runtime.sched" { found = true if s.Size < 100 { // As of Go 1.25 (Mar 2025), runtime.sched has 6848 bytes on @@ -1768,7 +1772,7 @@ func TestLinknameBSS(t *testing.T) { } } if !found { - t.Errorf("runtime.sched symbol not found") + t.Errorf("%sruntime.sched symbol not found", prefix) } // Executable should run. @@ -2208,15 +2212,19 @@ func TestModuledataPlacement(t *testing.T) { case pf != nil: defer pf.Close() + prefix := "" + if runtime.GOARCH == "386" { + prefix = "_" + } var moddataSym *pe.Symbol for _, sym := range pf.Symbols { - if sym.Name == moddataSymName || sym.Name == "_"+moddataSymName { + if sym.Name == prefix+moddataSymName { moddataSym = sym break } } if moddataSym == nil { - t.Fatalf("could not find symbol %s", moddataSymName) + t.Fatalf("could not find symbol %s%s", prefix, moddataSymName) } if moddataSym.SectionNumber <= 0 { t.Fatalf("moduledata not in a section (section number %d)", moddataSym.SectionNumber) @@ -2260,15 +2268,19 @@ func TestPEEdataSection(t *testing.T) { } defer pf.Close() + prefix := "" + if runtime.GOARCH == "386" { + prefix = "_" + } var edataSym *pe.Symbol for _, sym := range pf.Symbols { - if sym.Name == "runtime.edata" || sym.Name == "_runtime.edata" { + if sym.Name == prefix+"runtime.edata" { edataSym = sym break } } if edataSym == nil { - t.Fatal("could not find symbol runtime.edata") + t.Fatalf("could not find symbol %sruntime.edata", prefix) } if edataSym.SectionNumber <= 0 { t.Fatalf("runtime.edata not in a section (section number %d)", edataSym.SectionNumber) From a05a626f61792f9d17a75e11809309335222389d Mon Sep 17 00:00:00 2001 From: Zxilly Date: Tue, 23 Dec 2025 22:59:38 +0800 Subject: [PATCH 09/10] cmd/link: update section alignment handling to use SectAlign method --- src/cmd/link/internal/ld/data.go | 10 +++++----- src/cmd/link/internal/ld/dwarf.go | 2 +- src/cmd/link/internal/ld/lib.go | 2 +- src/cmd/link/internal/ld/target.go | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index 53bc10ecaa879a..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 = state.ctxt.Target.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,7 +1853,7 @@ func (state *dodataState) allocateNamedDataSection(seg *sym.Segment, sName strin } } } - sect.Align = state.ctxt.Target.Align(sect.Align) + sect.Align = state.ctxt.Target.SectAlign(sect.Align) state.datsize = Rnd(state.datsize, int64(sect.Align)) sect.Vaddr = uint64(state.datsize) return sect @@ -1947,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 = ctxt.Target.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) @@ -2524,7 +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 = ctxt.Target.Align(int32(Funcalign)) + sect.Align = ctxt.Target.SectAlign(int32(Funcalign)) ldr := ctxt.loader if *flagRandLayout != 0 { @@ -2756,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 = ctxt.Target.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 ec571ad13340d4..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 = ctxt.Target.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) diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 5c3ae4807a4c41..eff58c65c4bca8 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -585,7 +585,7 @@ func (ctxt *Link) loadlib() { // Set minimum section alignment for PE files. if ctxt.HeadType == objabi.Hwindows && ctxt.LinkMode == LinkInternal { - ctxt.Target.MinimumAlignment = int32(PESECTALIGN) + ctxt.Target.MinimumSectAlign = int32(PESECTALIGN) } if ctxt.LinkMode == LinkExternal && !iscgo && !(buildcfg.GOOS == "darwin" && ctxt.BuildMode != BuildModePlugin && ctxt.Arch.Family == sys.AMD64) { diff --git a/src/cmd/link/internal/ld/target.go b/src/cmd/link/internal/ld/target.go index 30d9852e8aa6d3..afd7e36497ce1c 100644 --- a/src/cmd/link/internal/ld/target.go +++ b/src/cmd/link/internal/ld/target.go @@ -22,7 +22,7 @@ type Target struct { linkShared bool canUsePlugins bool IsELF bool - MinimumAlignment int32 // minimum section alignment for the target + MinimumSectAlign int32 // minimum section alignment for the target } // @@ -206,8 +206,8 @@ func (t *Target) UsesLibc() bool { return false } -// Align returns the alignment adjusted to meet the target's minimum +// SectAlign returns the alignment adjusted to meet the target's minimum // section alignment requirement. -func (t *Target) Align(align int32) int32 { - return max(align, t.MinimumAlignment) +func (t *Target) SectAlign(align int32) int32 { + return max(align, t.MinimumSectAlign) } From acc858821f74949644f1571afe91e4bc03a9833a Mon Sep 17 00:00:00 2001 From: Zxilly Date: Wed, 24 Dec 2025 21:13:34 +0800 Subject: [PATCH 10/10] Revert "cmd/link: adjust symbol name checks for 386 architecture" This reverts commit ba62ab46cd84cae3d5c61983e44264eb4a6f54fb. --- src/cmd/link/link_test.go | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go index 9221ae93b720e4..98437a86af9d91 100644 --- a/src/cmd/link/link_test.go +++ b/src/cmd/link/link_test.go @@ -1755,13 +1755,9 @@ func TestLinknameBSS(t *testing.T) { if err != nil { t.Fatalf("fail to get symbols: %v", err) } - prefix := "" - if runtime.GOOS == "windows" && runtime.GOARCH == "386" { - prefix = "_" - } found := false for _, s := range syms { - if s.Name == prefix+"runtime.sched" { + if s.Name == "runtime.sched" || s.Name == "_runtime.sched" { found = true if s.Size < 100 { // As of Go 1.25 (Mar 2025), runtime.sched has 6848 bytes on @@ -1772,7 +1768,7 @@ func TestLinknameBSS(t *testing.T) { } } if !found { - t.Errorf("%sruntime.sched symbol not found", prefix) + t.Errorf("runtime.sched symbol not found") } // Executable should run. @@ -2212,19 +2208,15 @@ func TestModuledataPlacement(t *testing.T) { case pf != nil: defer pf.Close() - prefix := "" - if runtime.GOARCH == "386" { - prefix = "_" - } var moddataSym *pe.Symbol for _, sym := range pf.Symbols { - if sym.Name == prefix+moddataSymName { + if sym.Name == moddataSymName || sym.Name == "_"+moddataSymName { moddataSym = sym break } } if moddataSym == nil { - t.Fatalf("could not find symbol %s%s", prefix, moddataSymName) + t.Fatalf("could not find symbol %s", moddataSymName) } if moddataSym.SectionNumber <= 0 { t.Fatalf("moduledata not in a section (section number %d)", moddataSym.SectionNumber) @@ -2268,19 +2260,15 @@ func TestPEEdataSection(t *testing.T) { } defer pf.Close() - prefix := "" - if runtime.GOARCH == "386" { - prefix = "_" - } var edataSym *pe.Symbol for _, sym := range pf.Symbols { - if sym.Name == prefix+"runtime.edata" { + if sym.Name == "runtime.edata" || sym.Name == "_runtime.edata" { edataSym = sym break } } if edataSym == nil { - t.Fatalf("could not find symbol %sruntime.edata", prefix) + 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)