From 3aa2823d8b2c96d01980cca277f6cf7923db229f Mon Sep 17 00:00:00 2001 From: qmuntal Date: Wed, 11 Oct 2023 16:52:40 +0200 Subject: [PATCH] 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 Reviewed-by: Cherry Mui LUCI-TryBot-Result: Go LUCI Reviewed-by: Than McIntosh --- src/cmd/cgo/internal/test/callback_windows.go | 133 ++++++++++++++++++ src/cmd/cgo/internal/test/cgo_windows_test.go | 11 ++ src/cmd/link/internal/ld/data.go | 21 +-- src/cmd/link/internal/ld/lib.go | 14 +- src/cmd/link/internal/ld/pe.go | 8 +- src/cmd/link/internal/ld/seh.go | 8 +- src/cmd/link/internal/loadpe/ldpe.go | 117 ++++++++++----- src/cmd/link/internal/loadpe/seh.go | 109 ++++++++++++++ 8 files changed, 368 insertions(+), 53 deletions(-) create mode 100644 src/cmd/cgo/internal/test/callback_windows.go create mode 100644 src/cmd/cgo/internal/test/cgo_windows_test.go create mode 100644 src/cmd/link/internal/loadpe/seh.go diff --git a/src/cmd/cgo/internal/test/callback_windows.go b/src/cmd/cgo/internal/test/callback_windows.go new file mode 100644 index 0000000000..95e97c9af9 --- /dev/null +++ b/src/cmd/cgo/internal/test/callback_windows.go @@ -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 +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) + } +} diff --git a/src/cmd/cgo/internal/test/cgo_windows_test.go b/src/cmd/cgo/internal/test/cgo_windows_test.go new file mode 100644 index 0000000000..7bbed5b04e --- /dev/null +++ b/src/cmd/cgo/internal/test/cgo_windows_test.go @@ -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) } diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index 8534a55111..2d761c7ee7 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -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)) } diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index a6f7173706..b603fba6c7 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -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) } diff --git a/src/cmd/link/internal/ld/pe.go b/src/cmd/link/internal/ld/pe.go index 7c585b327e..8cfecafe84 100644 --- a/src/cmd/link/internal/ld/pe.go +++ b/src/cmd/link/internal/ld/pe.go @@ -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 { diff --git a/src/cmd/link/internal/ld/seh.go b/src/cmd/link/internal/ld/seh.go index 43b5176a53..9f0f747c04 100644 --- a/src/cmd/link/internal/ld/seh.go +++ b/src/cmd/link/internal/ld/seh.go @@ -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()) } diff --git a/src/cmd/link/internal/loadpe/ldpe.go b/src/cmd/link/internal/loadpe/ldpe.go index 2e9880bd6f..d1b7ae2b22 100644 --- a/src/cmd/link/internal/loadpe/ldpe.go +++ b/src/cmd/link/internal/loadpe/ldpe.go @@ -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] == '.' } diff --git a/src/cmd/link/internal/loadpe/seh.go b/src/cmd/link/internal/loadpe/seh.go new file mode 100644 index 0000000000..a97595c10c --- /dev/null +++ b/src/cmd/link/internal/loadpe/seh.go @@ -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() +} -- 2.44.0