github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/link/loadpe/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 loadpe
     6  
     7  import (
     8  	"fmt"
     9  	"sort"
    10  
    11  	"github.com/go-asm/go/cmd/link/loader"
    12  	"github.com/go-asm/go/cmd/link/sym"
    13  	"github.com/go-asm/go/cmd/objabi"
    14  	"github.com/go-asm/go/cmd/sys"
    15  )
    16  
    17  const (
    18  	UNW_FLAG_EHANDLER  = 1 << 3
    19  	UNW_FLAG_UHANDLER  = 2 << 3
    20  	UNW_FLAG_CHAININFO = 4 << 3
    21  	unwStaticDataSize  = 4 // Bytes of unwind data before the variable length part.
    22  	unwCodeSize        = 2 // Bytes per unwind code.
    23  )
    24  
    25  // processSEH walks all pdata relocations looking for exception handler function symbols.
    26  // We want to mark these as reachable if the function that they protect is reachable
    27  // in the final binary.
    28  func processSEH(ldr *loader.Loader, arch *sys.Arch, pdata sym.LoaderSym, xdata sym.LoaderSym) error {
    29  	switch arch.Family {
    30  	case sys.AMD64:
    31  		ldr.SetAttrReachable(pdata, true)
    32  		if xdata != 0 {
    33  			ldr.SetAttrReachable(xdata, true)
    34  		}
    35  		return processSEHAMD64(ldr, pdata)
    36  	default:
    37  		// TODO: support SEH on other architectures.
    38  		return fmt.Errorf("unsupported architecture for SEH: %v", arch.Family)
    39  	}
    40  }
    41  
    42  func processSEHAMD64(ldr *loader.Loader, pdata sym.LoaderSym) error {
    43  	// The following loop traverses a list of pdata entries,
    44  	// each entry being 3 relocations long. The first relocation
    45  	// is a pointer to the function symbol to which the pdata entry
    46  	// corresponds. The third relocation is a pointer to the
    47  	// corresponding .xdata entry.
    48  	// Reference:
    49  	// https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64#struct-runtime_function
    50  	rels := ldr.Relocs(pdata)
    51  	if rels.Count()%3 != 0 {
    52  		return fmt.Errorf(".pdata symbol %q has invalid relocation count", ldr.SymName(pdata))
    53  	}
    54  	for i := 0; i < rels.Count(); i += 3 {
    55  		xrel := rels.At(i + 2)
    56  		handler := findHandlerInXDataAMD64(ldr, xrel.Sym(), xrel.Add())
    57  		if handler != 0 {
    58  			sb := ldr.MakeSymbolUpdater(rels.At(i).Sym())
    59  			r, _ := sb.AddRel(objabi.R_KEEP)
    60  			r.SetSym(handler)
    61  		}
    62  	}
    63  	return nil
    64  }
    65  
    66  // findHandlerInXDataAMD64 finds the symbol in the .xdata section that
    67  // corresponds to the exception handler.
    68  // Reference:
    69  // https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64#struct-unwind_info
    70  func findHandlerInXDataAMD64(ldr *loader.Loader, xsym sym.LoaderSym, add int64) loader.Sym {
    71  	data := ldr.Data(xsym)
    72  	if add < 0 || add+unwStaticDataSize > int64(len(data)) {
    73  		return 0
    74  	}
    75  	data = data[add:]
    76  	var isChained bool
    77  	switch flag := data[0]; {
    78  	case flag&UNW_FLAG_EHANDLER != 0 || flag&UNW_FLAG_UHANDLER != 0:
    79  		// Exception handler.
    80  	case flag&UNW_FLAG_CHAININFO != 0:
    81  		isChained = true
    82  	default:
    83  		// Nothing to do.
    84  		return 0
    85  	}
    86  	codes := data[2]
    87  	if codes%2 != 0 {
    88  		// There are always an even number of unwind codes, even if the last one is unused.
    89  		codes += 1
    90  	}
    91  	// The exception handler relocation is the first relocation after the unwind codes,
    92  	// unless it is chained, but we will handle this case later.
    93  	targetOff := add + unwStaticDataSize + unwCodeSize*int64(codes)
    94  	xrels := ldr.Relocs(xsym)
    95  	xrelsCount := xrels.Count()
    96  	idx := sort.Search(xrelsCount, func(i int) bool {
    97  		return int64(xrels.At(i).Off()) >= targetOff
    98  	})
    99  	if idx == xrelsCount {
   100  		return 0
   101  	}
   102  	if isChained {
   103  		// The third relocations references the next .xdata entry in the chain, recurse.
   104  		idx += 2
   105  		if idx >= xrelsCount {
   106  			return 0
   107  		}
   108  		r := xrels.At(idx)
   109  		return findHandlerInXDataAMD64(ldr, r.Sym(), r.Add())
   110  	}
   111  	return xrels.At(idx).Sym()
   112  }