github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/obj/x86/seh.go (about) 1 // Copyright 2023 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package x86 6 7 import ( 8 "encoding/base64" 9 "fmt" 10 "math" 11 12 "github.com/go-asm/go/cmd/obj" 13 "github.com/go-asm/go/cmd/objabi" 14 "github.com/go-asm/go/cmd/src" 15 ) 16 17 type sehbuf struct { 18 ctxt *obj.Link 19 data []byte 20 off int 21 } 22 23 func newsehbuf(ctxt *obj.Link, nodes uint8) sehbuf { 24 // - 8 bytes for the header 25 // - 2 bytes for each node 26 // - 2 bytes in case nodes is not even 27 size := 8 + nodes*2 28 if nodes%2 != 0 { 29 size += 2 30 } 31 return sehbuf{ctxt, make([]byte, size), 0} 32 } 33 34 func (b *sehbuf) write8(v uint8) { 35 b.data[b.off] = v 36 b.off++ 37 } 38 39 func (b *sehbuf) write32(v uint32) { 40 b.ctxt.Arch.ByteOrder.PutUint32(b.data[b.off:], v) 41 b.off += 4 42 } 43 44 func (b *sehbuf) writecode(op, value uint8) { 45 b.write8(value<<4 | op) 46 } 47 48 // populateSeh generates the SEH unwind information for s. 49 func populateSeh(ctxt *obj.Link, s *obj.LSym) (sehsym *obj.LSym) { 50 if s.NoFrame() { 51 return 52 } 53 54 // This implementation expects the following function prologue layout: 55 // - Stack split code (optional) 56 // - PUSHQ BP 57 // - MOVQ SP, BP 58 // 59 // If the prologue layout change, the unwind information should be updated 60 // accordingly. 61 62 // Search for the PUSHQ BP instruction inside the prologue. 63 var pushbp *obj.Prog 64 for p := s.Func().Text; p != nil; p = p.Link { 65 if p.As == APUSHQ && p.From.Type == obj.TYPE_REG && p.From.Reg == REG_BP { 66 pushbp = p 67 break 68 } 69 if p.Pos.Xlogue() == src.PosPrologueEnd { 70 break 71 } 72 } 73 if pushbp == nil { 74 ctxt.Diag("missing frame pointer instruction: PUSHQ BP") 75 return 76 } 77 78 // It must be followed by a MOVQ SP, BP. 79 movbp := pushbp.Link 80 if movbp == nil { 81 ctxt.Diag("missing frame pointer instruction: MOVQ SP, BP") 82 return 83 } 84 if !(movbp.As == AMOVQ && movbp.From.Type == obj.TYPE_REG && movbp.From.Reg == REG_SP && 85 movbp.To.Type == obj.TYPE_REG && movbp.To.Reg == REG_BP && movbp.From.Offset == 0) { 86 ctxt.Diag("unexpected frame pointer instruction\n%v", movbp) 87 return 88 } 89 if movbp.Link.Pc > math.MaxUint8 { 90 // SEH unwind information don't support prologues that are more than 255 bytes long. 91 // These are very rare, but still possible, e.g., when compiling functions with many 92 // parameters with -gcflags=-d=maymorestack=runtime.mayMoreStackPreempt. 93 // Return without reporting an error. 94 return 95 } 96 97 // Reference: 98 // https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64#struct-unwind_info 99 100 const ( 101 UWOP_PUSH_NONVOL = 0 102 UWOP_SET_FPREG = 3 103 SEH_REG_BP = 5 104 UNW_FLAG_EHANDLER = 1 << 3 105 ) 106 107 var exceptionHandler *obj.LSym 108 var flags uint8 109 if s.Name == "runtime.asmcgocall_landingpad" { 110 // Most cgo calls go through runtime.asmcgocall_landingpad, 111 // we can use it to catch exceptions from C code. 112 // TODO: use a more generic approach to identify which calls need an exception handler. 113 exceptionHandler = ctxt.Lookup("runtime.sehtramp") 114 if exceptionHandler == nil { 115 ctxt.Diag("missing runtime.sehtramp\n") 116 return 117 } 118 flags = UNW_FLAG_EHANDLER 119 } 120 121 // Fow now we only support operations which are encoded 122 // using a single 2-byte node, so the number of nodes 123 // is the number of operations. 124 nodes := uint8(2) 125 buf := newsehbuf(ctxt, nodes) 126 buf.write8(flags | 1) // Flags + version 127 buf.write8(uint8(movbp.Link.Pc)) // Size of prolog 128 buf.write8(nodes) // Count of nodes 129 buf.write8(SEH_REG_BP) // FP register 130 131 // Notes are written in reverse order of appearance. 132 buf.write8(uint8(movbp.Link.Pc)) 133 buf.writecode(UWOP_SET_FPREG, 0) 134 135 buf.write8(uint8(pushbp.Link.Pc)) 136 buf.writecode(UWOP_PUSH_NONVOL, SEH_REG_BP) 137 138 // The following 4 bytes reference the RVA of the exception handler. 139 // The value is set to 0 for now, if an exception handler is needed, 140 // it will be updated later with a R_PEIMAGEOFF relocation to the 141 // exception handler. 142 buf.write32(0) 143 144 // The list of unwind infos in a PE binary have very low cardinality 145 // as each info only contains frame pointer operations, 146 // which are very similar across functions. 147 // Dedup them when possible. 148 hash := base64.StdEncoding.EncodeToString(buf.data) 149 symname := fmt.Sprintf("%d.%s", len(buf.data), hash) 150 return ctxt.LookupInit("go:sehuw."+symname, func(s *obj.LSym) { 151 s.WriteBytes(ctxt, 0, buf.data) 152 s.Type = objabi.SSEHUNWINDINFO 153 s.Set(obj.AttrDuplicateOK, true) 154 s.Set(obj.AttrLocal, true) 155 if exceptionHandler != nil { 156 r := obj.Addrel(s) 157 r.Off = int32(len(buf.data) - 4) 158 r.Siz = 4 159 r.Sym = exceptionHandler 160 r.Type = objabi.R_PEIMAGEOFF 161 } 162 // Note: AttrContentAddressable cannot be set here, 163 // because the content-addressable-handling code 164 // does not know about aux symbols. 165 }) 166 }