Skip to content

Commit

Permalink
cmd/link: generate .pdata PE section
Browse files Browse the repository at this point in the history
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 <[email protected]>
Reviewed-by: Alex Brainman <[email protected]>
Reviewed-by: Than McIntosh <[email protected]>
Run-TryBot: Quim Muntal <[email protected]>
Reviewed-by: Cherry Mui <[email protected]>
  • Loading branch information
qmuntal committed May 2, 2023
1 parent 53279a6 commit 14cf82a
Show file tree
Hide file tree
Showing 16 changed files with 237 additions and 5 deletions.
4 changes: 4 additions & 0 deletions src/cmd/link/internal/ld/asmb.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ func asmb(ctxt *Link) {

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

if Segpdata.Filelen > 0 {
writeParallel(&wg, pdatablk, ctxt, Segpdata.Fileoff, Segpdata.Vaddr, Segpdata.Filelen)
}

wg.Wait()
}

Expand Down
39 changes: 39 additions & 0 deletions src/cmd/link/internal/ld/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -1154,6 +1154,10 @@ func dwarfblk(ctxt *Link, out *OutBuf, addr int64, size int64) {
writeBlocks(ctxt, out, ctxt.outSem, ctxt.loader, syms, addr, size, zeros[:])
}

func pdatablk(ctxt *Link, out *OutBuf, addr int64, size int64) {
writeBlocks(ctxt, out, ctxt.outSem, ctxt.loader, []loader.Sym{sehp.pdata}, addr, size, zeros[:])
}

var covCounterDataStartOff, covCounterDataLen uint64

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

state.allocateSEHSections(ctxt)

// Create *sym.Section objects and assign symbols to sections for
// DWARF symbols.
state.allocateDwarfSections(ctxt)
Expand Down Expand Up @@ -1676,6 +1682,10 @@ func (ctxt *Link) dodata(symGroupType []sym.SymKind) {
sect.Extnum = n
n++
}
for _, sect := range Segpdata.Sections {
sect.Extnum = n
n++
}
}

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

// allocateSEHSections allocate a sym.Section object for SEH
// symbols, and assigns symbols to sections.
func (state *dodataState) allocateSEHSections(ctxt *Link) {
if sehp.pdata > 0 {
sect := state.allocateDataSectionForSym(&Segpdata, sehp.pdata, 04)
state.assignDsymsToSection(sect, []loader.Sym{sehp.pdata}, sym.SRODATA, aligndatsize)
state.checkdatsize(sym.SPDATASECT)
}
}

type symNameSize struct {
name string
sz int64
Expand Down Expand Up @@ -2684,6 +2704,21 @@ func (ctxt *Link) address() []*sym.Segment {
// simply because right now we know where the BSS starts.
Segdata.Filelen = bss.Vaddr - Segdata.Vaddr

if len(Segpdata.Sections) > 0 {
va = uint64(Rnd(int64(va), int64(*FlagRound)))
order = append(order, &Segpdata)
Segpdata.Rwx = 04
Segpdata.Vaddr = va
// Segpdata.Sections is intended to contain just one section.
// Loop through the slice anyway for consistency.
for _, s := range Segpdata.Sections {
va = uint64(Rnd(int64(va), int64(s.Align)))
s.Vaddr = va
va += s.Length
}
Segpdata.Length = va - Segpdata.Vaddr
}

va = uint64(Rnd(int64(va), int64(*FlagRound)))
order = append(order, &Segdwarf)
Segdwarf.Rwx = 06
Expand Down Expand Up @@ -2735,6 +2770,10 @@ func (ctxt *Link) address() []*sym.Segment {
}
}

if sect := ldr.SymSect(sehp.pdata); sect != nil {
ldr.AddToSymValue(sehp.pdata, int64(sect.Vaddr))
}

if ctxt.BuildMode == BuildModeShared {
s := ldr.LookupOrCreateSym("go:link.abihashbytes", 0)
sect := ldr.SymSect(ldr.LookupOrCreateSym(".note.go.abihash", 0))
Expand Down
3 changes: 2 additions & 1 deletion src/cmd/link/internal/ld/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,9 @@ var (
Segrelrodata sym.Segment
Segdata sym.Segment
Segdwarf sym.Segment
Segpdata sym.Segment // windows-only

Segments = []*sym.Segment{&Segtext, &Segrodata, &Segrelrodata, &Segdata, &Segdwarf}
Segments = []*sym.Segment{&Segtext, &Segrodata, &Segrelrodata, &Segdata, &Segdwarf, &Segpdata}
)

const pkgdef = "__.PKGDEF"
Expand Down
30 changes: 28 additions & 2 deletions src/cmd/link/internal/ld/pe.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ type peFile struct {
dataSect *peSection
bssSect *peSection
ctorsSect *peSection
pdataSect *peSection
nextSectOffset uint32
nextFileOffset uint32
symtabOffset int64 // offset to the start of symbol table
Expand Down Expand Up @@ -498,6 +499,25 @@ func (f *peFile) addDWARF() {
}
}

// addSEH adds SEH information to the COFF file f.
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)
// TODO: remove extraSize once the dummy unwind info is removed from the .pdata section.
const extraSize = 12
pefile.dataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress = d.virtualAddress + extraSize
pefile.dataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size = d.virtualSize - extraSize
}

