diff options
| author | qmuntal <quimmuntal@gmail.com> | 2023-01-16 16:21:48 +0100 |
|---|---|---|
| committer | Quim Muntal <quimmuntal@gmail.com> | 2023-04-05 19:44:37 +0000 |
| commit | 76ac54b50ea39bbb1389ecfed71f4f0991cb4289 (patch) | |
| tree | 5c14d62f6a442fc72a011b68a247fc7730b184a6 /src/cmd/internal/obj/x86 | |
| parent | d15fcbc79f252fe5c01e099ab89b58f1de61df32 (diff) | |
| download | go-76ac54b50ea39bbb1389ecfed71f4f0991cb4289.tar.xz | |
cmd/internal/obj: generate SEH aux symbols for windows/amd64
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 <dagood@microsoft.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Than McIntosh <thanm@google.com>
Run-TryBot: Quim Muntal <quimmuntal@gmail.com>
Diffstat (limited to 'src/cmd/internal/obj/x86')
| -rw-r--r-- | src/cmd/internal/obj/x86/obj6.go | 2 | ||||
| -rw-r--r-- | src/cmd/internal/obj/x86/seh.go | 141 |
2 files changed, 143 insertions, 0 deletions
diff --git a/src/cmd/internal/obj/x86/obj6.go b/src/cmd/internal/obj/x86/obj6.go index 6b6aa8809a..c85b5018eb 100644 --- a/src/cmd/internal/obj/x86/obj6.go +++ b/src/cmd/internal/obj/x86/obj6.go @@ -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)) @@ -1526,6 +1527,7 @@ var Linkamd64 = obj.LinkArch{ Preprocess: preprocess, Assemble: span6, Progedit: progedit, + SEH: populateSeh, UnaryDst: unaryDst, DWARFRegisters: AMD64DWARFRegisters, } diff --git a/src/cmd/internal/obj/x86/seh.go b/src/cmd/internal/obj/x86/seh.go new file mode 100644 index 0000000000..e7d3d571b7 --- /dev/null +++ b/src/cmd/internal/obj/x86/seh.go @@ -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. + }) +} |
