Skip to content

Commit

Permalink
cmd/internal/obj: generate SEH aux symbols for windows/amd64
Browse files Browse the repository at this point in the history
This CL updates the Go compiler so it generate SEH unwind info [1] as a
function auxiliary symbol when building for windows/amd64.

A follow up CL will teach the Go linker how to assemble these codes
into the PE .xdata section.

Updates #57302

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

Change-Id: I40ae0437bfee326c1a67c2b5e1496f0bf3ecea17
Reviewed-on: https://go-review.googlesource.com/c/go/+/461749
Reviewed-by: Davis Goodin <[email protected]>
Reviewed-by: Michael Knyszek <[email protected]>
TryBot-Result: Gopher Robot <[email protected]>
Reviewed-by: Than McIntosh <[email protected]>
Run-TryBot: Quim Muntal <[email protected]>
  • Loading branch information
qmuntal committed Apr 5, 2023
1 parent d15fcbc commit 76ac54b
Show file tree
Hide file tree
Showing 11 changed files with 170 additions and 7 deletions.
1 change: 1 addition & 0 deletions src/cmd/internal/goobj/objfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,7 @@ const (
AuxPcinline
AuxPcdata
AuxWasmImport
AuxSehUnwindInfo
)

func (a *Aux) Type() uint8 { return a[0] }
Expand Down
3 changes: 3 additions & 0 deletions src/cmd/internal/obj/link.go
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,8 @@ type FuncInfo struct {
FuncInfoSym *LSym
WasmImportSym *LSym
WasmImport *WasmImport

sehUnwindInfoSym *LSym
}

