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
14 # include "mozilla/Assertions.h"
15 # include "mozilla/UniquePtr.h"
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
48 // UnwindCode.operation_info == 0 or 1,
49 // alloc size in next slot (if 0) or next 2 slots (if 1)
51 // UnwindCode.operation_info == size of allocation / 8 - 1
53 // no UnwindCode.operation_info; register number UnwindInfo.frame_register
54 // receives (rsp + UnwindInfo.frame_offset*16)
56 // UnwindCode.operation_info == register number, offset in next slot
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
62 // Version 2; undocumented
64 // Version 1; undocumented; not meant for x64
65 UWOP_SAVE_XMM_FAR
= 7,
66 // Version 2; undocumented
68 // UnwindCode.operation_info == XMM reg number, offset in next slot
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.
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
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
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 {
138 auto result
= MakeUnique
<uint8_t[]>(s
);
139 std::memcpy(result
.get(), reinterpret_cast<const void*>(this), s
);
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");
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
:
171 case UWOP_SAVE_NONVOL
:
172 case UWOP_SAVE_XMM128
:
176 case UWOP_PUSH_NONVOL
:
177 case UWOP_ALLOC_SMALL
:
179 case UWOP_PUSH_MACHFRAME
:
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 "
192 slotsCount
= 2 + unwindCode
.operation_info
;
197 MOZ_ASSERT_UNREACHABLE(
198 "Operation code UWOP_SPARE is used, but version is not 2");
206 MOZ_ASSERT_UNREACHABLE(
207 "Operation code UWOP_EPILOG is used, but version is not 2");
214 MOZ_ASSERT_UNREACHABLE("An unknown operation code is used");
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");
226 *aSlotsCount
= slotsCount
;
231 class IterableUnwindInfo
{
234 UnwindInfo
& Info() { return mInfo
; }
236 uint8_t Index() const {
237 MOZ_ASSERT(IsValid());
241 uint8_t SlotsCount() const {
242 MOZ_ASSERT(IsValid());
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
) {
258 // Comparing two invalid iterators.
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());
280 const UnwindCode
& operator*() {
281 MOZ_ASSERT(IsValid());
282 return mInfo
.unwind_code
[mIndex
];
286 friend class IterableUnwindInfo
;
288 Iterator(UnwindInfo
& aInfo
, uint8_t aIndex
, uint8_t aSlotsCount
,
292 mSlotsCount(aSlotsCount
),
293 mIsValid(aIsValid
) {};
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
; }
325 } // namespace mozilla
329 #endif // mozilla_WindowsUnwindInfo_h