Merge mozilla-central to autoland. CLOSED TREE
[gecko.git] / mozglue / misc / WindowsUnwindInfo.h
blob3667b5f2bc800afb05e87d689c222fbc20c7a1a7
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
7 #ifndef mozilla_WindowsUnwindInfo_h
8 #define mozilla_WindowsUnwindInfo_h
10 #ifdef _M_X64
12 # include <cstdint>
14 # include "mozilla/Assertions.h"
15 # include "mozilla/UniquePtr.h"
17 namespace mozilla {
19 // On Windows x64, there is no standard function prologue, hence extra
20 // information that describes the prologue must be added for each non-leaf
21 // function in order to properly unwind the stack. This extra information is
22 // grouped into so-called function tables.
24 // A function table is a contiguous array of one or more RUNTIME_FUNCTION
25 // entries. Each RUNTIME_FUNCTION entry associates a start and end offset in
26 // code with specific unwind information. The function table is present in the
27 // .pdata section of binaries for static code, and added dynamically with
28 // RtlAddFunctionTable or RtlInstallFunctionTableCallback for dynamic code.
29 // RUNTIME_FUNCTION entries point to the unwind information, which can thus
30 // live at a different location in memory, for example it lives in the .xdata
31 // section for static code.
33 // Contrary to RUNTIME_FUNCTION, Microsoft provides no standard structure
34 // definition to map the unwind information. This file thus provides some
35 // helpers to read this data, originally based on breakpad code. The unwind
36 // information is partially documented at:
37 // https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64.
39 // The unwind information stores a bytecode in UnwindInfo.unwind_code[] that
40 // describes how the instructions in the function prologue interact with the
41 // stack. An instruction in this bytecode is called an unwind code.
42 // UnwindCodeOperationCodes enumerates all opcodes used by this bytecode.
43 // Unwind codes are stored in contiguous slots of 16 bits, where each unwind
44 // code can span either 1, 2, or 3 slots depending on the opcode it uses.
45 enum UnwindOperationCodes : uint8_t {
46 // UnwindCode.operation_info == register number
47 UWOP_PUSH_NONVOL = 0,
48 // UnwindCode.operation_info == 0 or 1,
49 // alloc size in next slot (if 0) or next 2 slots (if 1)
50 UWOP_ALLOC_LARGE = 1,
51 // UnwindCode.operation_info == size of allocation / 8 - 1
52 UWOP_ALLOC_SMALL = 2,
53 // no UnwindCode.operation_info; register number UnwindInfo.frame_register
54 // receives (rsp + UnwindInfo.frame_offset*16)
55 UWOP_SET_FPREG = 3,
56 // UnwindCode.operation_info == register number, offset in next slot
57 UWOP_SAVE_NONVOL = 4,
58 // UnwindCode.operation_info == register number, offset in next 2 slots
59 UWOP_SAVE_NONVOL_FAR = 5,
60 // Version 1; undocumented; not meant for x64
61 UWOP_SAVE_XMM = 6,
62 // Version 2; undocumented
63 UWOP_EPILOG = 6,
64 // Version 1; undocumented; not meant for x64
65 UWOP_SAVE_XMM_FAR = 7,
66 // Version 2; undocumented
67 UWOP_SPARE = 7,
68 // UnwindCode.operation_info == XMM reg number, offset in next slot
69 UWOP_SAVE_XMM128 = 8,
70 // UnwindCode.operation_info == XMM reg number, offset in next 2 slots
71 UWOP_SAVE_XMM128_FAR = 9,
72 // UnwindCode.operation_info == 0: no error-code, 1: error-code
73 UWOP_PUSH_MACHFRAME = 10
76 // Strictly speaking, UnwindCode represents a slot -- not a full unwind code.
77 union UnwindCode {
78 struct {
79 uint8_t offset_in_prolog;
80 UnwindOperationCodes unwind_operation_code : 4;
81 uint8_t operation_info : 4;
83 uint16_t frame_offset;
86 // UnwindInfo is a variable-sized struct meant for C-style direct access to the
87 // unwind information. Be careful:
88 // - prefer using the size() helper method to using sizeof;
89 // - don't construct objects of this type, cast pointers instead;
90 // - consider using the IterableUnwindInfo helpers to iterate over unwind
91 // codes.
92 struct UnwindInfo {
93 uint8_t version : 3;
94 uint8_t flags : 5; // either 0, UNW_FLAG_CHAININFO, or a combination of
95 // UNW_FLAG_EHANDLER and UNW_FLAG_UHANDLER
96 uint8_t size_of_prolog;
97 uint8_t count_of_codes; // contains the length of the unwind_code[] array
98 uint8_t frame_register : 4;
99 uint8_t frame_offset : 4;
100 UnwindCode unwind_code[1]; // variable length
101 // Note: There is extra data after the variable length array if using flags
102 // UNW_FLAG_EHANDLER, UNW_FLAG_UHANDLER, or UNW_FLAG_CHAININFO. We
103 // ignore the extra data at the moment. For more details, see:
104 // https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64.
106 // When using UNW_FLAG_EHANDLER or UNW_FLAG_UHANDLER, the extra data
107 // includes handler data of unspecificied size: only the handler knows
108 // the correct size for this data. This makes it difficult to know the
109 // size of the full unwind information or to copy it in this particular
110 // case.
112 UnwindInfo(const UnwindInfo&) = delete;
113 UnwindInfo& operator=(const UnwindInfo&) = delete;
114 UnwindInfo(UnwindInfo&&) = delete;
115 UnwindInfo& operator=(UnwindInfo&&) = delete;
116 ~UnwindInfo() = delete;
118 // Size of this structure, including the variable length unwind_code array
119 // but NOT including the extra data related to flags UNW_FLAG_EHANDLER,
120 // UNW_FLAG_UHANDLER, and UNW_FLAG_CHAININFO.
122 // The places where we currently use these helpers read unwind information at
123 // function entry points; as such we expect that they may encounter
124 // UNW_FLAG_EHANDLER and/or UNW_FLAG_UHANDLER but won't need to use the
125 // associated extra data, and it is expected that they should not encounter
126 // UNW_FLAG_CHAININFO. UNW_FLAG_CHAININFO is typically used for code that
127 // lives separately from the entry point of the function to which it belongs,
128 // this code then has chained unwind info pointing to the entry point.
129 inline size_t Size() const {
130 return offsetof(UnwindInfo, unwind_code) +
131 count_of_codes * sizeof(UnwindCode);
134 // Note: We currently do not copy the extra data related to flags
135 // UNW_FLAG_EHANDLER, UNW_FLAG_UHANDLER, and UNW_FLAG_CHAININFO.
136 UniquePtr<uint8_t[]> Copy() const {
137 auto s = Size();
138 auto result = MakeUnique<uint8_t[]>(s);
139 std::memcpy(result.get(), reinterpret_cast<const void*>(this), s);
140 return result;
143 // An unwind code spans a number of slots in the unwind_code array that can
144 // vary from 1 to 3. This method assumes that the index parameter points to
145 // a slot that is the start of an unwind code. If the unwind code is
146 // well-formed, it returns true and sets its second parameter to the number
147 // of slots that the unwind code occupies.
149 // This function returns false if the unwind code is ill-formed, i.e.:
150 // - either the index points out of bounds;
151 // - or the opcode is invalid, or unexpected (e.g. UWOP_SAVE_XMM and
152 // UWOP_SAVE_XMM_FAR in version 1);
153 // - or using the correct slots count for the opcode would go out of bounds.
154 bool GetSlotsCountForCodeAt(uint8_t aIndex, uint8_t* aSlotsCount) const {
155 if (aIndex >= count_of_codes) {
156 MOZ_ASSERT_UNREACHABLE("The index is out of bounds");
157 return false;
160 const UnwindCode& unwindCode = unwind_code[aIndex];
161 uint8_t slotsCount = 0;
163 // See https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64
164 switch (unwindCode.unwind_operation_code) {
165 // Start with fixed-size opcodes common to versions 1 and 2
166 case UWOP_SAVE_NONVOL_FAR:
167 case UWOP_SAVE_XMM128_FAR:
168 slotsCount = 3;
169 break;
171 case UWOP_SAVE_NONVOL:
172 case UWOP_SAVE_XMM128:
173 slotsCount = 2;
174 break;
176 case UWOP_PUSH_NONVOL:
177 case UWOP_ALLOC_SMALL:
178 case UWOP_SET_FPREG:
179 case UWOP_PUSH_MACHFRAME:
180 slotsCount = 1;
181 break;
183 // UWOP_ALLOC_LARGE is the only variable-sized opcode. It is common to
184 // versions 1 and 2. It is ill-formed if the info is not 0 or 1.
185 case UWOP_ALLOC_LARGE:
186 if (unwindCode.operation_info > 1) {
187 MOZ_ASSERT_UNREACHABLE(
188 "Operation UWOP_ALLOC_LARGE is used, but operation_info "
189 "is not 0 or 1");
190 return false;
192 slotsCount = 2 + unwindCode.operation_info;
193 break;
195 case UWOP_SPARE:
196 if (version != 2) {
197 MOZ_ASSERT_UNREACHABLE(
198 "Operation code UWOP_SPARE is used, but version is not 2");
199 return false;
201 slotsCount = 3;
202 break;
204 case UWOP_EPILOG:
205 if (version != 2) {
206 MOZ_ASSERT_UNREACHABLE(
207 "Operation code UWOP_EPILOG is used, but version is not 2");
208 return false;
210 slotsCount = 2;
211 break;
213 default:
214 MOZ_ASSERT_UNREACHABLE("An unknown operation code is used");
215 return false;
218 // The unwind code is ill-formed if using the correct number of slots for
219 // the opcode would go out of bounds.
220 if (count_of_codes - aIndex < slotsCount) {
221 MOZ_ASSERT_UNREACHABLE(
222 "A valid operation code is used, but it spans too many slots");
223 return false;
226 *aSlotsCount = slotsCount;
227 return true;
231 class IterableUnwindInfo {
232 class Iterator {
233 public:
234 UnwindInfo& Info() { return mInfo; }
236 uint8_t Index() const {
237 MOZ_ASSERT(IsValid());
238 return mIndex;
241 uint8_t SlotsCount() const {
242 MOZ_ASSERT(IsValid());
243 return mSlotsCount;
246 // An iterator is valid if it points to a well-formed unwind code.
247 // The end iterator is invalid as it does not point to any unwind code.
248 // All invalid iterators compare equal, which allows comparison with the
249 // end iterator to exit loops as soon as an ill-formed unwind code is met.
250 bool IsValid() const { return mIsValid; }
252 bool IsAtEnd() const { return mIndex >= mInfo.count_of_codes; }
254 bool operator==(const Iterator& aOther) const {
255 if (mIsValid != aOther.mIsValid) {
256 return false;
258 // Comparing two invalid iterators.
259 if (!mIsValid) {
260 return true;
262 // Comparing two valid iterators.
263 return mIndex == aOther.mIndex;
266 bool operator!=(const Iterator& aOther) const { return !(*this == aOther); }
268 Iterator& operator++() {
269 MOZ_ASSERT(IsValid());
270 mIndex += mSlotsCount;
271 if (mIndex < mInfo.count_of_codes) {
272 mIsValid = mInfo.GetSlotsCountForCodeAt(mIndex, &mSlotsCount);
273 MOZ_ASSERT(IsValid());
274 } else {
275 mIsValid = false;
277 return *this;
280 const UnwindCode& operator*() {
281 MOZ_ASSERT(IsValid());
282 return mInfo.unwind_code[mIndex];
285 private:
286 friend class IterableUnwindInfo;
288 Iterator(UnwindInfo& aInfo, uint8_t aIndex, uint8_t aSlotsCount,
289 bool aIsValid)
290 : mInfo(aInfo),
291 mIndex(aIndex),
292 mSlotsCount(aSlotsCount),
293 mIsValid(aIsValid){};
295 UnwindInfo& mInfo;
296 uint8_t mIndex;
297 uint8_t mSlotsCount;
298 bool mIsValid;
301 public:
302 explicit IterableUnwindInfo(UnwindInfo& aInfo)
303 : mBegin(aInfo, 0, 0, false),
304 mEnd(aInfo, aInfo.count_of_codes, 0, false) {
305 if (aInfo.count_of_codes) {
306 mBegin.mIsValid = aInfo.GetSlotsCountForCodeAt(0, &mBegin.mSlotsCount);
307 MOZ_ASSERT(mBegin.mIsValid);
311 explicit IterableUnwindInfo(uint8_t* aInfo)
312 : IterableUnwindInfo(*reinterpret_cast<UnwindInfo*>(aInfo)) {}
314 UnwindInfo& Info() { return mBegin.Info(); }
316 const Iterator& begin() { return mBegin; }
318 const Iterator& end() { return mEnd; }
320 private:
321 Iterator mBegin;
322 Iterator mEnd;
325 } // namespace mozilla
327 #endif // _M_X64
329 #endif // mozilla_WindowsUnwindInfo_h