summaryrefslogtreecommitdiffstats
path: root/mozglue/misc/WindowsUnwindInfo.h
diff options
context:
space:
mode:
Diffstat (limited to 'mozglue/misc/WindowsUnwindInfo.h')
-rw-r--r--mozglue/misc/WindowsUnwindInfo.h329
1 files changed, 329 insertions, 0 deletions
diff --git a/mozglue/misc/WindowsUnwindInfo.h b/mozglue/misc/WindowsUnwindInfo.h
new file mode 100644
index 0000000000..3667b5f2bc
--- /dev/null
+++ b/mozglue/misc/WindowsUnwindInfo.h
@@ -0,0 +1,329 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_WindowsUnwindInfo_h
+#define mozilla_WindowsUnwindInfo_h
+
+#ifdef _M_X64
+
+# include <cstdint>
+
+# include "mozilla/Assertions.h"
+# include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+
+// On Windows x64, there is no standard function prologue, hence extra
+// information that describes the prologue must be added for each non-leaf
+// function in order to properly unwind the stack. This extra information is
+// grouped into so-called function tables.
+//
+// A function table is a contiguous array of one or more RUNTIME_FUNCTION
+// entries. Each RUNTIME_FUNCTION entry associates a start and end offset in
+// code with specific unwind information. The function table is present in the
+// .pdata section of binaries for static code, and added dynamically with
+// RtlAddFunctionTable or RtlInstallFunctionTableCallback for dynamic code.
+// RUNTIME_FUNCTION entries point to the unwind information, which can thus
+// live at a different location in memory, for example it lives in the .xdata
+// section for static code.
+//
+// Contrary to RUNTIME_FUNCTION, Microsoft provides no standard structure
+// definition to map the unwind information. This file thus provides some
+// helpers to read this data, originally based on breakpad code. The unwind
+// information is partially documented at:
+// https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64.
+
+// The unwind information stores a bytecode in UnwindInfo.unwind_code[] that
+// describes how the instructions in the function prologue interact with the
+// stack. An instruction in this bytecode is called an unwind code.
+// UnwindCodeOperationCodes enumerates all opcodes used by this bytecode.
+// Unwind codes are stored in contiguous slots of 16 bits, where each unwind
+// code can span either 1, 2, or 3 slots depending on the opcode it uses.
+enum UnwindOperationCodes : uint8_t {
+ // UnwindCode.operation_info == register number
+ UWOP_PUSH_NONVOL = 0,
+ // UnwindCode.operation_info == 0 or 1,
+ // alloc size in next slot (if 0) or next 2 slots (if 1)
+ UWOP_ALLOC_LARGE = 1,
+ // UnwindCode.operation_info == size of allocation / 8 - 1
+ UWOP_ALLOC_SMALL = 2,
+ // no UnwindCode.operation_info; register number UnwindInfo.frame_register
+ // receives (rsp + UnwindInfo.frame_offset*16)
+ UWOP_SET_FPREG = 3,
+ // UnwindCode.operation_info == register number, offset in next slot
+ UWOP_SAVE_NONVOL = 4,
+ // UnwindCode.operation_info == register number, offset in next 2 slots
+ UWOP_SAVE_NONVOL_FAR = 5,
+ // Version 1; undocumented; not meant for x64
+ UWOP_SAVE_XMM = 6,
+ // Version 2; undocumented
+ UWOP_EPILOG = 6,
+ // Version 1; undocumented; not meant for x64
+ UWOP_SAVE_XMM_FAR = 7,
+ // Version 2; undocumented
+ UWOP_SPARE = 7,
+ // UnwindCode.operation_info == XMM reg number, offset in next slot
+ UWOP_SAVE_XMM128 = 8,
+ // UnwindCode.operation_info == XMM reg number, offset in next 2 slots
+ UWOP_SAVE_XMM128_FAR = 9,
+ // UnwindCode.operation_info == 0: no error-code, 1: error-code
+ UWOP_PUSH_MACHFRAME = 10
+};
+
+// Strictly speaking, UnwindCode represents a slot -- not a full unwind code.
+union UnwindCode {
+ struct {
+ uint8_t offset_in_prolog;
+ UnwindOperationCodes unwind_operation_code : 4;
+ uint8_t operation_info : 4;
+ };
+ uint16_t frame_offset;
+};
+
+// UnwindInfo is a variable-sized struct meant for C-style direct access to the
+// unwind information. Be careful:
+// - prefer using the size() helper method to using sizeof;
+// - don't construct objects of this type, cast pointers instead;
+// - consider using the IterableUnwindInfo helpers to iterate over unwind
+// codes.
+struct UnwindInfo {
+ uint8_t version : 3;
+ uint8_t flags : 5; // either 0, UNW_FLAG_CHAININFO, or a combination of
+ // UNW_FLAG_EHANDLER and UNW_FLAG_UHANDLER
+ uint8_t size_of_prolog;
+ uint8_t count_of_codes; // contains the length of the unwind_code[] array
+ uint8_t frame_register : 4;
+ uint8_t frame_offset : 4;
+ UnwindCode unwind_code[1]; // variable length
+ // Note: There is extra data after the variable length array if using flags
+ // UNW_FLAG_EHANDLER, UNW_FLAG_UHANDLER, or UNW_FLAG_CHAININFO. We
+ // ignore the extra data at the moment. For more details, see:
+ // https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64.
+ //
+ // When using UNW_FLAG_EHANDLER or UNW_FLAG_UHANDLER, the extra data
+ // includes handler data of unspecificied size: only the handler knows
+ // the correct size for this data. This makes it difficult to know the
+ // size of the full unwind information or to copy it in this particular
+ // case.
+
+ UnwindInfo(const UnwindInfo&) = delete;
+ UnwindInfo& operator=(const UnwindInfo&) = delete;
+ UnwindInfo(UnwindInfo&&) = delete;
+ UnwindInfo& operator=(UnwindInfo&&) = delete;
+ ~UnwindInfo() = delete;
+
+ // Size of this structure, including the variable length unwind_code array
+ // but NOT including the extra data related to flags UNW_FLAG_EHANDLER,
+ // UNW_FLAG_UHANDLER, and UNW_FLAG_CHAININFO.
+ //
+ // The places where we currently use these helpers read unwind information at
+ // function entry points; as such we expect that they may encounter
+ // UNW_FLAG_EHANDLER and/or UNW_FLAG_UHANDLER but won't need to use the
+ // associated extra data, and it is expected that they should not encounter
+ // UNW_FLAG_CHAININFO. UNW_FLAG_CHAININFO is typically used for code that
+ // lives separately from the entry point of the function to which it belongs,
+ // this code then has chained unwind info pointing to the entry point.
+ inline size_t Size() const {
+ return offsetof(UnwindInfo, unwind_code) +
+ count_of_codes * sizeof(UnwindCode);
+ }
+
+ // Note: We currently do not copy the extra data related to flags
+ // UNW_FLAG_EHANDLER, UNW_FLAG_UHANDLER, and UNW_FLAG_CHAININFO.
+ UniquePtr<uint8_t[]> Copy() const {
+ auto s = Size();
+ auto result = MakeUnique<uint8_t[]>(s);
+ std::memcpy(result.get(), reinterpret_cast<const void*>(this), s);
+ return result;
+ }
+
+ // An unwind code spans a number of slots in the unwind_code array that can
+ // vary from 1 to 3. This method assumes that the index parameter points to
+ // a slot that is the start of an unwind code. If the unwind code is
+ // well-formed, it returns true and sets its second parameter to the number
+ // of slots that the unwind code occupies.
+ //
+ // This function returns false if the unwind code is ill-formed, i.e.:
+ // - either the index points out of bounds;
+ // - or the opcode is invalid, or unexpected (e.g. UWOP_SAVE_XMM and
+ // UWOP_SAVE_XMM_FAR in version 1);
+ // - or using the correct slots count for the opcode would go out of bounds.
+ bool GetSlotsCountForCodeAt(uint8_t aIndex, uint8_t* aSlotsCount) const {
+ if (aIndex >= count_of_codes) {
+ MOZ_ASSERT_UNREACHABLE("The index is out of bounds");
+ return false;
+ }
+
+ const UnwindCode& unwindCode = unwind_code[aIndex];
+ uint8_t slotsCount = 0;
+
+ // See https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64
+ switch (unwindCode.unwind_operation_code) {
+ // Start with fixed-size opcodes common to versions 1 and 2
+ case UWOP_SAVE_NONVOL_FAR:
+ case UWOP_SAVE_XMM128_FAR:
+ slotsCount = 3;
+ break;
+
+ case UWOP_SAVE_NONVOL:
+ case UWOP_SAVE_XMM128:
+ slotsCount = 2;
+ break;
+
+ case UWOP_PUSH_NONVOL:
+ case UWOP_ALLOC_SMALL:
+ case UWOP_SET_FPREG:
+ case UWOP_PUSH_MACHFRAME:
+ slotsCount = 1;
+ break;
+
+ // UWOP_ALLOC_LARGE is the only variable-sized opcode. It is common to
+ // versions 1 and 2. It is ill-formed if the info is not 0 or 1.
+ case UWOP_ALLOC_LARGE:
+ if (unwindCode.operation_info > 1) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Operation UWOP_ALLOC_LARGE is used, but operation_info "
+ "is not 0 or 1");
+ return false;
+ }
+ slotsCount = 2 + unwindCode.operation_info;
+ break;
+
+ case UWOP_SPARE:
+ if (version != 2) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Operation code UWOP_SPARE is used, but version is not 2");
+ return false;
+ }
+ slotsCount = 3;
+ break;
+
+ case UWOP_EPILOG:
+ if (version != 2) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Operation code UWOP_EPILOG is used, but version is not 2");
+ return false;
+ }
+ slotsCount = 2;
+ break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("An unknown operation code is used");
+ return false;
+ }
+
+ // The unwind code is ill-formed if using the correct number of slots for
+ // the opcode would go out of bounds.
+ if (count_of_codes - aIndex < slotsCount) {
+ MOZ_ASSERT_UNREACHABLE(
+ "A valid operation code is used, but it spans too many slots");
+ return false;
+ }
+
+ *aSlotsCount = slotsCount;
+ return true;
+ }
+};
+
+class IterableUnwindInfo {
+ class Iterator {
+ public:
+ UnwindInfo& Info() { return mInfo; }
+
+ uint8_t Index() const {
+ MOZ_ASSERT(IsValid());
+ return mIndex;
+ }
+
+ uint8_t SlotsCount() const {
+ MOZ_ASSERT(IsValid());
+ return mSlotsCount;
+ }
+
+ // An iterator is valid if it points to a well-formed unwind code.
+ // The end iterator is invalid as it does not point to any unwind code.
+ // All invalid iterators compare equal, which allows comparison with the
+ // end iterator to exit loops as soon as an ill-formed unwind code is met.
+ bool IsValid() const { return mIsValid; }
+
+ bool IsAtEnd() const { return mIndex >= mInfo.count_of_codes; }
+
+ bool operator==(const Iterator& aOther) const {
+ if (mIsValid != aOther.mIsValid) {
+ return false;
+ }
+ // Comparing two invalid iterators.
+ if (!mIsValid) {
+ return true;
+ }
+ // Comparing two valid iterators.
+ return mIndex == aOther.mIndex;
+ }
+
+ bool operator!=(const Iterator& aOther) const { return !(*this == aOther); }
+
+ Iterator& operator++() {
+ MOZ_ASSERT(IsValid());
+ mIndex += mSlotsCount;
+ if (mIndex < mInfo.count_of_codes) {
+ mIsValid = mInfo.GetSlotsCountForCodeAt(mIndex, &mSlotsCount);
+ MOZ_ASSERT(IsValid());
+ } else {
+ mIsValid = false;
+ }
+ return *this;
+ }
+
+ const UnwindCode& operator*() {
+ MOZ_ASSERT(IsValid());
+ return mInfo.unwind_code[mIndex];
+ }
+
+ private:
+ friend class IterableUnwindInfo;
+
+ Iterator(UnwindInfo& aInfo, uint8_t aIndex, uint8_t aSlotsCount,
+ bool aIsValid)
+ : mInfo(aInfo),
+ mIndex(aIndex),
+ mSlotsCount(aSlotsCount),
+ mIsValid(aIsValid){};
+
+ UnwindInfo& mInfo;
+ uint8_t mIndex;
+ uint8_t mSlotsCount;
+ bool mIsValid;
+ };
+
+ public:
+ explicit IterableUnwindInfo(UnwindInfo& aInfo)
+ : mBegin(aInfo, 0, 0, false),
+ mEnd(aInfo, aInfo.count_of_codes, 0, false) {
+ if (aInfo.count_of_codes) {
+ mBegin.mIsValid = aInfo.GetSlotsCountForCodeAt(0, &mBegin.mSlotsCount);
+ MOZ_ASSERT(mBegin.mIsValid);
+ }
+ }
+
+ explicit IterableUnwindInfo(uint8_t* aInfo)
+ : IterableUnwindInfo(*reinterpret_cast<UnwindInfo*>(aInfo)) {}
+
+ UnwindInfo& Info() { return mBegin.Info(); }
+
+ const Iterator& begin() { return mBegin; }
+
+ const Iterator& end() { return mEnd; }
+
+ private:
+ Iterator mBegin;
+ Iterator mEnd;
+};
+
+} // namespace mozilla
+
+#endif // _M_X64
+
+#endif // mozilla_WindowsUnwindInfo_h