293 lines
9.8 KiB
C++
293 lines
9.8 KiB
C++
/* -*- 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 http://mozilla.org/MPL/2.0/. */
|
|
|
|
#ifndef mozilla_WindowsDiagnostics_h
|
|
#define mozilla_WindowsDiagnostics_h
|
|
|
|
#include "mozilla/Assertions.h"
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/Types.h"
|
|
|
|
// Bug 1898761: NativeNt.h depends on headers that live outside mozglue/misc/
|
|
// and are not available in SpiderMonkey builds. Until we fix this, we cannot
|
|
// depend on NativeNt.h in mozglue/misc/ cpp files.
|
|
#if !defined(IMPL_MFBT)
|
|
# include "mozilla/NativeNt.h"
|
|
#endif // !IMPL_MFBT
|
|
|
|
#include <windows.h>
|
|
#include <winternl.h>
|
|
|
|
#include <functional>
|
|
|
|
namespace mozilla {
|
|
|
|
enum class WindowsDiagnosticsError : uint32_t {
|
|
None,
|
|
InternalFailure,
|
|
DebuggerPresent,
|
|
ModuleNotFound,
|
|
BadModule,
|
|
};
|
|
|
|
// Struct storing the visible, loggable error-state of a Windows thread.
|
|
// Approximately `std::pair(::GetLastError(), ::RtlGetLastNtStatus())`.
|
|
//
|
|
// Uses sentinel values rather than a proper `Maybe` type to simplify
|
|
// minidump-analysis.
|
|
struct WinErrorState {
|
|
// Last error, as provided by ::GetLastError().
|
|
DWORD error = ~0;
|
|
// Last NTSTATUS, as provided by the TIB.
|
|
NTSTATUS ntStatus = ~0;
|
|
|
|
private:
|
|
// per WINE et al.; stable since NT 3.51
|
|
constexpr static size_t kLastNtStatusOffset =
|
|
sizeof(size_t) == 8 ? 0x1250 : 0xbf4;
|
|
|
|
static void SetLastNtStatus(NTSTATUS status) {
|
|
auto* teb = ::NtCurrentTeb();
|
|
*reinterpret_cast<NTSTATUS*>(reinterpret_cast<char*>(teb) +
|
|
kLastNtStatusOffset) = status;
|
|
}
|
|
|
|
static NTSTATUS GetLastNtStatus() {
|
|
auto const* teb = ::NtCurrentTeb();
|
|
return *reinterpret_cast<NTSTATUS const*>(
|
|
reinterpret_cast<char const*>(teb) + kLastNtStatusOffset);
|
|
}
|
|
|
|
public:
|
|
// Restore (or just set) the error state of the current thread.
|
|
static void Apply(WinErrorState const& state) {
|
|
SetLastNtStatus(state.ntStatus);
|
|
::SetLastError(state.error);
|
|
}
|
|
|
|
// Clear the error-state of the current thread.
|
|
static void Clear() { Apply({.error = 0, .ntStatus = 0}); }
|
|
|
|
// Get the error-state of the current thread.
|
|
static WinErrorState Get() {
|
|
return WinErrorState{
|
|
.error = ::GetLastError(),
|
|
.ntStatus = GetLastNtStatus(),
|
|
};
|
|
}
|
|
|
|
bool operator==(WinErrorState const& that) const {
|
|
return this->error == that.error && this->ntStatus == that.ntStatus;
|
|
}
|
|
|
|
bool operator!=(WinErrorState const& that) const { return !operator==(that); }
|
|
};
|
|
|
|
#if defined(_M_AMD64)
|
|
|
|
using OnSingleStepCallback = std::function<bool(void*, CONTEXT*)>;
|
|
|
|
class MOZ_RAII AutoOnSingleStepCallback {
|
|
public:
|
|
MFBT_API AutoOnSingleStepCallback(OnSingleStepCallback aOnSingleStepCallback,
|
|
void* aState);
|
|
MFBT_API ~AutoOnSingleStepCallback();
|
|
|
|
AutoOnSingleStepCallback(const AutoOnSingleStepCallback&) = delete;
|
|
AutoOnSingleStepCallback(AutoOnSingleStepCallback&&) = delete;
|
|
AutoOnSingleStepCallback& operator=(const AutoOnSingleStepCallback&) = delete;
|
|
AutoOnSingleStepCallback& operator=(AutoOnSingleStepCallback&&) = delete;
|
|
};
|
|
|
|
MFBT_API MOZ_NEVER_INLINE __attribute__((naked)) void EnableTrapFlag();
|
|
MFBT_API MOZ_NEVER_INLINE __attribute__((naked)) void DisableTrapFlag();
|
|
MFBT_API LONG SingleStepExceptionHandler(_EXCEPTION_POINTERS* aExceptionInfo);
|
|
|
|
// Run aCallbackToRun instruction by instruction, and between each instruction
|
|
// call aOnSingleStepCallback. Single-stepping ends when aOnSingleStepCallback
|
|
// returns false (in which case aCallbackToRun will continue to run
|
|
// unmonitored), or when we reach the end of aCallbackToRun.
|
|
template <typename CallbackToRun>
|
|
[[clang::optnone]] MOZ_NEVER_INLINE WindowsDiagnosticsError
|
|
CollectSingleStepData(CallbackToRun aCallbackToRun,
|
|
OnSingleStepCallback aOnSingleStepCallback,
|
|
void* aOnSingleStepCallbackState) {
|
|
if (::IsDebuggerPresent()) {
|
|
return WindowsDiagnosticsError::DebuggerPresent;
|
|
}
|
|
|
|
AutoOnSingleStepCallback setCallback(std::move(aOnSingleStepCallback),
|
|
aOnSingleStepCallbackState);
|
|
|
|
auto veh = ::AddVectoredExceptionHandler(TRUE, SingleStepExceptionHandler);
|
|
if (!veh) {
|
|
return WindowsDiagnosticsError::InternalFailure;
|
|
}
|
|
|
|
EnableTrapFlag();
|
|
aCallbackToRun();
|
|
DisableTrapFlag();
|
|
::RemoveVectoredExceptionHandler(veh);
|
|
|
|
return WindowsDiagnosticsError::None;
|
|
}
|
|
|
|
// This block uses nt::PEHeaders and thus depends on NativeNt.h.
|
|
# if !defined(IMPL_MFBT)
|
|
|
|
template <int NMaxSteps, int NMaxErrorStates>
|
|
struct ModuleSingleStepData {
|
|
uint32_t mStepsLog[NMaxSteps]{};
|
|
WinErrorState mErrorStatesLog[NMaxErrorStates]{};
|
|
uint16_t mStepsAtErrorState[NMaxErrorStates]{};
|
|
};
|
|
|
|
template <int NMaxSteps, int NMaxErrorStates>
|
|
struct ModuleSingleStepState {
|
|
uintptr_t mModuleStart;
|
|
uintptr_t mModuleEnd;
|
|
uint32_t mSteps;
|
|
uint32_t mErrorStates;
|
|
WinErrorState mLastRecordedErrorState;
|
|
ModuleSingleStepData<NMaxSteps, NMaxErrorStates> mData;
|
|
|
|
ModuleSingleStepState(uintptr_t aModuleStart, uintptr_t aModuleEnd)
|
|
: mModuleStart{aModuleStart},
|
|
mModuleEnd{aModuleEnd},
|
|
mSteps{},
|
|
mErrorStates{},
|
|
mLastRecordedErrorState{},
|
|
mData{} {}
|
|
};
|
|
|
|
namespace InstructionFilter {
|
|
|
|
// These functions return true if the instruction behind aInstructionPointer
|
|
// should be recorded during single-stepping.
|
|
|
|
inline bool All(const uint8_t* aInstructionPointer) { return true; }
|
|
|
|
// Note: This filter does *not* currently identify all call/ret instructions.
|
|
// For example, prefixed instructions are not recognized.
|
|
inline bool CallRet(const uint8_t* aInstructionPointer) {
|
|
auto firstByte = aInstructionPointer[0];
|
|
// E8: CALL rel. Call near, relative.
|
|
if (firstByte == 0xe8) {
|
|
return true;
|
|
}
|
|
// FF /2: CALL r. Call near, absolute indirect.
|
|
else if (firstByte == 0xff) {
|
|
auto secondByte = aInstructionPointer[1];
|
|
if ((secondByte & 0x38) == 0x10) {
|
|
return true;
|
|
}
|
|
}
|
|
// C3: RET. Near return.
|
|
else if (firstByte == 0xc3) {
|
|
return true;
|
|
}
|
|
// C2: RET imm. Near return and pop imm bytes.
|
|
else if (firstByte == 0xc2) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
} // namespace InstructionFilter
|
|
|
|
// This function runs aCallbackToRun instruction by instruction, recording
|
|
// information about the paths taken within a specific module given by
|
|
// aModulePath. It then calls aPostCollectionCallback with the collected data.
|
|
//
|
|
// We store the collected data in stack, so that it is available in crash
|
|
// reports in case we decide to crash from aPostCollectionCallback. Remember to
|
|
// carefully estimate the stack usage when choosing NMaxSteps and
|
|
// NMaxErrorStates. Consider using an InstructionFilter if you need to reduce
|
|
// the number of steps that get recorded.
|
|
//
|
|
// This function is typically useful on known-to-crash paths, where we can
|
|
// replace the crash by a new single-stepped attempt at doing the operation
|
|
// that just failed. If the operation fails while single-stepped, we'll be able
|
|
// to produce a crash report that contains single step data, which may prove
|
|
// useful to understand why the operation failed.
|
|
template <
|
|
int NMaxSteps, int NMaxErrorStates, typename CallbackToRun,
|
|
typename PostCollectionCallback,
|
|
typename InstructionFilterCallback = decltype(&InstructionFilter::All)>
|
|
WindowsDiagnosticsError CollectModuleSingleStepData(
|
|
const wchar_t* aModulePath, CallbackToRun aCallbackToRun,
|
|
PostCollectionCallback aPostCollectionCallback,
|
|
InstructionFilterCallback aInstructionFilter = InstructionFilter::All) {
|
|
HANDLE mod = ::GetModuleHandleW(aModulePath);
|
|
if (!mod) {
|
|
return WindowsDiagnosticsError::ModuleNotFound;
|
|
}
|
|
|
|
nt::PEHeaders headers{mod};
|
|
auto maybeBounds = headers.GetBounds();
|
|
if (maybeBounds.isNothing()) {
|
|
return WindowsDiagnosticsError::BadModule;
|
|
}
|
|
|
|
auto& bounds = maybeBounds.ref();
|
|
using State = ModuleSingleStepState<NMaxSteps, NMaxErrorStates>;
|
|
State state{reinterpret_cast<uintptr_t>(bounds.begin().get()),
|
|
reinterpret_cast<uintptr_t>(bounds.end().get())};
|
|
|
|
WindowsDiagnosticsError rv = CollectSingleStepData(
|
|
std::move(aCallbackToRun),
|
|
[&aInstructionFilter](void* aState, CONTEXT* aContextRecord) -> bool {
|
|
auto& state = *reinterpret_cast<State*>(aState);
|
|
auto instructionPointer = aContextRecord->Rip;
|
|
// Record data for the current step, if in module
|
|
if (state.mModuleStart <= instructionPointer &&
|
|
instructionPointer < state.mModuleEnd &&
|
|
aInstructionFilter(
|
|
reinterpret_cast<const uint8_t*>(instructionPointer))) {
|
|
// We record the instruction pointer
|
|
if (state.mSteps < NMaxSteps) {
|
|
state.mData.mStepsLog[state.mSteps] =
|
|
static_cast<uint32_t>(instructionPointer - state.mModuleStart);
|
|
}
|
|
|
|
// We record changes in the error state
|
|
auto currentErrorState{WinErrorState::Get()};
|
|
if (currentErrorState != state.mLastRecordedErrorState) {
|
|
state.mLastRecordedErrorState = currentErrorState;
|
|
|
|
if (state.mErrorStates < NMaxErrorStates) {
|
|
state.mData.mErrorStatesLog[state.mErrorStates] =
|
|
currentErrorState;
|
|
state.mData.mStepsAtErrorState[state.mErrorStates] = state.mSteps;
|
|
}
|
|
|
|
++state.mErrorStates;
|
|
}
|
|
|
|
++state.mSteps;
|
|
}
|
|
|
|
// Continue single-stepping
|
|
return true;
|
|
},
|
|
reinterpret_cast<void*>(&state));
|
|
|
|
if (rv != WindowsDiagnosticsError::None) {
|
|
return rv;
|
|
}
|
|
|
|
aPostCollectionCallback(state.mData);
|
|
|
|
return WindowsDiagnosticsError::None;
|
|
}
|
|
|
|
# endif // !IMPL_MFBT
|
|
|
|
#endif // _M_AMD64
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif // mozilla_WindowsDiagnostics_h
|