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 }