]> Cypherpunks.ru repositories - gostls13.git/commitdiff
cmd/link: generate .xdata PE section
authorqmuntal <quimmuntal@gmail.com>
Thu, 12 Jan 2023 14:47:19 +0000 (15:47 +0100)
committerQuim Muntal <quimmuntal@gmail.com>
Tue, 2 May 2023 09:22:56 +0000 (09:22 +0000)
This CL adds a .xdata section to the PE file generated by the Go linker.
It is also the first CL of the SEH chain that adds effective support
for unwinding the Go stack, as demonstrated by the newly added tests.

The .xdata section is a standard PE section that contains
an array of unwind data info structures. This structures are used to
record the effects a function has on the stack pointer,
and where the nonvolatile registers are saved on the stack [1].

Note that this CL still does not support unwinding the cgo stack.

Updates #57302

[1] https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64#struct-unwind_info

Change-Id: I6f305a51ed130b758ff9ca7b90c091e50a109a6f
Reviewed-on: https://go-review.googlesource.com/c/go/+/457455
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Davis Goodin <dagood@microsoft.com>
Run-TryBot: Quim Muntal <quimmuntal@gmail.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Than McIntosh <thanm@google.com>
src/cmd/link/internal/ld/asmb.go
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/sym/symkind.go
src/cmd/link/internal/sym/symkind_string.go
src/internal/syscall/windows/syscall_windows.go
src/internal/syscall/windows/zsyscall_windows.go
src/runtime/runtime-seh_windows_test.go

index fc088be51eee5b292fc2eabc0c728e4967841006..ca9a57741c03939c677664c6acf9f9138844dfbb 100644 (file)
@@ -63,6 +63,9 @@ func asmb(ctxt *Link) {
        if Segpdata.Filelen > 0 {
                writeParallel(&wg, pdatablk, ctxt, Segpdata.Fileoff, Segpdata.Vaddr, Segpdata.Filelen)
        }
+       if Segxdata.Filelen > 0 {
+               writeParallel(&wg, xdatablk, ctxt, Segxdata.Fileoff, Segxdata.Vaddr, Segxdata.Filelen)
+       }
 
        wg.Wait()
 }
index 849629ebe3c63e5a7bb33a0650419747224b3ba8..0a0c17e9281d419773a9832365d2db6573b7fec4 100644 (file)
@@ -1158,6 +1158,10 @@ func pdatablk(ctxt *Link, out *OutBuf, addr int64, size int64) {
        writeBlocks(ctxt, out, ctxt.outSem, ctxt.loader, []loader.Sym{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[:])
+}
+
 var covCounterDataStartOff, covCounterDataLen uint64
 
 var zeros [512]byte
@@ -1686,6 +1690,10 @@ func (ctxt *Link) dodata(symGroupType []sym.SymKind) {
                sect.Extnum = n
                n++
        }
+       for _, sect := range Segxdata.Sections {
+               sect.Extnum = n
+               n++
+       }
 }
 
 // allocateDataSectionForSym creates a new sym.Section into which a
@@ -2164,7 +2172,12 @@ func (state *dodataState) allocateSEHSections(ctxt *Link) {
        if sehp.pdata > 0 {
                sect := state.allocateDataSectionForSym(&Segpdata, sehp.pdata, 04)
                state.assignDsymsToSection(sect, []loader.Sym{sehp.pdata}, sym.SRODATA, aligndatsize)
-               state.checkdatsize(sym.SPDATASECT)
+               state.checkdatsize(sym.SSEHSECT)
+       }
+       if sehp.xdata > 0 {
+               sect := state.allocateNamedDataSection(&Segxdata, ".xdata", []sym.SymKind{}, 04)
+               state.assignDsymsToSection(sect, []loader.Sym{sehp.xdata}, sym.SRODATA, aligndatsize)
+               state.checkdatsize(sym.SSEHSECT)
        }
 }
 
@@ -2719,6 +2732,21 @@ func (ctxt *Link) address() []*sym.Segment {
                Segpdata.Length = va - Segpdata.Vaddr
        }
 
