| // 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. |
| }) |
| } |