]> Cypherpunks.ru repositories - gostls13.git/commitdiff
cmd/internal/link: merge .pdata and .xdata sections from host object files
authorqmuntal <quimmuntal@gmail.com>
Wed, 11 Oct 2023 14:52:40 +0000 (16:52 +0200)
committerQuim Muntal <quimmuntal@gmail.com>
Thu, 9 Nov 2023 19:01:27 +0000 (19:01 +0000)
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>
src/cmd/cgo/internal/test/callback_windows.go [new file with mode: 0644]
src/cmd/cgo/internal/test/cgo_windows_test.go [new file with mode: 0644]
src/cmd/link/internal/ld/data.go
src/cmd/link/internal/ld/lib.go
src/cmd/link/internal/ld/pe.go
src/cmd/link/internal/ld/seh.go
src/cmd/link/internal/loadpe/ldpe.go
src/cmd/link/internal/loadpe/seh.go [new file with mode: 0644]

diff --git a/src/cmd/cgo/internal/test/callback_windows.go b/src/cmd/cgo/internal/test/callback_windows.go
new file mode 100644 (file)
index 0000000..95e97c9
--- /dev/null
@@ -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)
+       }
+}
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 (file)
index 0000000..7bbed5b
--- /dev/null
@@ -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) }
index 8534a55111666b3cfe4d499fa8e0af18b7efc255..2d761c7ee7da57b1a798aa9cae0ab2414927e991 100644 (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))
                }
index a6f7173706f5f964b326fa1aa488fe83cd0f0681..b603fba6c7181fa83e6723f6ed3614322076cb3e 100644 (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)
        }
index 7c585b327e53e218b2be729b92c62ad701a66a01..8cfecafe84ab6c576a3cdc9470f0fabfcdbdf5b7 100644 (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 {
index 43b5176a535bfec0057af58b791734ee264742c9..9f0f747c042b154a337e2eaecf4df58823213f2f 100644 (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())
 }
index 2e9880bd6fc3299c5242a40ba6eb92cdef2ec7d1..d1b7ae2b22a3635295f30a44854c3f14155b7111 100644 (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] == '.'
 }
diff --git a/src/cmd/link/internal/loadpe/seh.go b/src/cmd/link/internal/loadpe/seh.go
new file mode 100644 (file)
index 0000000..a97595c
--- /dev/null
@@ -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()
+}