tor-browser

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

WindowsDiagnostics.h (9966B)


      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 http://mozilla.org/MPL/2.0/. */
      6 
      7 #ifndef mozilla_WindowsDiagnostics_h
      8 #define mozilla_WindowsDiagnostics_h
      9 
     10 #include "mozilla/Attributes.h"
     11 #include "mozilla/Types.h"
     12 
     13 // Bug 1898761: NativeNt.h depends on headers that live outside mozglue/misc/
     14 // and are not available in SpiderMonkey builds. Until we fix this, we cannot
     15 // depend on NativeNt.h in mozglue/misc/ cpp files.
     16 #if !defined(IMPL_MFBT)
     17 #  include "mozilla/NativeNt.h"
     18 #endif  // !IMPL_MFBT
     19 
     20 #include <windows.h>
     21 #include <winternl.h>
     22 
     23 #include <functional>
     24 
     25 namespace mozilla {
     26 
     27 enum class WindowsDiagnosticsError : uint32_t {
     28  None,
     29  InternalFailure,
     30  DebuggerPresent,
     31  ModuleNotFound,
     32  BadModule,
     33 };
     34 
     35 // Struct storing the visible, loggable error-state of a Windows thread.
     36 // Approximately `std::pair(::GetLastError(), ::RtlGetLastNtStatus())`.
     37 //
     38 // Uses sentinel values rather than a proper `Maybe` type to simplify
     39 // minidump-analysis.
     40 struct WinErrorState {
     41  // Last error, as provided by ::GetLastError().
     42  DWORD error = ~0;
     43  // Last NTSTATUS, as provided by the TIB.
     44  NTSTATUS ntStatus = ~0;
     45 
     46 private:
     47  // per WINE et al.; stable since NT 3.51
     48  constexpr static size_t kLastNtStatusOffset =
     49      sizeof(size_t) == 8 ? 0x1250 : 0xbf4;
     50 
     51  static void SetLastNtStatus(NTSTATUS status) {
     52    auto* teb = ::NtCurrentTeb();
     53    *reinterpret_cast<NTSTATUS*>(reinterpret_cast<char*>(teb) +
     54                                 kLastNtStatusOffset) = status;
     55  }
     56 
     57  static NTSTATUS GetLastNtStatus() {
     58    auto const* teb = ::NtCurrentTeb();
     59    return *reinterpret_cast<NTSTATUS const*>(
     60        reinterpret_cast<char const*>(teb) + kLastNtStatusOffset);
     61  }
     62 
     63 public:
     64  // Restore (or just set) the error state of the current thread.
     65  static void Apply(WinErrorState const& state) {
     66    SetLastNtStatus(state.ntStatus);
     67    ::SetLastError(state.error);
     68  }
     69 
     70  // Clear the error-state of the current thread.
     71  static void Clear() { Apply({.error = 0, .ntStatus = 0}); }
     72 
     73  // Get the error-state of the current thread.
     74  static WinErrorState Get() {
     75    return WinErrorState{
     76        .error = ::GetLastError(),
     77        .ntStatus = GetLastNtStatus(),
     78    };
     79  }
     80 
     81  bool operator==(WinErrorState const& that) const {
     82    return this->error == that.error && this->ntStatus == that.ntStatus;
     83  }
     84 
     85  bool operator!=(WinErrorState const& that) const { return !operator==(that); }
     86 };
     87 
     88 #if defined(_M_AMD64)
     89 
     90 using OnSingleStepCallback = std::function<bool(void*, CONTEXT*)>;
     91 
     92 class MOZ_RAII AutoOnSingleStepCallback {
     93 public:
     94  MFBT_API AutoOnSingleStepCallback(OnSingleStepCallback aOnSingleStepCallback,
     95                                    void* aState);
     96  MFBT_API ~AutoOnSingleStepCallback();
     97 
     98  AutoOnSingleStepCallback(const AutoOnSingleStepCallback&) = delete;
     99  AutoOnSingleStepCallback(AutoOnSingleStepCallback&&) = delete;
    100  AutoOnSingleStepCallback& operator=(const AutoOnSingleStepCallback&) = delete;
    101  AutoOnSingleStepCallback& operator=(AutoOnSingleStepCallback&&) = delete;
    102 };
    103 
    104 MFBT_API MOZ_NEVER_INLINE __attribute__((naked)) void EnableTrapFlag();
    105 MFBT_API MOZ_NEVER_INLINE __attribute__((naked)) void DisableTrapFlag();
    106 MFBT_API LONG SingleStepExceptionHandler(_EXCEPTION_POINTERS* aExceptionInfo);
    107 
    108 // Run aCallbackToRun instruction by instruction, and between each instruction
    109 // call aOnSingleStepCallback. Single-stepping ends when aOnSingleStepCallback
    110 // returns false (in which case aCallbackToRun will continue to run
    111 // unmonitored), or when we reach the end of aCallbackToRun.
    112 template <typename CallbackToRun>
    113 [[clang::optnone]] MOZ_NEVER_INLINE WindowsDiagnosticsError
    114 CollectSingleStepData(CallbackToRun aCallbackToRun,
    115                      OnSingleStepCallback aOnSingleStepCallback,
    116                      void* aOnSingleStepCallbackState) {
    117  if (::IsDebuggerPresent()) {
    118    return WindowsDiagnosticsError::DebuggerPresent;
    119  }
    120 
    121  AutoOnSingleStepCallback setCallback(std::move(aOnSingleStepCallback),
    122                                       aOnSingleStepCallbackState);
    123 
    124  auto veh = ::AddVectoredExceptionHandler(TRUE, SingleStepExceptionHandler);
    125  if (!veh) {
    126    return WindowsDiagnosticsError::InternalFailure;
    127  }
    128 
    129  EnableTrapFlag();
    130  aCallbackToRun();
    131  DisableTrapFlag();
    132  ::RemoveVectoredExceptionHandler(veh);
    133 
    134  return WindowsDiagnosticsError::None;
    135 }
    136 
    137 // This block uses nt::PEHeaders and thus depends on NativeNt.h.
    138 #  if !defined(IMPL_MFBT)
    139 
    140 template <int NMaxSteps, int NMaxErrorStates>
    141 struct ModuleSingleStepData {
    142  uint32_t mStepsLog[NMaxSteps]{};
    143  WinErrorState mErrorStatesLog[NMaxErrorStates]{};
    144  uint16_t mStepsAtErrorState[NMaxErrorStates]{};
    145 };
    146 
    147 template <int NMaxSteps, int NMaxErrorStates>
    148 struct ModuleSingleStepState {
    149  uintptr_t mModuleStart;
    150  uintptr_t mModuleEnd;
    151  uint32_t mSteps;
    152  uint32_t mErrorStates;
    153  WinErrorState mLastRecordedErrorState;
    154  ModuleSingleStepData<NMaxSteps, NMaxErrorStates> mData;
    155 
    156  ModuleSingleStepState(uintptr_t aModuleStart, uintptr_t aModuleEnd)
    157      : mModuleStart{aModuleStart},
    158        mModuleEnd{aModuleEnd},
    159        mSteps{},
    160        mErrorStates{},
    161        mLastRecordedErrorState{},
    162        mData{} {}
    163 };
    164 
    165 namespace InstructionFilter {
    166 
    167 // These functions return true if the instruction behind aInstructionPointer
    168 // should be recorded during single-stepping.
    169 
    170 inline bool All(const uint8_t* aInstructionPointer) { return true; }
    171 
    172 // Note: This filter does *not* currently identify all call/ret instructions.
    173 //       For example, prefixed instructions are not recognized.
    174 inline bool CallRet(const uint8_t* aInstructionPointer) {
    175  auto firstByte = aInstructionPointer[0];
    176  // E8: CALL rel. Call near, relative.
    177  if (firstByte == 0xe8) {
    178    return true;
    179  }
    180  // FF /2: CALL r.	Call near, absolute indirect.
    181  else if (firstByte == 0xff) {
    182    auto secondByte = aInstructionPointer[1];
    183    if ((secondByte & 0x38) == 0x10) {
    184      return true;
    185    }
    186  }
    187  // C3: RET. Near return.
    188  else if (firstByte == 0xc3) {
    189    return true;
    190  }
    191  // C2: RET imm. Near return and pop imm bytes.
    192  else if (firstByte == 0xc2) {
    193    return true;
    194  }
    195  return false;
    196 }
    197 
    198 }  // namespace InstructionFilter
    199 
    200 // This function runs aCallbackToRun instruction by instruction, recording
    201 // information about the paths taken within a specific module given by
    202 // aModulePath. It then calls aPostCollectionCallback with the collected data.
    203 //
    204 // We store the collected data in stack, so that it is available in crash
    205 // reports in case we decide to crash from aPostCollectionCallback. Remember to
    206 // carefully estimate the stack usage when choosing NMaxSteps and
    207 // NMaxErrorStates. Consider using an InstructionFilter if you need to reduce
    208 // the number of steps that get recorded.
    209 //
    210 // This function is typically useful on known-to-crash paths, where we can
    211 // replace the crash by a new single-stepped attempt at doing the operation
    212 // that just failed. If the operation fails while single-stepped, we'll be able
    213 // to produce a crash report that contains single step data, which may prove
    214 // useful to understand why the operation failed.
    215 template <
    216    int NMaxSteps, int NMaxErrorStates, typename CallbackToRun,
    217    typename PostCollectionCallback,
    218    typename InstructionFilterCallback = decltype(&InstructionFilter::All)>
    219 WindowsDiagnosticsError CollectModuleSingleStepData(
    220    const wchar_t* aModulePath, CallbackToRun aCallbackToRun,
    221    PostCollectionCallback aPostCollectionCallback,
    222    InstructionFilterCallback aInstructionFilter = InstructionFilter::All) {
    223  HANDLE mod = ::GetModuleHandleW(aModulePath);
    224  if (!mod) {
    225    return WindowsDiagnosticsError::ModuleNotFound;
    226  }
    227 
    228  nt::PEHeaders headers{mod};
    229  auto maybeBounds = headers.GetBounds();
    230  if (maybeBounds.isNothing()) {
    231    return WindowsDiagnosticsError::BadModule;
    232  }
    233 
    234  auto& bounds = maybeBounds.ref();
    235  using State = ModuleSingleStepState<NMaxSteps, NMaxErrorStates>;
    236  State state{reinterpret_cast<uintptr_t>(bounds.begin().get()),
    237              reinterpret_cast<uintptr_t>(bounds.end().get())};
    238 
    239  WindowsDiagnosticsError rv = CollectSingleStepData(
    240      std::move(aCallbackToRun),
    241      [&aInstructionFilter](void* aState, CONTEXT* aContextRecord) -> bool {
    242        auto& state = *reinterpret_cast<State*>(aState);
    243        auto instructionPointer = aContextRecord->Rip;
    244        // Record data for the current step, if in module
    245        if (state.mModuleStart <= instructionPointer &&
    246            instructionPointer < state.mModuleEnd &&
    247            aInstructionFilter(
    248                reinterpret_cast<const uint8_t*>(instructionPointer))) {
    249          // We record the instruction pointer
    250          if (state.mSteps < NMaxSteps) {
    251            state.mData.mStepsLog[state.mSteps] =
    252                static_cast<uint32_t>(instructionPointer - state.mModuleStart);
    253          }
    254 
    255          // We record changes in the error state
    256          auto currentErrorState{WinErrorState::Get()};
    257          if (currentErrorState != state.mLastRecordedErrorState) {
    258            state.mLastRecordedErrorState = currentErrorState;
    259 
    260            if (state.mErrorStates < NMaxErrorStates) {
    261              state.mData.mErrorStatesLog[state.mErrorStates] =
    262                  currentErrorState;
    263              state.mData.mStepsAtErrorState[state.mErrorStates] = state.mSteps;
    264            }
    265 
    266            ++state.mErrorStates;
    267          }
    268 
    269          ++state.mSteps;
    270        }
    271 
    272        // Continue single-stepping
    273        return true;
    274      },
    275      reinterpret_cast<void*>(&state));
    276 
    277  if (rv != WindowsDiagnosticsError::None) {
    278    return rv;
    279  }
    280 
    281  aPostCollectionCallback(state.mData);
    282 
    283  return WindowsDiagnosticsError::None;
    284 }
    285 
    286 #  endif  // !IMPL_MFBT
    287 
    288 #endif  // _M_AMD64
    289 
    290 }  // namespace mozilla
    291 
    292 #endif  // mozilla_WindowsDiagnostics_h