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