summaryrefslogtreecommitdiffstats
path: root/mozglue/misc/WindowsUnwindInfo.h
blob: 3667b5f2bc800afb05e87d689c222fbc20c7a1a7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
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