tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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