cmd/internal/link: merge .pdata and .xdata sections from host object files

The Go linker doesn't currently merge .pdata/.xdata sections from the
host object files generated by the C compiler when using internal
linking. This means that the stack can't be unwind in C -> Go.

This CL fixes that and adds a test to ensure that the stack can be
unwind in C -> Go and Go -> C transitions, which was not well tested.

Updates #57302

Change-Id: Ie86a5e6e30b80978277e66ccc2c48550e51263c8
Reviewed-on: https://go-review.googlesource.com/c/go/+/534555
Reviewed-by: Heschi Kreinick <heschi@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Than McIntosh <thanm@google.com>
This commit is contained in:
qmuntal 2023-10-11 16:52:40 +02:00 committed by Quim Muntal
parent 96eeb4512b
commit 3aa2823d8b
8 changed files with 368 additions and 53 deletions

View File

@ -0,0 +1,133 @@
// 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 cgotest
/*
#include <windows.h>
USHORT backtrace(ULONG FramesToCapture, PVOID *BackTrace) {
#ifdef _AMD64_
CONTEXT context;
RtlCaptureContext(&context);
ULONG64 ControlPc;
ControlPc = context.Rip;
int i;
for (i = 0; i < FramesToCapture; i++) {
PRUNTIME_FUNCTION FunctionEntry;
ULONG64 ImageBase;
VOID *HandlerData;
ULONG64 EstablisherFrame;
FunctionEntry = RtlLookupFunctionEntry(ControlPc, &ImageBase, NULL);
if (!FunctionEntry) {
// For simplicity, don't unwind leaf entries, which are not used in this test.
break;
} else {
RtlVirtualUnwind(0, ImageBase, ControlPc, FunctionEntry, &context, &HandlerData, &EstablisherFrame, NULL);
}
ControlPc = context.Rip;
// Check if we left the user range.
if (ControlPc < 0x10000) {
break;
}
BackTrace[i] = (PVOID)(ControlPc);
}
return i;
#else
return 0;
#endif
}
*/
import "C"
import (
"internal/testenv"
"reflect"
"runtime"
"strings"
"testing"
"unsafe"
)
// Test that the stack can be unwound through a call out and call back
// into Go.
func testCallbackCallersSEH(t *testing.T) {
testenv.SkipIfOptimizationOff(t) // This test requires inlining.
if runtime.Compiler != "gc" {
// The exact function names are not going to be the same.
t.Skip("skipping for non-gc toolchain")
}
if runtime.GOARCH != "amd64" {
// TODO: support SEH on other architectures.
t.Skip("skipping on non-amd64")
}
const cgoexpPrefix = "_cgoexp_"
want := []string{
"runtime.asmcgocall_landingpad",
"runtime.asmcgocall",
"runtime.cgocall",
"test._Cfunc_backtrace",
"test.testCallbackCallersSEH.func1.1",
"test.testCallbackCallersSEH.func1",
"test.goCallback",
cgoexpPrefix + "goCallback",
"runtime.cgocallbackg1",
"runtime.cgocallbackg",
"runtime.cgocallbackg",
"runtime.cgocallback",
"crosscall2",
"runtime.asmcgocall_landingpad",
"runtime.asmcgocall",
"runtime.cgocall",
"test._Cfunc_callback",
"test.nestedCall.func1",
"test.nestedCall",
"test.testCallbackCallersSEH",
"test.TestCallbackCallersSEH",
"testing.tRunner",
"testing.(*T).Run.gowrap1",
"runtime.goexit",
}
pc := make([]uintptr, 100)
n := 0
nestedCall(func() {
n = int(C.backtrace(C.DWORD(len(pc)), (*C.PVOID)(unsafe.Pointer(&pc[0]))))
})
got := make([]string, 0, n)
for i := 0; i < n; i++ {
f := runtime.FuncForPC(pc[i] - 1)
if f == nil {
continue
}
fname := f.Name()
switch fname {
case "goCallback", "callback":
// TODO(qmuntal): investigate why these functions don't appear
// when using the external linker.
continue
}
// Skip cgo-generated functions, the runtime might not know about them,
// depending on the link mode.
if strings.HasPrefix(fname, "_cgo_") {
continue
}
// Remove the cgo-generated random prefix.
if strings.HasPrefix(fname, cgoexpPrefix) {
idx := strings.Index(fname[len(cgoexpPrefix):], "_")
if idx >= 0 {
fname = cgoexpPrefix + fname[len(cgoexpPrefix)+idx+1:]
}
}
// In module mode, this package has a fully-qualified import path.
// Remove it if present.
fname = strings.TrimPrefix(fname, "cmd/cgo/internal/")
got = append(got, fname)
}
if !reflect.DeepEqual(want, got) {
t.Errorf("incorrect backtrace:\nwant:\t%v\ngot:\t%v", want, got)
}
}

