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