// JumpTable represents a table used for implementing multi-way
Expand Down Expand Up @@ -1072,6 +1074,7 @@ type LinkArch struct {
Preprocess func(*Link, *LSym, ProgAlloc)
Assemble func(*Link, *LSym, ProgAlloc)
Progedit func(*Link, *Prog, ProgAlloc)
SEH func(*Link, *LSym) *LSym
UnaryDst map[As]bool // Instruction takes one operand, a destination.
DWARFRegisters map[int16]int16
}
8 changes: 7 additions & 1 deletion src/cmd/internal/obj/objfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,9 @@ func (w *writer) Aux(s *LSym) {
if fn.Pcln.Pcinline != nil && fn.Pcln.Pcinline.Size != 0 {
w.aux1(goobj.AuxPcinline, fn.Pcln.Pcinline)
}
if fn.sehUnwindInfoSym != nil && fn.sehUnwindInfoSym.Size != 0 {
w.aux1(goobj.AuxSehUnwindInfo, fn.sehUnwindInfoSym)
}
for _, pcSym := range fn.Pcln.Pcdata {
w.aux1(goobj.AuxPcdata, pcSym)
}
Expand Down Expand Up @@ -707,6 +710,9 @@ func nAuxSym(s *LSym) int {
if fn.Pcln.Pcinline != nil && fn.Pcln.Pcinline.Size != 0 {
n++
}
if fn.sehUnwindInfoSym != nil && fn.sehUnwindInfoSym.Size != 0 {
n++
}
n += len(fn.Pcln.Pcdata)
if fn.WasmImport != nil {
if fn.WasmImportSym == nil || fn.WasmImportSym.Size == 0 {
Expand Down Expand Up @@ -770,7 +776,7 @@ func genFuncInfoSyms(ctxt *Link) {
fn.FuncInfoSym = isym
b.Reset()

auxsyms := []*LSym{fn.dwarfRangesSym, fn.dwarfLocSym, fn.dwarfDebugLinesSym, fn.dwarfInfoSym, fn.WasmImportSym}
auxsyms := []*LSym{fn.dwarfRangesSym, fn.dwarfLocSym, fn.dwarfDebugLinesSym, fn.dwarfInfoSym, fn.WasmImportSym, fn.sehUnwindInfoSym}
for _, s := range auxsyms {
if s == nil || s.Size == 0 {
continue
Expand Down
3 changes: 3 additions & 0 deletions src/cmd/internal/obj/plist.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@ func Flushplist(ctxt *Link, plist *Plist, newprog ProgAlloc, myimportpath string
if myimportpath != "" {
ctxt.populateDWARF(plist.Curfn, s, myimportpath)
}
if ctxt.Headtype == objabi.Hwindows && ctxt.Arch.SEH != nil {
s.Func().sehUnwindInfoSym = ctxt.Arch.SEH(ctxt, s)
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/cmd/internal/obj/sym.go
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ func (ctxt *Link) traverseFuncAux(flag traverseFlag, fsym *LSym, fn func(parent
}
}

auxsyms := []*LSym{fninfo.dwarfRangesSym, fninfo.dwarfLocSym, fninfo.dwarfDebugLinesSym, fninfo.dwarfInfoSym, fninfo.WasmImportSym}
auxsyms := []*LSym{fninfo.dwarfRangesSym, fninfo.dwarfLocSym, fninfo.dwarfDebugLinesSym, fninfo.dwarfInfoSym, fninfo.WasmImportSym, fninfo.sehUnwindInfoSym}
for _, s := range auxsyms {
if s == nil || s.Size == 0 {
continue
Expand Down
2 changes: 2 additions & 0 deletions src/cmd/internal/obj/x86/obj6.go
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
p.To.Offset += int64(bpsize)
} else {
bpsize = 0
p.From.Sym.Set(obj.AttrNoFrame, true)
}

textarg := int64(p.To.Val.(int32))
Expand Down Expand Up @@ -1526,6 +1527,7 @@ var Linkamd64 = obj.LinkArch{
Preprocess: preprocess,
Assemble: span6,
Progedit: progedit,
SEH: populateSeh,
UnaryDst: unaryDst,
DWARFRegisters: AMD64DWARFRegisters,
}
Expand Down
141 changes: 141 additions & 0 deletions src/cmd/internal/obj/x86/seh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// 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 x86

import (
"cmd/internal/obj"
"cmd/internal/objabi"
"cmd/internal/src"
"encoding/base64"
"fmt"
"math"
)

type sehbuf struct {
ctxt *obj.Link
data []byte
off int
}

func newsehbuf(ctxt *obj.Link, nodes uint8) sehbuf {
// - 8 bytes for the header
// - 2 bytes for each node
// - 2 bytes in case nodes is not even
size := 8 + nodes*2
if nodes%2 != 0 {
size += 2
}
return sehbuf{ctxt, make([]byte, size), 0}
}

func (b *sehbuf) write8(v uint8) {
b.data[b.off] = v
b.off++
}

func (b *sehbuf) write32(v uint32) {
b.ctxt.Arch.ByteOrder.PutUint32(b.data[b.off:], v)
b.off += 4
}

func (b *sehbuf) writecode(op, value uint8) {
b.write8(value<<4 | op)
}

// populateSeh generates the SEH unwind information for s.
func populateSeh(ctxt *obj.Link, s *obj.LSym) (sehsym *obj.LSym) {
if s.NoFrame() {
return
}

// This implementation expects the following function prologue layout:
// - Stack split code (optional)
// - PUSHQ BP
// - MOVQ SP, BP
//
// If the prologue layout change, the unwind information should be updated
// accordingly.

// Search for the PUSHQ BP instruction inside the prologue.
var pushbp *obj.Prog
for p := s.Func().Text; p != nil; p = p.Link {
if p.As == APUSHQ && p.From.Type == obj.TYPE_REG && p.From.Reg == REG_BP {
pushbp = p
break
}
if p.Pos.Xlogue() == src.PosPrologueEnd {
break
}
}
if pushbp == nil {
ctxt.Diag("missing frame pointer instruction: PUSHQ BP")
return
}

// It must be followed by a MOVQ SP, BP.
movbp := pushbp.Link
if movbp == nil {
ctxt.Diag("missing frame pointer instruction: MOVQ SP, BP")
return
}
if !(movbp.As == AMOVQ && movbp.From.Type == obj.TYPE_REG && movbp.From.Reg == REG_SP &&
movbp.To.Type == obj.TYPE_REG && movbp.To.Reg == REG_BP && movbp.From.Offset == 0) {
ctxt.Diag("unexpected frame pointer instruction\n%v", movbp)
return
}
if movbp.Link.Pc > math.MaxUint8 {
// SEH unwind information don't support prologues that are more than 255 bytes long.
// These are very rare, but still possible, e.g., when compiling functions with many
// parameters with -gcflags=-d=maymorestack=runtime.mayMoreStackPreempt.
// Return without reporting an error.
return
}

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

const (
UWOP_PUSH_NONVOL = 0
UWOP_SET_FPREG = 3
SEH_REG_BP = 5
)

// Fow now we only support operations which are encoded
// using a single 2-byte node, so the number of nodes
// is the number of operations.
nodes := uint8(2)
buf := newsehbuf(ctxt, nodes)
buf.write8(1) // Flags + version
buf.write8(uint8(movbp.Link.Pc)) // Size of prolog
buf.write8(nodes) // Count of nodes
buf.write8(SEH_REG_BP) // FP register

// Notes are written in reverse order of appearance.
buf.write8(uint8(movbp.Link.Pc))
buf.writecode(UWOP_SET_FPREG, 0)

buf.write8(uint8(pushbp.Link.Pc))
buf.writecode(UWOP_PUSH_NONVOL, SEH_REG_BP)

// The following 4 bytes reference the RVA of the exception handler,
// in case the function has one. We don't use it for now.
buf.write32(0)

// The list of unwind infos in a PE binary have very low cardinality
// as each info only contains frame pointer operations,
// which are very similar across functions.
// Dedup them when possible.
hash := base64.StdEncoding.EncodeToString(buf.data)
symname := fmt.Sprintf("%d.%s", len(buf.data), hash)
return ctxt.LookupInit("go:sehuw."+symname, func(s *obj.LSym) {
s.WriteBytes(ctxt, 0, buf.data)
s.Type = objabi.SSEHUNWINDINFO
s.Set(obj.AttrDuplicateOK, true)
s.Set(obj.AttrLocal, true)
// Note: AttrContentAddressable cannot be set here,
// because the content-addressable-handling code
// does not know about aux symbols.
})
}
1 change: 1 addition & 0 deletions src/cmd/internal/objabi/symkind.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,6 @@ const (
SCOVERAGE_COUNTER
SCOVERAGE_AUXVAR

SSEHUNWINDINFO
// Update cmd/link/internal/sym/AbiSymKindToSymKind for new SymKind values.
)
5 changes: 3 additions & 2 deletions src/cmd/internal/objabi/symkind_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions src/cmd/link/internal/sym/symkind.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ const (
SDWARFRANGE
SDWARFLOC
SDWARFLINES

// SEH symbol types
SSEHUNWINDINFO
)

// AbiSymKindToSymKind maps values read from object files (which are
Expand All @@ -148,6 +151,7 @@ var AbiSymKindToSymKind = [...]SymKind{
objabi.SLIBFUZZER_8BIT_COUNTER: SLIBFUZZER_8BIT_COUNTER,
objabi.SCOVERAGE_COUNTER: SCOVERAGE_COUNTER,
objabi.SCOVERAGE_AUXVAR: SCOVERAGE_AUXVAR,
objabi.SSEHUNWINDINFO: SSEHUNWINDINFO,
}

// ReadOnly are the symbol kinds that form read-only sections. In some
Expand Down
7 changes: 4 additions & 3 deletions src/cmd/link/internal/sym/symkind_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 76ac54b

Please sign in to comment.