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  }