WindowsUnwindInfo.h (11830B)
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/. */ 6 7 #ifndef mozilla_WindowsUnwindInfo_h 8 #define mozilla_WindowsUnwindInfo_h 9 10 #ifdef _M_X64 11 12 # include <cstdint> 13 14 # include "mozilla/Assertions.h" 15 # include "mozilla/UniquePtr.h" 16 17 namespace mozilla { 18 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. 23 // 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. 32 // 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. 38 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 74 }; 75 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; 82 }; 83 uint16_t frame_offset; 84 }; 85 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. 105 // 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. 111 112 UnwindInfo(const UnwindInfo&) = delete; 113 UnwindInfo& operator=(const UnwindInfo&) = delete; 114 UnwindInfo(UnwindInfo&&) = delete; 115 UnwindInfo& operator=(UnwindInfo&&) = delete; 116 ~UnwindInfo() = delete; 117 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. 121 // 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); 132 } 133 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; 141 } 142 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. 148 // 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; 158 } 159 160 const UnwindCode& unwindCode = unwind_code[aIndex]; 161 uint8_t slotsCount = 0; 162 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; 170 171 case UWOP_SAVE_NONVOL: 172 case UWOP_SAVE_XMM128: 173 slotsCount = 2; 174 break; 175 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; 182 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; 191 } 192 slotsCount = 2 + unwindCode.operation_info; 193 break; 194 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; 200 } 201 slotsCount = 3; 202 break; 203 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; 209 } 210 slotsCount = 2; 211 break; 212 213 default: 214 MOZ_ASSERT_UNREACHABLE("An unknown operation code is used"); 215 return false; 216 } 217 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; 224 } 225 226 *aSlotsCount = slotsCount; 227 return true; 228 } 229 }; 230 231 class IterableUnwindInfo { 232 class Iterator { 233 public: 234 UnwindInfo& Info() { return mInfo; } 235 236 uint8_t Index() const { 237 MOZ_ASSERT(IsValid()); 238 return mIndex; 239 } 240 241 uint8_t SlotsCount() const { 242 MOZ_ASSERT(IsValid()); 243 return mSlotsCount; 244 } 245 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; } 251 252 bool IsAtEnd() const { return mIndex >= mInfo.count_of_codes; } 253 254 bool operator==(const Iterator& aOther) const { 255 if (mIsValid != aOther.mIsValid) { 256 return false; 257 } 258 // Comparing two invalid iterators. 259 if (!mIsValid) { 260 return true; 261 } 262 // Comparing two valid iterators. 263 return mIndex == aOther.mIndex; 264 } 265 266 bool operator!=(const Iterator& aOther) const { return !(*this == aOther); } 267 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; 276 } 277 return *this; 278 } 279 280 const UnwindCode& operator*() { 281 MOZ_ASSERT(IsValid()); 282 return mInfo.unwind_code[mIndex]; 283 } 284 285 private: 286 friend class IterableUnwindInfo; 287 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) {}; 294 295 UnwindInfo& mInfo; 296 uint8_t mIndex; 297 uint8_t mSlotsCount; 298 bool mIsValid; 299 }; 300 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); 308 } 309 } 310 311 explicit IterableUnwindInfo(uint8_t* aInfo) 312 : IterableUnwindInfo(*reinterpret_cast<UnwindInfo*>(aInfo)) {} 313 314 UnwindInfo& Info() { return mBegin.Info(); } 315 316 const Iterator& begin() { return mBegin; } 317 318 const Iterator& end() { return mEnd; } 319 320 private: 321 Iterator mBegin; 322 Iterator mEnd; 323 }; 324 325 } // namespace mozilla 326 327 #endif // _M_X64 328 329 #endif // mozilla_WindowsUnwindInfo_h