Skip to content

Commit 14cf82a

Browse files
committed
cmd/link: generate .pdata PE section
This CL adds a .pdata section to the PE file generated by the Go linker. The .pdata section is a standard section [1] that contains an array of function table entries that are used for stack unwinding. The table entries layout is taken from [2]. This CL just generates the table entries without any unwinding information, which is enough to start doing some E2E tests between the Go linker and the Win32 APIs. The goal of the .pdata table is to allow Windows retrieve unwind information for a function at a given PC. It does so by doing a binary search on the table, looking for an entry that meets BeginAddress >= PC < EndAddress. Each table entry takes 12 bytes and only non-leaf functions with frame pointer needs an entry on the .pdata table. The result is that PE binaries will be ~0.7% bigger due to the unwind information, a reasonable amount considering the benefits in debuggability. Updates #57302 [1] https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#the-pdata-section [2] https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64#struct-runtime_function Change-Id: If675d10c64452946dbab76709da20569651e3e9f Reviewed-on: https://go-review.googlesource.com/c/go/+/461738 TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Alex Brainman <alex.brainman@gmail.com> Reviewed-by: Than McIntosh <thanm@google.com> Run-TryBot: Quim Muntal <quimmuntal@gmail.com> Reviewed-by: Cherry Mui <cherryyz@google.com>
1 parent 53279a6 commit 14cf82a

16 files changed

+237
-5
lines changed

src/cmd/link/internal/ld/asmb.go

+4
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ func asmb(ctxt *Link) {
6060

6161
writeParallel(&wg, dwarfblk, ctxt, Segdwarf.Fileoff, Segdwarf.Vaddr, Segdwarf.Filelen)
6262

63+
if Segpdata.Filelen > 0 {
64+
writeParallel(&wg, pdatablk, ctxt, Segpdata.Fileoff, Segpdata.Vaddr, Segpdata.Filelen)
65+
}
66+
6367
wg.Wait()
6468
}
6569

src/cmd/link/internal/ld/data.go

+39
Original file line numberDiff line numberDiff line change
@@ -1154,6 +1154,10 @@ func dwarfblk(ctxt *Link, out *OutBuf, addr int64, size int64) {
11541154
writeBlocks(ctxt, out, ctxt.outSem, ctxt.loader, syms, addr, size, zeros[:])
11551155
}
11561156

1157+
func pdatablk(ctxt *Link, out *OutBuf, addr int64, size int64) {
1158+
writeBlocks(ctxt, out, ctxt.outSem, ctxt.loader, []loader.Sym{sehp.pdata}, addr, size, zeros[:])
1159+
}
1160+
11571161
var covCounterDataStartOff, covCounterDataLen uint64
11581162

11591163
var zeros [512]byte
@@ -1649,6 +1653,8 @@ func (ctxt *Link) dodata(symGroupType []sym.SymKind) {
16491653
// data/rodata (and related) symbols.
16501654
state.allocateDataSections(ctxt)
16511655

1656+
state.allocateSEHSections(ctxt)
1657+
16521658
// Create *sym.Section objects and assign symbols to sections for
16531659
// DWARF symbols.
16541660
state.allocateDwarfSections(ctxt)
@@ -1676,6 +1682,10 @@ func (ctxt *Link) dodata(symGroupType []sym.SymKind) {
16761682
sect.Extnum = n
16771683
n++
16781684
}
1685+
for _, sect := range Segpdata.Sections {
1686+
sect.Extnum = n
1687+
n++
1688+
}
16791689
}
16801690

16811691
// allocateDataSectionForSym creates a new sym.Section into which a
@@ -2148,6 +2158,16 @@ func (state *dodataState) allocateDwarfSections(ctxt *Link) {
21482158
}
21492159
}
21502160

