| // Copyright 2023 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package loadpe |
| |
| import ( |
| "cmd/internal/objabi" |
| "cmd/internal/sys" |
| "cmd/link/internal/loader" |
| "cmd/link/internal/sym" |
| "fmt" |
| "sort" |
| ) |
| |
| const ( |
| UNW_FLAG_EHANDLER = 1 << 3 |
| UNW_FLAG_UHANDLER = 2 << 3 |
| UNW_FLAG_CHAININFO = 4 << 3 |
| unwStaticDataSize = 4 // Bytes of unwind data before the variable length part. |
| unwCodeSize = 2 // Bytes per unwind code. |
| ) |
| |
| // processSEH walks all pdata relocations looking for exception handler function symbols. |
| // We want to mark these as reachable if the function that they protect is reachable |
| // in the final binary. |
| func processSEH(ldr *loader.Loader, arch *sys.Arch, pdata sym.LoaderSym, xdata sym.LoaderSym) error { |
| switch arch.Family { |
| case sys.AMD64: |
| ldr.SetAttrReachable(pdata, true) |
| if xdata != 0 { |
| ldr.SetAttrReachable(xdata, true) |
| } |
| return processSEHAMD64(ldr, pdata) |
| default: |
| // TODO: support SEH on other architectures. |
| return fmt.Errorf("unsupported architecture for SEH: %v", arch.Family) |
| } |
| } |
| |
| func processSEHAMD64(ldr *loader.Loader, pdata sym.LoaderSym) error { |
| // The following loop traverses a list of pdata entries, |
| // each entry being 3 relocations long. The first relocation |
| // is a pointer to the function symbol to which the pdata entry |
| // corresponds. The third relocation is a pointer to the |
| // corresponding .xdata entry. |
| // Reference: |
| // https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64#struct-runtime_function |
| rels := ldr.Relocs(pdata) |
| if rels.Count()%3 != 0 { |
| return fmt.Errorf(".pdata symbol %q has invalid relocation count", ldr.SymName(pdata)) |
| } |
| for i := 0; i < rels.Count(); i += 3 { |
| xrel := rels.At(i + 2) |
| handler := findHandlerInXDataAMD64(ldr, xrel.Sym(), xrel.Add()) |
| if handler != 0 { |
| sb := ldr.MakeSymbolUpdater(rels.At(i).Sym()) |
| r, _ := sb.AddRel(objabi.R_KEEP) |
| r.SetSym(handler) |
| } |
| } |
| return nil |
| } |
| |
| // findHandlerInXDataAMD64 finds the symbol in the .xdata section that |
| // corresponds to the exception handler. |
| // Reference: |
| // https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64#struct-unwind_info |
| func findHandlerInXDataAMD64(ldr *loader.Loader, xsym sym.LoaderSym, add int64) loader.Sym { |
| data := ldr.Data(xsym) |
| if add < 0 || add+unwStaticDataSize > int64(len(data)) { |
| return 0 |
| } |
| data = data[add:] |
| var isChained bool |
| switch flag := data[0]; { |
| case flag&UNW_FLAG_EHANDLER != 0 || flag&UNW_FLAG_UHANDLER != 0: |
| // Exception handler. |
| case flag&UNW_FLAG_CHAININFO != 0: |
| isChained = true |
| default: |
| // Nothing to do. |
| return 0 |
| } |
| codes := data[2] |
| if codes%2 != 0 { |
| // There are always an even number of unwind codes, even if the last one is unused. |
| codes += 1 |
| } |
| // The exception handler relocation is the first relocation after the unwind codes, |
| // unless it is chained, but we will handle this case later. |
| targetOff := add + unwStaticDataSize + unwCodeSize*int64(codes) |
| xrels := ldr.Relocs(xsym) |
| xrelsCount := xrels.Count() |
| idx := sort.Search(xrelsCount, func(i int) bool { |
| return int64(xrels.At(i).Off()) >= targetOff |
| }) |
| if idx == xrelsCount { |
| return 0 |
| } |
| if isChained { |
| // The third relocations references the next .xdata entry in the chain, recurse. |
| idx += 2 |
| if idx >= xrelsCount { |
| return 0 |
| } |
| r := xrels.At(idx) |
| return findHandlerInXDataAMD64(ldr, r.Sym(), r.Add()) |
| } |
| return xrels.At(idx).Sym() |
| } |