--- /dev/null
+// 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)
+ }
+}
--- /dev/null
+// 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) }
}
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
// 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)
}
}
}
}
- 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))
}
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)
}
{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 {
)
var sehp struct {
- pdata loader.Sym
- xdata loader.Sym
+ pdata []sym.LoaderSym
+ xdata []sym.LoaderSym
}
func writeSEH(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())
}
// 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,
// 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
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)
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)
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
}
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
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:])))
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
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:])))
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:])))
}
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()
name, err := pesym.FullName(f.StringTable)
if err != nil {
- return nil, nil, err
+ return nil, err
}
if name == "" {
continue
bld, s, err := state.readpesym(pesym)
if err != nil {
- return nil, nil, err
+ return nil, err
}
if pesym.SectionNumber == 0 { // extern
} 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.
}
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)
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)
}
// 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
}
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
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] == '.'
}
--- /dev/null
+// 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()
+}