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