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