+       if len(Segxdata.Sections) > 0 {
+               va = uint64(Rnd(int64(va), int64(*FlagRound)))
+               order = append(order, &Segxdata)
+               Segxdata.Rwx = 04
+               Segxdata.Vaddr = va
+               // Segxdata.Sections is intended to contain just one section.
+               // Loop through the slice anyway for consistency.
+               for _, s := range Segxdata.Sections {
+                       va = uint64(Rnd(int64(va), int64(s.Align)))
+                       s.Vaddr = va
+                       va += s.Length
+               }
+               Segxdata.Length = va - Segxdata.Vaddr
+       }
+
        va = uint64(Rnd(int64(va), int64(*FlagRound)))
        order = append(order, &Segdwarf)
        Segdwarf.Rwx = 06
@@ -2770,8 +2798,10 @@ func (ctxt *Link) address() []*sym.Segment {
                }
        }
 
-       if sect := ldr.SymSect(sehp.pdata); sect != nil {
-               ldr.AddToSymValue(sehp.pdata, int64(sect.Vaddr))
+       for _, s := range []loader.Sym{sehp.pdata, sehp.xdata} {
+               if sect := ldr.SymSect(s); sect != nil {
+                       ldr.AddToSymValue(s, int64(sect.Vaddr))
+               }
        }
 
        if ctxt.BuildMode == BuildModeShared {
index f1eff33c6e78b79d7ca7ccca39ea1542eddc64af..5b6575b3fb2a35e7f9826cb05e57847476bc5fb5 100644 (file)
@@ -329,8 +329,9 @@ var (
        Segdata      sym.Segment
        Segdwarf     sym.Segment
        Segpdata     sym.Segment // windows-only
+       Segxdata     sym.Segment // windows-only
 
-       Segments = []*sym.Segment{&Segtext, &Segrodata, &Segrelrodata, &Segdata, &Segdwarf, &Segpdata}
+       Segments = []*sym.Segment{&Segtext, &Segrodata, &Segrelrodata, &Segdata, &Segdwarf, &Segpdata, &Segxdata}
 )
 
 const pkgdef = "__.PKGDEF"
index b07f2763eb6591dd63311fec159ad0405c26d723..9f4b00371aed52354340d4770abb38ef957dd761 100644 (file)
@@ -434,6 +434,7 @@ type peFile struct {
        bssSect        *peSection
        ctorsSect      *peSection
        pdataSect      *peSection
+       xdataSect      *peSection
        nextSectOffset uint32
        nextFileOffset uint32
        symtabOffset   int64 // offset to the start of symbol table
@@ -501,6 +502,8 @@ func (f *peFile) addDWARF() {
 
 // addSEH adds SEH information to the COFF file f.
 func (f *peFile) addSEH(ctxt *Link) {
+       // .pdata section can exist without the .xdata section.
+       // .xdata section depends on the .pdata section.
        if Segpdata.Length == 0 {
                return
        }
@@ -512,10 +515,19 @@ func (f *peFile) addSEH(ctxt *Link) {
        }
        pefile.pdataSect = d
        d.checkSegment(&Segpdata)
-       // TODO: remove extraSize once the dummy unwind info is removed from the .pdata section.
-       const extraSize = 12
-       pefile.dataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress = d.virtualAddress + extraSize
-       pefile.dataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size = d.virtualSize - extraSize
+       pefile.dataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress = d.virtualAddress
+       pefile.dataDirectory[pe.IMAGE_DIRECTORY_ENTRY_EXCEPTION].Size = d.virtualSize
+
+       if Segxdata.Length > 0 {
+               d = pefile.addSection(".xdata", int(Segxdata.Length), int(Segxdata.Length))
+               d.characteristics = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ
+               if ctxt.LinkMode == LinkExternal {
+                       // Some gcc versions don't honor the default alignment for the .xdata section.
+                       d.characteristics |= IMAGE_SCN_ALIGN_4BYTES
+               }
+               pefile.xdataSect = d
+               d.checkSegment(&Segxdata)
+       }
 }
 
 // addInitArray adds .ctors COFF section to the file f.
@@ -626,6 +638,9 @@ func (f *peFile) emitRelocations(ctxt *Link) {
        if sehp.pdata != 0 {
                sects = append(sects, relsect{f.pdataSect, &Segpdata, []loader.Sym{sehp.pdata}})
        }
+       if sehp.xdata != 0 {
+               sects = append(sects, relsect{f.xdataSect, &Segxdata, []loader.Sym{sehp.xdata}})
+       }
        for _, s := range sects {
                s.peSect.emitRelocations(ctxt.Out, func() int {
                        var n int
index b95084751ccf5d5a1d052775112c6ff216743b03..5379528c30f540c3d4499b77bea9cc5dcff2a539 100644 (file)
@@ -12,6 +12,7 @@ import (
 
 var sehp struct {
        pdata loader.Sym
+       xdata loader.Sym
 }
 
 func writeSEH(ctxt *Link) {
@@ -29,11 +30,15 @@ func writeSEHAMD64(ctxt *Link) {
                s.SetAlign(4)
                return s
        }
-       pdata := mkSecSym(".pdata", sym.SPDATASECT)
-       // TODO: the following 12 bytes represent a dummy unwind info,
-       // remove once unwind infos are encoded in the .xdata section.
-       pdata.AddUint64(ctxt.Arch, 0)
-       pdata.AddUint32(ctxt.Arch, 0)
+       pdata := mkSecSym(".pdata", sym.SSEHSECT)
+       xdata := mkSecSym(".xdata", sym.SSEHSECT)
+       // The .xdata entries have very low cardinality
+       // as it only contains frame pointer operations,
+       // which are very similar across functions.
+       // These are referenced by .pdata entries using
+       // an RVA, so it is possible, and binary-size wise,
+       // to deduplicate .xdata entries.
+       uwcache := make(map[string]int64) // aux symbol name --> .xdata offset
        for _, s := range ctxt.Textp {
                if fi := ldr.FuncInfo(s); !fi.Valid() || fi.TopFrame() {
                        continue
@@ -42,13 +47,20 @@ func writeSEHAMD64(ctxt *Link) {
                if uw == 0 {
                        continue
                }
+               name := ctxt.SymName(uw)
+               off, cached := uwcache[name]
+               if !cached {
+                       off = xdata.Size()
+                       uwcache[name] = off
+                       xdata.AddBytes(ldr.Data(uw))
+               }
 
                // Reference:
                // https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64#struct-runtime_function
                pdata.AddPEImageRelativeAddrPlus(ctxt.Arch, s, 0)
                pdata.AddPEImageRelativeAddrPlus(ctxt.Arch, s, ldr.SymSize(s))
-               // TODO: reference the .xdata symbol.
-               pdata.AddPEImageRelativeAddrPlus(ctxt.Arch, pdata.Sym(), 0)
+               pdata.AddPEImageRelativeAddrPlus(ctxt.Arch, xdata.Sym(), off)
        }
        sehp.pdata = pdata.Sym()
+       sehp.xdata = xdata.Sym()
 }
index acb96ad0ad224afd5ce392ca29611beb71fb5141..77dbf75a51cc5938096aeb2511bc151581976e9a 100644 (file)
@@ -126,7 +126,7 @@ const (
 
        // SEH symbol types
        SSEHUNWINDINFO
-       SPDATASECT
+       SSEHSECT
 )
 
 // AbiSymKindToSymKind maps values read from object files (which are
index 30de0a812f5a8527cbe2c98e8b72243a2ee31d9e..62b4fd92e505816c82e9e72297ca2a399e920b3a 100644 (file)
@@ -68,12 +68,12 @@ func _() {
        _ = x[SDWARFLOC-57]
        _ = x[SDWARFLINES-58]
        _ = x[SSEHUNWINDINFO-59]
-       _ = x[SPDATASECT-60]
+       _ = x[SSEHSECT-60]
 }
 
-const _SymKind_name = "SxxxSTEXTSELFRXSECTSMACHOPLTSTYPESSTRINGSGOSTRINGSGOFUNCSGCBITSSRODATASFUNCTABSELFROSECTSTYPERELROSSTRINGRELROSGOSTRINGRELROSGOFUNCRELROSGCBITSRELROSRODATARELROSFUNCTABRELROSTYPELINKSITABLINKSSYMTABSPCLNTABSFirstWritableSBUILDINFOSELFSECTSMACHOSMACHOGOTSWINDOWSSELFGOTSNOPTRDATASINITARRSDATASXCOFFTOCSBSSSNOPTRBSSSLIBFUZZER_8BIT_COUNTERSCOVERAGE_COUNTERSCOVERAGE_AUXVARSTLSBSSSXREFSMACHOSYMSTRSMACHOSYMTABSMACHOINDIRECTPLTSMACHOINDIRECTGOTSFILEPATHSDYNIMPORTSHOSTOBJSUNDEFEXTSDWARFSECTSDWARFCUINFOSDWARFCONSTSDWARFFCNSDWARFABSFCNSDWARFTYPESDWARFVARSDWARFRANGESDWARFLOCSDWARFLINESSSEHUNWINDINFOSPDATASECT"
+const _SymKind_name = "SxxxSTEXTSELFRXSECTSMACHOPLTSTYPESSTRINGSGOSTRINGSGOFUNCSGCBITSSRODATASFUNCTABSELFROSECTSTYPERELROSSTRINGRELROSGOSTRINGRELROSGOFUNCRELROSGCBITSRELROSRODATARELROSFUNCTABRELROSTYPELINKSITABLINKSSYMTABSPCLNTABSFirstWritableSBUILDINFOSELFSECTSMACHOSMACHOGOTSWINDOWSSELFGOTSNOPTRDATASINITARRSDATASXCOFFTOCSBSSSNOPTRBSSSLIBFUZZER_8BIT_COUNTERSCOVERAGE_COUNTERSCOVERAGE_AUXVARSTLSBSSSXREFSMACHOSYMSTRSMACHOSYMTABSMACHOINDIRECTPLTSMACHOINDIRECTGOTSFILEPATHSDYNIMPORTSHOSTOBJSUNDEFEXTSDWARFSECTSDWARFCUINFOSDWARFCONSTSDWARFFCNSDWARFABSFCNSDWARFTYPESDWARFVARSDWARFRANGESDWARFLOCSDWARFLINESSSEHUNWINDINFOSSEHSECT"
 
-var _SymKind_index = [...]uint16{0, 4, 9, 19, 28, 33, 40, 49, 56, 63, 70, 78, 88, 98, 110, 124, 136, 148, 160, 173, 182, 191, 198, 206, 220, 230, 238, 244, 253, 261, 268, 278, 286, 291, 300, 304, 313, 336, 353, 369, 376, 381, 393, 405, 422, 439, 448, 458, 466, 475, 485, 497, 508, 517, 529, 539, 548, 559, 568, 579, 593, 603}
+var _SymKind_index = [...]uint16{0, 4, 9, 19, 28, 33, 40, 49, 56, 63, 70, 78, 88, 98, 110, 124, 136, 148, 160, 173, 182, 191, 198, 206, 220, 230, 238, 244, 253, 261, 268, 278, 286, 291, 300, 304, 313, 336, 353, 369, 376, 381, 393, 405, 422, 439, 448, 458, 466, 475, 485, 497, 508, 517, 529, 539, 548, 559, 568, 579, 593, 601}
 
 func (i SymKind) String() string {
        if i >= SymKind(len(_SymKind_index)-1) {
index 409b334bcbcad19365d50a45c3a4138f085e5c66..cfe4695258bae3a66dfce42682bf4387f6c46328 100644 (file)
@@ -401,3 +401,4 @@ type FILE_ID_BOTH_DIR_INFO struct {
 //sys  GetVolumeInformationByHandle(file syscall.Handle, volumeNameBuffer *uint16, volumeNameSize uint32, volumeNameSerialNumber *uint32, maximumComponentLength *uint32, fileSystemFlags *uint32, fileSystemNameBuffer *uint16, fileSystemNameSize uint32) (err error) = GetVolumeInformationByHandleW
 
 //sys  RtlLookupFunctionEntry(pc uintptr, baseAddress *uintptr, table *byte) (ret uintptr) = kernel32.RtlLookupFunctionEntry
+//sys  RtlVirtualUnwind(handlerType uint32, baseAddress uintptr, pc uintptr, entry uintptr, ctxt uintptr, data *uintptr, frame *uintptr, ctxptrs *byte) (ret uintptr) = kernel32.RtlVirtualUnwind
index 4a6ca406d170d25ce1ab08083803a0307452cd5a..32744b00fc7bb1985ed8009d2c201309dbbf09e4 100644 (file)
@@ -70,6 +70,7 @@ var (
        procMoveFileExW                   = modkernel32.NewProc("MoveFileExW")
        procMultiByteToWideChar           = modkernel32.NewProc("MultiByteToWideChar")
        procRtlLookupFunctionEntry        = modkernel32.NewProc("RtlLookupFunctionEntry")
+       procRtlVirtualUnwind              = modkernel32.NewProc("RtlVirtualUnwind")
        procSetFileInformationByHandle    = modkernel32.NewProc("SetFileInformationByHandle")
        procUnlockFileEx                  = modkernel32.NewProc("UnlockFileEx")
        procVirtualQuery                  = modkernel32.NewProc("VirtualQuery")
@@ -296,6 +297,12 @@ func RtlLookupFunctionEntry(pc uintptr, baseAddress *uintptr, table *byte) (ret
        return
 }
 
+func RtlVirtualUnwind(handlerType uint32, baseAddress uintptr, pc uintptr, entry uintptr, ctxt uintptr, data *uintptr, frame *uintptr, ctxptrs *byte) (ret uintptr) {
+       r0, _, _ := syscall.Syscall9(procRtlVirtualUnwind.Addr(), 8, uintptr(handlerType), uintptr(baseAddress), uintptr(pc), uintptr(entry), uintptr(ctxt), uintptr(unsafe.Pointer(data)), uintptr(unsafe.Pointer(frame)), uintptr(unsafe.Pointer(ctxptrs)), 0)
+       ret = uintptr(r0)
+       return
+}
+
 func SetFileInformationByHandle(handle syscall.Handle, fileInformationClass uint32, buf uintptr, bufsize uint32) (err error) {
        r1, _, e1 := syscall.Syscall6(procSetFileInformationByHandle.Addr(), 4, uintptr(handle), uintptr(fileInformationClass), uintptr(buf), uintptr(bufsize), 0, 0)
        if r1 == 0 {
index 23f5b87bf54700f91d7740672ffcd5785473e1fe..c8a4a593b979c42904fa5fa26cc3c33bc91c3933 100644 (file)
@@ -8,7 +8,9 @@ import (
        "internal/abi"
        "internal/syscall/windows"
        "runtime"
+       "slices"
        "testing"
+       "unsafe"
 )
 
 func sehf1() int {
@@ -61,3 +63,129 @@ func TestSehLookupFunctionEntry(t *testing.T) {
                }
        }
 }
+
+func sehCallers() []uintptr {
+       // We don't need a real context,
+       // RtlVirtualUnwind just needs a context with
+       // valid a pc, sp and fp (aka bp).
+       ctx := runtime.NewContextStub()
+
+       pcs := make([]uintptr, 15)
+       var base, frame uintptr
+       var n int
+       for i := 0; i < len(pcs); i++ {
+               fn := windows.RtlLookupFunctionEntry(ctx.GetPC(), &base, nil)
+               if fn == 0 {
+                       break
+               }
+               windows.RtlVirtualUnwind(0, base, ctx.GetPC(), fn, uintptr(unsafe.Pointer(&ctx)), nil, &frame, nil)
+               n++
+               pcs[i] = ctx.GetPC()
+       }
+       return pcs[:n]
+}
+
+// SEH unwinding does not report inlined frames.
+//
+//go:noinline
+func sehf3(pan bool) []uintptr {
+       return sehf4(pan)
+}
+
+//go:noinline
+func sehf4(pan bool) []uintptr {
+       var pcs []uintptr
+       if pan {
+               panic("sehf4")
+       }
+       pcs = sehCallers()
+       return pcs
+}
+
+func testSehCallersEqual(t *testing.T, pcs []uintptr, want []string) {
+       t.Helper()
+       got := make([]string, 0, len(want))
+       for _, pc := range pcs {
+               fn := runtime.FuncForPC(pc)
+               if fn == nil || len(got) >= len(want) {
+                       break
+               }
+               name := fn.Name()
+               switch name {
+               case "runtime.deferCallSave", "runtime.runOpenDeferFrame", "runtime.panicmem":
+                       // These functions are skipped as they appear inconsistently depending
+                       // whether inlining is on or off.
+                       continue
+               }
+               got = append(got, name)
+       }
+       if !slices.Equal(want, got) {
+               t.Fatalf("wanted %v, got %v", want, got)
+       }
+}
+
+func TestSehUnwind(t *testing.T) {
+       if runtime.GOARCH != "amd64" {
+               t.Skip("skipping amd64-only test")
+       }
+       pcs := sehf3(false)
+       testSehCallersEqual(t, pcs, []string{"runtime_test.sehCallers", "runtime_test.sehf4",
+               "runtime_test.sehf3", "runtime_test.TestSehUnwind"})
+}
+
+func TestSehUnwindPanic(t *testing.T) {
+       if runtime.GOARCH != "amd64" {
+               t.Skip("skipping amd64-only test")
+       }
+       want := []string{"runtime_test.sehCallers", "runtime_test.TestSehUnwindPanic.func1", "runtime.gopanic",
+               "runtime_test.sehf4", "runtime_test.sehf3", "runtime_test.TestSehUnwindPanic"}
+       defer func() {
+               if r := recover(); r == nil {
+                       t.Fatal("did not panic")
+               }
+               pcs := sehCallers()
+               testSehCallersEqual(t, pcs, want)
+       }()
+       sehf3(true)
+}
+
+func TestSehUnwindDoublePanic(t *testing.T) {
+       if runtime.GOARCH != "amd64" {
+               t.Skip("skipping amd64-only test")
+       }
+       want := []string{"runtime_test.sehCallers", "runtime_test.TestSehUnwindDoublePanic.func1.1", "runtime.gopanic",
+               "runtime_test.TestSehUnwindDoublePanic.func1", "runtime.gopanic", "runtime_test.TestSehUnwindDoublePanic"}
+       defer func() {
+               defer func() {
+                       if recover() == nil {
+                               t.Fatal("did not panic")
+                       }
+                       pcs := sehCallers()
+                       testSehCallersEqual(t, pcs, want)
+               }()
+               if recover() == nil {
+                       t.Fatal("did not panic")
+               }
+               panic(2)
+       }()
+       panic(1)
+}
+
+func TestSehUnwindNilPointerPanic(t *testing.T) {
+       if runtime.GOARCH != "amd64" {
+               t.Skip("skipping amd64-only test")
+       }
+       want := []string{"runtime_test.sehCallers", "runtime_test.TestSehUnwindNilPointerPanic.func1", "runtime.gopanic",
+               "runtime.sigpanic", "runtime_test.TestSehUnwindNilPointerPanic"}
+       defer func() {
+               if r := recover(); r == nil {
+                       t.Fatal("did not panic")
+               }
+               pcs := sehCallers()
+               testSehCallersEqual(t, pcs, want)
+       }()
+       var p *int
+       if *p == 3 {
+               t.Fatal("did not see nil pointer panic")
+       }
+}