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"
28 enum class WindowsDiagnosticsError
: uint32_t {
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
41 struct WinErrorState
{
42 // Last error, as provided by ::GetLastError().
44 // Last NTSTATUS, as provided by the TIB.
45 NTSTATUS ntStatus
= ~0;
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
);
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() {
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
); }
91 using OnSingleStepCallback
= std::function
<bool(void*, CONTEXT
*)>;
93 class MOZ_RAII AutoOnSingleStepCallback
{
95 MFBT_API
AutoOnSingleStepCallback(OnSingleStepCallback aOnSingleStepCallback
,
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
);
127 return WindowsDiagnosticsError::InternalFailure
;
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
;
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
},
162 mLastRecordedErrorState
{},
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) {
181 // FF /2: CALL r. Call near, absolute indirect.
182 else if (firstByte
== 0xff) {
183 auto secondByte
= aInstructionPointer
[1];
184 if ((secondByte
& 0x38) == 0x10) {
188 // C3: RET. Near return.
189 else if (firstByte
== 0xc3) {
192 // C2: RET imm. Near return and pop imm bytes.
193 else if (firstByte
== 0xc2) {
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.
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
);
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
&&
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
] =
264 state
.mData
.mStepsAtErrorState
[state
.mErrorStates
] = state
.mSteps
;
267 ++state
.mErrorStates
;
273 // Continue single-stepping
276 reinterpret_cast<void*>(&state
));
278 if (rv
!= WindowsDiagnosticsError::None
) {
282 aPostCollectionCallback(state
.mData
);
284 return WindowsDiagnosticsError::None
;
287 # endif // !IMPL_MFBT
291 } // namespace mozilla
293 #endif // mozilla_WindowsDiagnostics_h