Source file src/cmd/internal/obj/x86/seh.go

     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  	"cmd/internal/obj"
     9  	"cmd/internal/objabi"
    10  	"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  		s.Set(obj.AttrContentAddressable, true)
   155  		if exceptionHandler != nil {
   156  			s.AddRel(ctxt, obj.Reloc{
   157  				Type: objabi.R_PEIMAGEOFF,
   158  				Off:  int32(len(buf.data) - 4),
   159  				Siz:  4,
   160  				Sym:  exceptionHandler,
   161  			})
   162  		}
   163  		ctxt.SEHSyms = append(ctxt.SEHSyms, s)
   164  	})
   165  }
   166  

View as plain text