2161+
// allocateSEHSections allocate a sym.Section object for SEH
2162+
// symbols, and assigns symbols to sections.
2163+
func (state *dodataState) allocateSEHSections(ctxt *Link) {
2164+
if sehp.pdata > 0 {
2165+
sect := state.allocateDataSectionForSym(&Segpdata, sehp.pdata, 04)
2166+
state.assignDsymsToSection(sect, []loader.Sym{sehp.pdata}, sym.SRODATA, aligndatsize)
2167+
state.checkdatsize(sym.SPDATASECT)
2168+
}
2169+
}
2170+
21512171
type symNameSize struct {
21522172
name string
21532173
sz int64
@@ -2684,6 +2704,21 @@ func (ctxt *Link) address() []*sym.Segment {
26842704
// simply because right now we know where the BSS starts.
26852705
Segdata.Filelen = bss.Vaddr - Segdata.Vaddr
26862706

2707+
if len(Segpdata.Sections) > 0 {
2708+
va = uint64(Rnd(int64(va), int64(*FlagRound)))
2709+
order = append(order, &Segpdata)
2710+
Segpdata.Rwx = 04
2711+
Segpdata.Vaddr = va
2712+
// Segpdata.Sections is intended to contain just one section.
2713+
// Loop through the slice anyway for consistency.
2714+
for _, s := range Segpdata.Sections {
2715+
va = uint64(Rnd(int64(va), int64(s.Align)))
2716+
s.Vaddr = va
2717+
va += s.Length
2718+
}
2719+
Segpdata.Length = va - Segpdata.Vaddr
2720+
}
2721+
26872722
va = uint64(Rnd(int64(va), int64(*FlagRound)))
26882723
order = append(order, &Segdwarf)
26892724
Segdwarf.Rwx = 06
@@ -2735,6 +2770,10 @@ func (ctxt *Link) address() []*sym.Segment {
27352770
}
27362771
}
27372772

2773+
if sect := ldr.SymSect(sehp.pdata); sect != nil {
2774+
ldr.AddToSymValue(sehp.pdata, int64(sect.Vaddr))
2775+
}
2776+
27382777
if ctxt.BuildMode == BuildModeShared {
27392778
s := ldr.LookupOrCreateSym("go:link.abihashbytes", 0)
27402779
sect := ldr.SymSect(ldr.LookupOrCreateSym(".note.go.abihash", 0))

src/cmd/link/internal/ld/lib.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -328,8 +328,9 @@ var (
328328
Segrelrodata sym.Segment
329329
Segdata sym.Segment
330330
Segdwarf sym.Segment
331+
Segpdata sym.Segment // windows-only
331332

332-
Segments = []*sym.Segment{&Segtext, &Segrodata, &Segrelrodata, &Segdata, &Segdwarf}
333+
Segments = []*sym.Segment{&Segtext, &Segrodata, &Segrelrodata, &Segdata, &Segdwarf, &Segpdata}
333334
)
334335

335336
const pkgdef = "__.PKGDEF"

src/cmd/link/internal/ld/pe.go

+28-2
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,7 @@ type peFile struct {
433433
dataSect *peSection
434434
bssSect *peSection
435435
ctorsSect *peSection
436+
pdataSect *peSection
436437
nextSectOffset uint32
437438
nextFileOffset uint32
438439
symtabOffset int64 // offset to the start of symbol table
@@ -498,6 +499,25 @@ func (f *peFile) addDWARF() {
498499
}
499500
}
500501

502+
// addSEH adds SEH information to the COFF file f.
503+
func (f *peFile) addSEH(ctxt *Link) {
504+
if Segpdata.Length == 0 {
505+
return
506+
}
507+
d := pefile.addSection(".pdata", int(Segpdata.Length), int(Segpdata.Length))
508+
d.characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ
509+
if ctxt.LinkMode == LinkExternal {
510+
// Some gcc versions don't honor the default alignment for the .pdata section.
511+
d.characteristics |= IMAGE_SCN_ALIGN_4BYTES
512+
}
513+
pefile.pdataSect = d
514+
d.checkSegment(&Segpdata)
515+
// TODO: remove extraSize once the dummy unwind info is removed from the .pdata section.
516+
const extraSize = 12
517+
pefile.dataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress = d.virtualAddress + extraSize
518+
pefile.dataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size = d.virtualSize - extraSize
519+
}
520+
501521
// addInitArray adds .ctors COFF section to the file f.
502522
func (f *peFile) addInitArray(ctxt *Link) *peSection {
503523
// The size below was determined by the specification for array relocations,
@@ -593,15 +613,19 @@ func (f *peFile) emitRelocations(ctxt *Link) {
593613
return int(sect.Rellen / relocLen)
594614
}
595615

596-
sects := []struct {
616+
type relsect struct {
597617
peSect *peSection
598618
seg *sym.Segment
599619
syms []loader.Sym
600-
}{
620+
}
621+
sects := []relsect{
601622
{f.textSect, &Segtext, ctxt.Textp},
602623
{f.rdataSect, &Segrodata, ctxt.datap},
603624
{f.dataSect, &Segdata, ctxt.datap},
604625
}
626+
if sehp.pdata != 0 {
627+
sects = append(sects, relsect{f.pdataSect, &Segpdata, []loader.Sym{sehp.pdata}})
628+
}
605629
for _, s := range sects {
606630
s.peSect.emitRelocations(ctxt.Out, func() int {
607631
var n int
@@ -1595,6 +1619,7 @@ func addPEBaseReloc(ctxt *Link) {
15951619
func (ctxt *Link) dope() {
15961620
initdynimport(ctxt)
15971621
initdynexport(ctxt)
1622+
writeSEH(ctxt)
15981623
}
15991624

16001625
func setpersrc(ctxt *Link, syms []loader.Sym) {
@@ -1689,6 +1714,7 @@ func asmbPe(ctxt *Link) {
16891714
pefile.bssSect = b
16901715
}
16911716

1717+
pefile.addSEH(ctxt)
16921718
pefile.addDWARF()
16931719

16941720
if ctxt.LinkMode == LinkExternal {

src/cmd/link/internal/ld/seh.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package ld
6+
7+
import (
8+
"cmd/internal/sys"
9+
"cmd/link/internal/loader"
10+
"cmd/link/internal/sym"
11+
)
12+
13+
var sehp struct {
14+
pdata loader.Sym
15+
}
16+
17+
func writeSEH(ctxt *Link) {
18+
switch ctxt.Arch.Family {
19+
case sys.AMD64:
20+
writeSEHAMD64(ctxt)
21+
}
22+
}
23+
24+
func writeSEHAMD64(ctxt *Link) {
25+
ldr := ctxt.loader
26+
mkSecSym := func(name string, kind sym.SymKind) *loader.SymbolBuilder {
27+
s := ldr.CreateSymForUpdate(name, 0)
28+
s.SetType(kind)
29+
s.SetAlign(4)
30+
return s
31+
}
32+
pdata := mkSecSym(".pdata", sym.SPDATASECT)
33+
// TODO: the following 12 bytes represent a dummy unwind info,
34+
// remove once unwind infos are encoded in the .xdata section.
35+
pdata.AddUint64(ctxt.Arch, 0)
36+
pdata.AddUint32(ctxt.Arch, 0)
37+
for _, s := range ctxt.Textp {
38+
if fi := ldr.FuncInfo(s); !fi.Valid() || fi.TopFrame() {
39+
continue
40+
}
41+
uw := ldr.SEHUnwindSym(s)
42+
if uw == 0 {
43+
continue
44+
}
45+
46+
// Reference:
47+
// https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64#struct-runtime_function
48+
pdata.AddPEImageRelativeAddrPlus(ctxt.Arch, s, 0)
49+
pdata.AddPEImageRelativeAddrPlus(ctxt.Arch, s, ldr.SymSize(s))
50+
// TODO: reference the .xdata symbol.
51+
pdata.AddPEImageRelativeAddrPlus(ctxt.Arch, pdata.Sym(), 0)
52+
}
53+
sehp.pdata = pdata.Sym()
54+
}

src/cmd/link/internal/loader/loader.go

+10
Original file line numberDiff line numberDiff line change
@@ -1646,6 +1646,16 @@ func (l *Loader) WasmImportSym(fnSymIdx Sym) (Sym, bool) {
16461646
return 0, false
16471647
}
16481648

1649+
// SEHUnwindSym returns the auxiliary SEH unwind symbol associated with
1650+
// a given function symbol.
1651+
func (l *Loader) SEHUnwindSym(fnSymIdx Sym) Sym {
1652+
if l.SymType(fnSymIdx) != sym.STEXT {
1653+
log.Fatalf("error: non-function sym %d/%s t=%s passed to SEHUnwindSym", fnSymIdx, l.SymName(fnSymIdx), l.SymType(fnSymIdx).String())
1654+
}
1655+
1656+
return l.aux1(fnSymIdx, goobj.AuxSehUnwindInfo)
1657+
}
1658+
16491659
// GetFuncDwarfAuxSyms collects and returns the auxiliary DWARF
16501660
// symbols associated with a given function symbol. Prior to the
16511661
// introduction of the loader, this was done purely using name

src/cmd/link/internal/sym/symkind.go

+1
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ const (
126126

127127
// SEH symbol types
128128
SSEHUNWINDINFO
129+
SPDATASECT
129130
)
130131

131132
// AbiSymKindToSymKind maps values read from object files (which are

src/cmd/link/internal/sym/symkind_string.go

+3-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/internal/syscall/windows/syscall_windows.go

+2
Original file line numberDiff line numberDiff line change
@@ -399,3 +399,5 @@ type FILE_ID_BOTH_DIR_INFO struct {
399399
}
400400

401401
//sys GetVolumeInformationByHandle(file syscall.Handle, volumeNameBuffer *uint16, volumeNameSize uint32, volumeNameSerialNumber *uint32, maximumComponentLength *uint32, fileSystemFlags *uint32, fileSystemNameBuffer *uint16, fileSystemNameSize uint32) (err error) = GetVolumeInformationByHandleW
402+
403+
//sys RtlLookupFunctionEntry(pc uintptr, baseAddress *uintptr, table *byte) (ret uintptr) = kernel32.RtlLookupFunctionEntry

src/internal/syscall/windows/zsyscall_windows.go

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/runtime/defs_windows_386.go

+3
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ func (c *context) set_lr(x uintptr) {}
5656
func (c *context) set_ip(x uintptr) { c.eip = uint32(x) }
5757
func (c *context) set_sp(x uintptr) { c.esp = uint32(x) }
5858

59+
// 386 does not have frame pointer register.
60+
func (c *context) set_fp(x uintptr) {}
61+
5962
func prepareContextForSigResume(c *context) {
6063
c.edx = c.esp
6164
c.ecx = c.eip

src/runtime/defs_windows_amd64.go

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ func (c *context) set_lr(x uintptr) {}
6969

7070
func (c *context) set_ip(x uintptr) { c.rip = uint64(x) }
7171
func (c *context) set_sp(x uintptr) { c.rsp = uint64(x) }
72+
func (c *context) set_fp(x uintptr) { c.rbp = uint64(x) }
7273

7374
func prepareContextForSigResume(c *context) {
7475
c.r8 = c.rsp

src/runtime/defs_windows_arm.go

+3
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ func (c *context) set_ip(x uintptr) { c.pc = uint32(x) }
5858
func (c *context) set_sp(x uintptr) { c.spr = uint32(x) }
5959
func (c *context) set_lr(x uintptr) { c.lrr = uint32(x) }
6060

61+
// arm does not have frame pointer register.
62+
func (c *context) set_fp(x uintptr) {}
63+
6164
func prepareContextForSigResume(c *context) {
6265
c.r0 = c.spr
6366
c.r1 = c.pc

src/runtime/defs_windows_arm64.go

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ func (c *context) lr() uintptr { return uintptr(c.x[30]) }
4040
func (c *context) set_ip(x uintptr) { c.pc = uint64(x) }
4141
func (c *context) set_sp(x uintptr) { c.xsp = uint64(x) }
4242
func (c *context) set_lr(x uintptr) { c.x[30] = uint64(x) }
43+
func (c *context) set_fp(x uintptr) { c.x[29] = uint64(x) }
4344

4445
func prepareContextForSigResume(c *context) {
4546
c.x[0] = c.xsp

src/runtime/export_windows_test.go

+16
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,19 @@ func NumberOfProcessors() int32 {
2020
stdcall1(_GetSystemInfo, uintptr(unsafe.Pointer(&info)))
2121
return int32(info.dwnumberofprocessors)
2222
}
23+
24+
type ContextStub struct {
25+
context
26+
}
27+
28+
func (c ContextStub) GetPC() uintptr {
29+
return c.ip()
30+
}
31+
32+
func NewContextStub() ContextStub {
33+
var ctx context
34+
ctx.set_ip(getcallerpc())
35+
ctx.set_sp(getcallersp())
36+
ctx.set_fp(getcallerfp())
37+
return ContextStub{ctx}
38+
}

0 commit comments

Comments
 (0)