View File

@ -0,0 +1,11 @@
// 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.
//go:build cgo && windows
package cgotest
import "testing"
func TestCallbackCallersSEH(t *testing.T) { testCallbackCallersSEH(t) }

View File

@ -1161,11 +1161,11 @@ func dwarfblk(ctxt *Link, out *OutBuf, addr int64, size int64) {
}
func pdatablk(ctxt *Link, out *OutBuf, addr int64, size int64) {
writeBlocks(ctxt, out, ctxt.outSem, ctxt.loader, []loader.Sym{sehp.pdata}, addr, size, zeros[:])
writeBlocks(ctxt, out, ctxt.outSem, ctxt.loader, sehp.pdata, addr, size, zeros[:])
}
func xdatablk(ctxt *Link, out *OutBuf, addr int64, size int64) {
writeBlocks(ctxt, out, ctxt.outSem, ctxt.loader, []loader.Sym{sehp.xdata}, addr, size, zeros[:])
writeBlocks(ctxt, out, ctxt.outSem, ctxt.loader, sehp.xdata, addr, size, zeros[:])
}
var covCounterDataStartOff, covCounterDataLen uint64
@ -2199,14 +2199,14 @@ 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)
if len(sehp.pdata) > 0 {
sect := state.allocateNamedDataSection(&Segpdata, ".pdata", []sym.SymKind{}, 04)
state.assignDsymsToSection(sect, sehp.pdata, sym.SRODATA, aligndatsize)
state.checkdatsize(sym.SSEHSECT)
}
if sehp.xdata > 0 {
if len(sehp.xdata) > 0 {
sect := state.allocateNamedDataSection(&Segxdata, ".xdata", []sym.SymKind{}, 04)
state.assignDsymsToSection(sect, []loader.Sym{sehp.xdata}, sym.SRODATA, aligndatsize)
state.assignDsymsToSection(sect, sehp.xdata, sym.SRODATA, aligndatsize)
state.checkdatsize(sym.SSEHSECT)
}
}
@ -2872,7 +2872,12 @@ func (ctxt *Link) address() []*sym.Segment {
}
}
for _, s := range []loader.Sym{sehp.pdata, sehp.xdata} {
for _, s := range sehp.pdata {
if sect := ldr.SymSect(s); sect != nil {
ldr.AddToSymValue(s, int64(sect.Vaddr))
}
}
for _, s := range sehp.xdata {
if sect := ldr.SymSect(s); sect != nil {
ldr.AddToSymValue(s, int64(sect.Vaddr))
}

View File

@ -2220,15 +2220,21 @@ func ldobj(ctxt *Link, f *bio.Reader, lib *sym.Library, length int64, pn string,
0xc401, // arm
0x64aa: // arm64
ldpe := func(ctxt *Link, f *bio.Reader, pkg string, length int64, pn string) {
textp, rsrc, err := loadpe.Load(ctxt.loader, ctxt.Arch, ctxt.IncVersion(), f, pkg, length, pn)
ls, err := loadpe.Load(ctxt.loader, ctxt.Arch, ctxt.IncVersion(), f, pkg, length, pn)
if err != nil {
Errorf(nil, "%v", err)
return
}
if len(rsrc) != 0 {
setpersrc(ctxt, rsrc)
if len(ls.Resources) != 0 {
setpersrc(ctxt, ls.Resources)
}
ctxt.Textp = append(ctxt.Textp, textp...)
if ls.PData != 0 {
sehp.pdata = append(sehp.pdata, ls.PData)
}
if ls.XData != 0 {
sehp.xdata = append(sehp.xdata, ls.XData)
}
ctxt.Textp = append(ctxt.Textp, ls.Textp...)
}
return ldhostobj(ldpe, ctxt.HeadType, f, pkg, length, pn, file)
}

View File

@ -635,11 +635,11 @@ func (f *peFile) emitRelocations(ctxt *Link) {
{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}})
if len(sehp.pdata) != 0 {
sects = append(sects, relsect{f.pdataSect, &Segpdata, sehp.pdata})
}
if sehp.xdata != 0 {
sects = append(sects, relsect{f.xdataSect, &Segxdata, []loader.Sym{sehp.xdata}})
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 {

View File

@ -11,8 +11,8 @@ import (
)
var sehp struct {
pdata loader.Sym
xdata loader.Sym
pdata []sym.LoaderSym
xdata []sym.LoaderSym
}
func writeSEH(ctxt *Link) {
@ -72,6 +72,6 @@ func writeSEHAMD64(ctxt *Link) {
pdata.AddPEImageRelativeAddrPlus(ctxt.Arch, s, ldr.SymSize(s))
pdata.AddPEImageRelativeAddrPlus(ctxt.Arch, xdata.Sym(), off)
}
sehp.pdata = pdata.Sym()
sehp.xdata = xdata.Sym()
sehp.pdata = append(sehp.pdata, pdata.Sym())
sehp.xdata = append(sehp.xdata, xdata.Sym())
}

View File

@ -221,12 +221,17 @@ type peLoaderState struct {
// is symbol size (or -1 if we're using the "any" strategy).
var comdatDefinitions map[string]int64
// Symbols contains the symbols that can be loaded from a PE file.
type Symbols struct {
Textp []loader.Sym // text symbols
Resources []loader.Sym // .rsrc section or set of .rsrc$xx sections
PData loader.Sym
XData loader.Sym
}
// Load loads the PE file pn from input.
// Symbols from the object file are created via the loader 'l',
// and a slice of the text symbols is returned.
// If an .rsrc section or set of .rsrc$xx sections is found, its symbols are
// returned as rsrc.
func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Reader, pkg string, length int64, pn string) (textp []loader.Sym, rsrc []loader.Sym, err error) {
// Symbols from the object file are created via the loader 'l'.
func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Reader, pkg string, length int64, pn string) (*Symbols, error) {
state := &peLoaderState{
l: l,
arch: arch,
@ -249,11 +254,13 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read
// TODO: replace pe.NewFile with pe.Load (grep for "add Load function" in debug/pe for details)
f, err := pe.NewFile(sr)
if err != nil {
return nil, nil, err
return nil, err
}
defer f.Close()
state.f = f
var ls Symbols
// TODO return error if found .cormeta
// create symbols for mapped sections
@ -274,7 +281,12 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read
switch sect.Characteristics & (pe.IMAGE_SCN_CNT_UNINITIALIZED_DATA | pe.IMAGE_SCN_CNT_INITIALIZED_DATA | pe.IMAGE_SCN_MEM_READ | pe.IMAGE_SCN_MEM_WRITE | pe.IMAGE_SCN_CNT_CODE | pe.IMAGE_SCN_MEM_EXECUTE) {
case pe.IMAGE_SCN_CNT_INITIALIZED_DATA | pe.IMAGE_SCN_MEM_READ: //.rdata
bld.SetType(sym.SRODATA)
if issehsect(arch, sect) {
bld.SetType(sym.SSEHSECT)
bld.SetAlign(4)
} else {
bld.SetType(sym.SRODATA)
}
case pe.IMAGE_SCN_CNT_UNINITIALIZED_DATA | pe.IMAGE_SCN_MEM_READ | pe.IMAGE_SCN_MEM_WRITE: //.bss
bld.SetType(sym.SNOPTRBSS)
@ -286,13 +298,13 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read
bld.SetType(sym.STEXT)
default:
return nil, nil, fmt.Errorf("unexpected flags %#06x for PE section %s", sect.Characteristics, sect.Name)
return nil, fmt.Errorf("unexpected flags %#06x for PE section %s", sect.Characteristics, sect.Name)
}
if bld.Type() != sym.SNOPTRBSS {
data, err := sect.Data()
if err != nil {
return nil, nil, err
return nil, err
}
state.sectdata[sect] = data
bld.SetData(data)
@ -300,13 +312,19 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read
bld.SetSize(int64(sect.Size))
state.sectsyms[sect] = s
if sect.Name == ".rsrc" || strings.HasPrefix(sect.Name, ".rsrc$") {
rsrc = append(rsrc, s)
ls.Resources = append(ls.Resources, s)
} else if bld.Type() == sym.SSEHSECT {
if sect.Name == ".pdata" {
ls.PData = s
} else if sect.Name == ".xdata" {
ls.XData = s
}
}
}
// Make a prepass over the symbols to collect info about COMDAT symbols.
if err := state.preprocessSymbols(); err != nil {
return nil, nil, err
return nil, err
}
// load relocations
@ -327,22 +345,23 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read
}
splitResources := strings.HasPrefix(rsect.Name, ".rsrc$")
issehsect := issehsect(arch, rsect)
sb := l.MakeSymbolUpdater(state.sectsyms[rsect])
for j, r := range rsect.Relocs {
if int(r.SymbolTableIndex) >= len(f.COFFSymbols) {
return nil, nil, fmt.Errorf("relocation number %d symbol index idx=%d cannot be large then number of symbols %d", j, r.SymbolTableIndex, len(f.COFFSymbols))
return nil, fmt.Errorf("relocation number %d symbol index idx=%d cannot be large then number of symbols %d", j, r.SymbolTableIndex, len(f.COFFSymbols))
}
pesym := &f.COFFSymbols[r.SymbolTableIndex]
_, gosym, err := state.readpesym(pesym)
if err != nil {
return nil, nil, err
return nil, err
}
if gosym == 0 {
name, err := pesym.FullName(f.StringTable)
if err != nil {
name = string(pesym.Name[:])
}
return nil, nil, fmt.Errorf("reloc of invalid sym %s idx=%d type=%d", name, r.SymbolTableIndex, pesym.Type)
return nil, fmt.Errorf("reloc of invalid sym %s idx=%d type=%d", name, r.SymbolTableIndex, pesym.Type)
}
rSym := gosym
@ -352,21 +371,29 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read
var rType objabi.RelocType
switch arch.Family {
default:
return nil, nil, fmt.Errorf("%s: unsupported arch %v", pn, arch.Family)
return nil, fmt.Errorf("%s: unsupported arch %v", pn, arch.Family)
case sys.I386, sys.AMD64:
switch r.Type {
default:
return nil, nil, fmt.Errorf("%s: %v: unknown relocation type %v", pn, state.sectsyms[rsect], r.Type)
return nil, fmt.Errorf("%s: %v: unknown relocation type %v", pn, state.sectsyms[rsect], r.Type)
case IMAGE_REL_I386_REL32, IMAGE_REL_AMD64_REL32,
IMAGE_REL_AMD64_ADDR32, // R_X86_64_PC32
IMAGE_REL_AMD64_ADDR32NB:
rType = objabi.R_PCREL
if r.Type == IMAGE_REL_AMD64_ADDR32NB {
rType = objabi.R_PEIMAGEOFF
} else {
rType = objabi.R_PCREL
}
rAdd = int64(int32(binary.LittleEndian.Uint32(state.sectdata[rsect][rOff:])))
case IMAGE_REL_I386_DIR32NB, IMAGE_REL_I386_DIR32:
rType = objabi.R_ADDR
if r.Type == IMAGE_REL_I386_DIR32NB {
rType = objabi.R_PEIMAGEOFF
} else {
rType = objabi.R_ADDR
}
// load addend from image
rAdd = int64(int32(binary.LittleEndian.Uint32(state.sectdata[rsect][rOff:])))
@ -383,7 +410,7 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read
case sys.ARM:
switch r.Type {
default:
return nil, nil, fmt.Errorf("%s: %v: unknown ARM relocation type %v", pn, state.sectsyms[rsect], r.Type)
return nil, fmt.Errorf("%s: %v: unknown ARM relocation type %v", pn, state.sectsyms[rsect], r.Type)
case IMAGE_REL_ARM_SECREL:
rType = objabi.R_PCREL
@ -391,7 +418,11 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read
rAdd = int64(int32(binary.LittleEndian.Uint32(state.sectdata[rsect][rOff:])))
case IMAGE_REL_ARM_ADDR32, IMAGE_REL_ARM_ADDR32NB:
rType = objabi.R_ADDR
if r.Type == IMAGE_REL_ARM_ADDR32NB {
rType = objabi.R_PEIMAGEOFF
} else {
rType = objabi.R_ADDR
}
rAdd = int64(int32(binary.LittleEndian.Uint32(state.sectdata[rsect][rOff:])))
@ -404,10 +435,14 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read
case sys.ARM64:
switch r.Type {
default:
return nil, nil, fmt.Errorf("%s: %v: unknown ARM64 relocation type %v", pn, state.sectsyms[rsect], r.Type)
return nil, fmt.Errorf("%s: %v: unknown ARM64 relocation type %v", pn, state.sectsyms[rsect], r.Type)
case IMAGE_REL_ARM64_ADDR32, IMAGE_REL_ARM64_ADDR32NB:
rType = objabi.R_ADDR
if r.Type == IMAGE_REL_ARM64_ADDR32NB {
rType = objabi.R_PEIMAGEOFF
} else {
rType = objabi.R_ADDR
}
rAdd = int64(int32(binary.LittleEndian.Uint32(state.sectdata[rsect][rOff:])))
}
@ -420,12 +455,20 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read
if issect(pesym) || splitResources {
rAdd += int64(pesym.Value)
}
if issehsect {
// .pdata and .xdata sections can contain records
// associated to functions that won't be used in
// the final binary, in which case the relocation
// target symbol won't be reachable.
rType |= objabi.R_WEAK
}
rel, _ := sb.AddRel(rType)
rel.SetOff(rOff)
rel.SetSiz(rSize)
rel.SetSym(rSym)
rel.SetAdd(rAdd)
}
sb.SortRelocs()
@ -439,7 +482,7 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read
name, err := pesym.FullName(f.StringTable)
if err != nil {
return nil, nil, err
return nil, err
}
if name == "" {
continue
@ -477,7 +520,7 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read
bld, s, err := state.readpesym(pesym)
if err != nil {
return nil, nil, err
return nil, err
}
if pesym.SectionNumber == 0 { // extern
@ -491,14 +534,14 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read
} else if pesym.SectionNumber > 0 && int(pesym.SectionNumber) <= len(f.Sections) {
sect = f.Sections[pesym.SectionNumber-1]
if _, found := state.sectsyms[sect]; !found {
return nil, nil, fmt.Errorf("%s: %v: missing sect.sym", pn, s)
return nil, fmt.Errorf("%s: %v: missing sect.sym", pn, s)
}
} else {
return nil, nil, fmt.Errorf("%s: %v: sectnum < 0!", pn, s)
return nil, fmt.Errorf("%s: %v: sectnum < 0!", pn, s)
}
if sect == nil {
return nil, nil, nil
return nil, nil
}
// Check for COMDAT symbol.
@ -517,7 +560,7 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read
}
outerName := l.SymName(l.OuterSym(s))
sectName := l.SymName(state.sectsyms[sect])
return nil, nil, fmt.Errorf("%s: duplicate symbol reference: %s in both %s and %s", pn, l.SymName(s), outerName, sectName)
return nil, fmt.Errorf("%s: duplicate symbol reference: %s in both %s and %s", pn, l.SymName(s), outerName, sectName)
}
bld = makeUpdater(l, bld, s)
@ -528,7 +571,7 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read
bld.SetSize(4)
if l.SymType(sectsym) == sym.STEXT {
if bld.External() && !bld.DuplicateOK() {
return nil, nil, fmt.Errorf("%s: duplicate symbol definition", l.SymName(s))
return nil, fmt.Errorf("%s: duplicate symbol definition", l.SymName(s))
}
bld.SetExternal(true)
}
@ -536,7 +579,7 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read
// This is a COMDAT definition. Record that we're picking
// this instance so that we can ignore future defs.
if _, ok := comdatDefinitions[l.SymName(s)]; ok {
return nil, nil, fmt.Errorf("internal error: preexisting COMDAT definition for %q", name)
return nil, fmt.Errorf("internal error: preexisting COMDAT definition for %q", name)
}
comdatDefinitions[l.SymName(s)] = sz
}
@ -554,15 +597,19 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, input *bio.Read
if l.SymType(s) == sym.STEXT {
for ; s != 0; s = l.SubSym(s) {
if l.AttrOnList(s) {
return nil, nil, fmt.Errorf("symbol %s listed multiple times", l.SymName(s))
return nil, fmt.Errorf("symbol %s listed multiple times", l.SymName(s))
}
l.SetAttrOnList(s, true)
textp = append(textp, s)
ls.Textp = append(ls.Textp, s)
}
}
}
return textp, rsrc, nil
if ls.PData != 0 {
processSEH(l, arch, ls.PData, ls.XData)
}
return &ls, nil
}
// PostProcessImports works to resolve inconsistencies with DLL import
@ -643,6 +690,10 @@ func PostProcessImports() error {
return nil
}
func issehsect(arch *sys.Arch, s *pe.Section) bool {
return arch.Family == sys.AMD64 && (s.Name == ".pdata" || s.Name == ".xdata")
}
func issect(s *pe.COFFSymbol) bool {
return s.StorageClass == IMAGE_SYM_CLASS_STATIC && s.Type == 0 && s.Name[0] == '.'
}

View File

@ -0,0 +1,109 @@
// 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 loadpe
import (
"cmd/internal/objabi"
"cmd/internal/sys"
"cmd/link/internal/loader"
"cmd/link/internal/sym"
"fmt"
"sort"
)
const (
UNW_FLAG_EHANDLER = 1 << 3
UNW_FLAG_UHANDLER = 2 << 3
UNW_FLAG_CHAININFO = 3 << 3
unwStaticDataSize = 8
)
// processSEH walks all pdata relocations looking for exception handler function symbols.
// We want to mark these as reachable if the function that they protect is reachable
// in the final binary.
func processSEH(ldr *loader.Loader, arch *sys.Arch, pdata sym.LoaderSym, xdata sym.LoaderSym) error {
switch arch.Family {
case sys.AMD64:
ldr.SetAttrReachable(pdata, true)
if xdata != 0 {
ldr.SetAttrReachable(xdata, true)
}
return processSEHAMD64(ldr, pdata)
default:
// TODO: support SEH on other architectures.
return fmt.Errorf("unsupported architecture for SEH: %v", arch.Family)
}
}
func processSEHAMD64(ldr *loader.Loader, pdata sym.LoaderSym) error {
// The following loop traverses a list of pdata entries,
// each entry being 3 relocations long. The first relocation
// is a pointer to the function symbol to which the pdata entry
// corresponds. The third relocation is a pointer to the
// corresponding .xdata entry.
// Reference:
// https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64#struct-runtime_function
rels := ldr.Relocs(pdata)
if rels.Count()%3 != 0 {
return fmt.Errorf(".pdata symbol %q has invalid relocation count", ldr.SymName(pdata))
}
for i := 0; i < rels.Count(); i += 3 {
xrel := rels.At(i + 2)
handler := findHandlerInXDataAMD64(ldr, xrel.Sym(), xrel.Add())
if handler != 0 {
sb := ldr.MakeSymbolUpdater(rels.At(i).Sym())
r, _ := sb.AddRel(objabi.R_KEEP)
r.SetSym(handler)
}
}
return nil
}
// findHandlerInXDataAMD64 finds the symbol in the .xdata section that
// corresponds to the exception handler.
// Reference:
// https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64#struct-unwind_info
func findHandlerInXDataAMD64(ldr *loader.Loader, xsym sym.LoaderSym, add int64) loader.Sym {
data := ldr.Data(xsym)
if add < 0 || add+unwStaticDataSize > int64(len(data)) {
return 0
}
data = data[add:]
var isChained bool
switch flag := data[0]; {
case flag&UNW_FLAG_EHANDLER != 0 || flag&UNW_FLAG_UHANDLER != 0:
// Exception handler.
case flag&UNW_FLAG_CHAININFO != 0:
isChained = true
default:
// Nothing to do.
return 0
}
codes := data[3]
if codes%2 != 0 {
// There are always an even number of unwind codes, even if the last one is unused.
codes += 1
}
// The exception handler relocation is the first relocation after the unwind codes,
// unless it is chained, but we will handle this case later.
targetOff := add + unwStaticDataSize*(1+int64(codes))
xrels := ldr.Relocs(xsym)
idx := sort.Search(xrels.Count(), func(i int) bool {
return int64(xrels.At(i).Off()) >= targetOff
})
if idx == 0 {
return 0
}
if isChained {
// The third relocations references the next .xdata entry in the chain, recurse.
idx += 2
if idx >= xrels.Count() {
return 0
}
r := xrels.At(idx)
return findHandlerInXDataAMD64(ldr, r.Sym(), r.Add())
}
return xrels.At(idx).Sym()
}