Source file src/cmd/link/internal/loadpe/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 loadpe
     6  
     7  import (
     8  	"cmd/internal/objabi"
     9  	"cmd/internal/sys"
    10  	"cmd/link/internal/loader"
    11  	"cmd/link/internal/sym"
    12  	"fmt"
    13  	"sort"
    14  )
    15  
    16  const (
    17  	UNW_FLAG_EHANDLER  = 1 << 3
    18  	UNW_FLAG_UHANDLER  = 2 << 3
    19  	UNW_FLAG_CHAININFO = 4 << 3
    20  	unwStaticDataSize  = 4 // Bytes of unwind data before the variable length part.
    21  	unwCodeSize        = 2 // Bytes per unwind code.
    22  )
    23  
    24  // processSEH walks host-object pdata relocations and returns the set of
    25  // per-entry pdata symbols created from the input section.
    26  func processSEH(ldr *loader.Loader, arch *sys.Arch, pdata sym.LoaderSym) ([]loader.Sym, error) {
    27  	switch arch.Family {
    28  	case sys.AMD64:
    29  		return processSEHAMD64(ldr, pdata)
    30  	default:
    31  		// TODO: support SEH on other architectures.
    32  		return nil, fmt.Errorf("unsupported architecture for SEH: %v", arch.Family)
    33  	}
    34  }
    35  
    36  func processSEHAMD64(ldr *loader.Loader, pdata sym.LoaderSym) ([]loader.Sym, error) {
    37  	// The following loop traverses a list of pdata entries,
    38  	// each entry being 3 relocations long. The first relocation
    39  	// is a pointer to the function symbol to which the pdata entry
    40  	// corresponds. The third relocation is a pointer to the
    41  	// corresponding .xdata entry.
    42  	// Reference:
    43  	// https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64#struct-runtime_function
    44  	rels := ldr.Relocs(pdata)
    45  	if rels.Count()%3 != 0 {
    46  		return nil, fmt.Errorf(".pdata symbol %q has invalid relocation count", ldr.SymName(pdata))
    47  	}
    48  	data := ldr.Data(pdata)
    49  	entries := make([]loader.Sym, 0, rels.Count()/3)
    50  
    51  	for i := 0; i < rels.Count(); i += 3 {
    52  		// Create a new symbol for the pdata entry.
    53  		entry := ldr.MakeSymbolBuilder("")
    54  		entry.SetType(sym.SSEHSECT)
    55  		entry.SetAlign(4)
    56  		entry.SetSize(12)
    57  		entryOff := int(rels.At(i).Off())
    58  		entryEnd := entryOff + 4*3
    59  		entry.SetData(data[entryOff:entryEnd:entryEnd])
    60  
    61  		// Add a relocation from the target function to the pdata entry
    62  		// and to the exception handler, if present, to ensure they are
    63  		// retained by dead code elimination.
    64  		if targetFunc := rels.At(i).Sym(); targetFunc != 0 {
    65  			sb := ldr.MakeSymbolUpdater(targetFunc)
    66  			r, _ := sb.AddRel(objabi.R_KEEP)
    67  			r.SetSym(entry.Sym())
    68  			xrel := rels.At(i + 2)
    69  			if xrel.Sym() != 0 {
    70  				r, _ = sb.AddRel(objabi.R_KEEP)
    71  				r.SetSym(xrel.Sym())
    72  			}
    73  			handler := findHandlerInXDataAMD64(ldr, xrel.Sym(), xrel.Add())
    74  			if handler != 0 {
    75  				r, _ = sb.AddRel(objabi.R_KEEP)
    76  				r.SetSym(handler)
    77  			}
    78  		}
    79  
    80  		// Copy the relocations from the original .pdata entry to the new symbol,
    81  		// adjusting the offsets.
    82  		for j := range 3 {
    83  			rOld := rels.At(i + j)
    84  			typ := rOld.Type()
    85  			if rOld.Weak() {
    86  				typ |= objabi.R_WEAK
    87  			}
    88  			rel, _ := entry.AddRel(typ)
    89  			rel.SetOff(int32(j * 4))
    90  			rel.SetSiz(rOld.Siz())
    91  			rel.SetSym(rOld.Sym())
    92  			rel.SetAdd(rOld.Add())
    93  		}
    94  
    95  		entries = append(entries, entry.Sym())
    96  	}
    97  	return entries, nil
    98  }
    99  
   100  // findHandlerInXDataAMD64 finds the symbol in the .xdata section that
   101  // corresponds to the exception handler.
   102  // Reference:
   103  // https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64#struct-unwind_info
   104  func findHandlerInXDataAMD64(ldr *loader.Loader, xsym sym.LoaderSym, add int64) loader.Sym {
   105  	data := ldr.Data(xsym)
   106  	if add < 0 || add+unwStaticDataSize > int64(len(data)) {
   107  		return 0
   108  	}
   109  	data = data[add:]
   110  	var isChained bool
   111  	switch flag := data[0]; {
   112  	case flag&UNW_FLAG_EHANDLER != 0 || flag&UNW_FLAG_UHANDLER != 0:
   113  		// Exception handler.
   114  	case flag&UNW_FLAG_CHAININFO != 0:
   115  		isChained = true
   116  	default:
   117  		// Nothing to do.
   118  		return 0
   119  	}
   120  	codes := data[2]
   121  	if codes%2 != 0 {
   122  		// There are always an even number of unwind codes, even if the last one is unused.
   123  		codes += 1
   124  	}
   125  	// The exception handler relocation is the first relocation after the unwind codes,
   126  	// unless it is chained, but we will handle this case later.
   127  	targetOff := add + unwStaticDataSize + unwCodeSize*int64(codes)
   128  	xrels := ldr.Relocs(xsym)
   129  	xrelsCount := xrels.Count()
   130  	idx := sort.Search(xrelsCount, func(i int) bool {
   131  		return int64(xrels.At(i).Off()) >= targetOff
   132  	})
   133  	if idx == xrelsCount {
   134  		return 0
   135  	}
   136  	if isChained {
   137  		// The third relocations references the next .xdata entry in the chain, recurse.
   138  		idx += 2
   139  		if idx >= xrelsCount {
   140  			return 0
   141  		}
   142  		r := xrels.At(idx)
   143  		return findHandlerInXDataAMD64(ldr, r.Sym(), r.Add())
   144  	}
   145  	return xrels.At(idx).Sym()
   146  }
   147  

View as plain text