// 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,
Expand Down Expand Up @@ -593,15 +613,19 @@ func (f *peFile) emitRelocations(ctxt *Link) {
return int(sect.Rellen / relocLen)
}

sects := []struct {
type relsect struct {
peSect *peSection
seg *sym.Segment
syms []loader.Sym
}{
}
sects := []relsect{
{f.textSect, &Segtext, ctxt.Textp},
{f.rdataSect, &Segrodata, ctxt.datap},
{f.dataSect, &Segdata, ctxt.datap},
}
if sehp.pdata != 0 {
sects = append(sects, relsect{f.pdataSect, &Segpdata, []loader.Sym{sehp.pdata}})
}
for _, s := range sects {
s.peSect.emitRelocations(ctxt.Out, func() int {
var n int
Expand Down Expand Up @@ -1595,6 +1619,7 @@ func addPEBaseReloc(ctxt *Link) {
func (ctxt *Link) dope() {
initdynimport(ctxt)
initdynexport(ctxt)
writeSEH(ctxt)
}

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

pefile.addSEH(ctxt)
pefile.addDWARF()

if ctxt.LinkMode == LinkExternal {
Expand Down
54 changes: 54 additions & 0 deletions src/cmd/link/internal/ld/seh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2023 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.

package ld

import (
"cmd/internal/sys"
"cmd/link/internal/loader"
"cmd/link/internal/sym"
)

var sehp struct {
pdata loader.Sym
}

func writeSEH(ctxt *Link) {
switch ctxt.Arch.Family {
case sys.AMD64:
writeSEHAMD64(ctxt)
}
}

func writeSEHAMD64(ctxt *Link) {
ldr := ctxt.loader
mkSecSym := func(name string, kind sym.SymKind) *loader.SymbolBuilder {
s := ldr.CreateSymForUpdate(name, 0)
s.SetType(kind)
s.SetAlign(4)
return s
}
pdata := mkSecSym(".pdata", sym.SPDATASECT)
// TODO: the following 12 bytes represent a dummy unwind info,
// remove once unwind infos are encoded in the .xdata section.
pdata.AddUint64(ctxt.Arch, 0)
pdata.AddUint32(ctxt.Arch, 0)
for _, s := range ctxt.Textp {
if fi := ldr.FuncInfo(s); !fi.Valid() || fi.TopFrame() {
continue
}
uw := ldr.SEHUnwindSym(s)
if uw == 0 {
continue
}

// Reference:
// https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64#struct-runtime_function
pdata.AddPEImageRelativeAddrPlus(ctxt.Arch, s, 0)
pdata.AddPEImageRelativeAddrPlus(ctxt.Arch, s, ldr.SymSize(s))
// TODO: reference the .xdata symbol.
pdata.AddPEImageRelativeAddrPlus(ctxt.Arch, pdata.Sym(), 0)
}
sehp.pdata = pdata.Sym()
}
10 changes: 10 additions & 0 deletions src/cmd/link/internal/loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -1646,6 +1646,16 @@ func (l *Loader) WasmImportSym(fnSymIdx Sym) (Sym, bool) {
return 0, false
}

// SEHUnwindSym returns the auxiliary SEH unwind symbol associated with
// a given function symbol.
func (l *Loader) SEHUnwindSym(fnSymIdx Sym) Sym {
if l.SymType(fnSymIdx) != sym.STEXT {
log.Fatalf("error: non-function sym %d/%s t=%s passed to SEHUnwindSym", fnSymIdx, l.SymName(fnSymIdx), l.SymType(fnSymIdx).String())
}

return l.aux1(fnSymIdx, goobj.AuxSehUnwindInfo)
}

// GetFuncDwarfAuxSyms collects and returns the auxiliary DWARF
// symbols associated with a given function symbol. Prior to the
// introduction of the loader, this was done purely using name
Expand Down
1 change: 1 addition & 0 deletions src/cmd/link/internal/sym/symkind.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ const (

// SEH symbol types
SSEHUNWINDINFO
SPDATASECT
)

// AbiSymKindToSymKind maps values read from object files (which are
Expand Down
5 changes: 3 additions & 2 deletions src/cmd/link/internal/sym/symkind_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/internal/syscall/windows/syscall_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -399,3 +399,5 @@ type FILE_ID_BOTH_DIR_INFO struct {
}

//sys GetVolumeInformationByHandle(file syscall.Handle, volumeNameBuffer *uint16, volumeNameSize uint32, volumeNameSerialNumber *uint32, maximumComponentLength *uint32, fileSystemFlags *uint32, fileSystemNameBuffer *uint16, fileSystemNameSize uint32) (err error) = GetVolumeInformationByHandleW

//sys RtlLookupFunctionEntry(pc uintptr, baseAddress *uintptr, table *byte) (ret uintptr) = kernel32.RtlLookupFunctionEntry
7 changes: 7 additions & 0 deletions src/internal/syscall/windows/zsyscall_windows.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/runtime/defs_windows_386.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ func (c *context) set_lr(x uintptr) {}
func (c *context) set_ip(x uintptr) { c.eip = uint32(x) }
func (c *context) set_sp(x uintptr) { c.esp = uint32(x) }

// 386 does not have frame pointer register.
func (c *context) set_fp(x uintptr) {}

func prepareContextForSigResume(c *context) {
c.edx = c.esp
c.ecx = c.eip
Expand Down
1 change: 1 addition & 0 deletions src/runtime/defs_windows_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ func (c *context) set_lr(x uintptr) {}

func (c *context) set_ip(x uintptr) { c.rip = uint64(x) }
func (c *context) set_sp(x uintptr) { c.rsp = uint64(x) }
func (c *context) set_fp(x uintptr) { c.rbp = uint64(x) }

func prepareContextForSigResume(c *context) {
c.r8 = c.rsp
Expand Down
3 changes: 3 additions & 0 deletions src/runtime/defs_windows_arm.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ func (c *context) set_ip(x uintptr) { c.pc = uint32(x) }
func (c *context) set_sp(x uintptr) { c.spr = uint32(x) }
func (c *context) set_lr(x uintptr) { c.lrr = uint32(x) }

// arm does not have frame pointer register.
func (c *context) set_fp(x uintptr) {}

func prepareContextForSigResume(c *context) {
c.r0 = c.spr
c.r1 = c.pc
Expand Down
1 change: 1 addition & 0 deletions src/runtime/defs_windows_arm64.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func (c *context) lr() uintptr { return uintptr(c.x[30]) }
func (c *context) set_ip(x uintptr) { c.pc = uint64(x) }
func (c *context) set_sp(x uintptr) { c.xsp = uint64(x) }
func (c *context) set_lr(x uintptr) { c.x[30] = uint64(x) }
func (c *context) set_fp(x uintptr) { c.x[29] = uint64(x) }

func prepareContextForSigResume(c *context) {
c.x[0] = c.xsp
Expand Down
16 changes: 16 additions & 0 deletions src/runtime/export_windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,19 @@ func NumberOfProcessors() int32 {
stdcall1(_GetSystemInfo, uintptr(unsafe.Pointer(&info)))
return int32(info.dwnumberofprocessors)
}

type ContextStub struct {
context
}

func (c ContextStub) GetPC() uintptr {
return c.ip()
}

func NewContextStub() ContextStub {
var ctx context
ctx.set_ip(getcallerpc())
ctx.set_sp(getcallersp())
ctx.set_fp(getcallerfp())
return ContextStub{ctx}
}
Loading

0 comments on commit 14cf82a

Please sign in to comment.