blob: eb317a8c78cb365eb5d9e2ea55e4e68c64b1ca34 [file] [log] [blame]
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_FRAME_CRASH_REPORTING_VECTORED_HANDLER_IMPL_H_
#define CHROME_FRAME_CRASH_REPORTING_VECTORED_HANDLER_IMPL_H_
#include "base/logging.h"
#include "chrome_frame/crash_reporting/vectored_handler.h"
#include "chrome_frame/crash_reporting/nt_loader.h"
#if !defined(_M_IX86)
#error only x86 is supported for now.
#endif
#ifndef EXCEPTION_CHAIN_END
#define EXCEPTION_CHAIN_END ((struct _EXCEPTION_REGISTRATION_RECORD*)-1)
#if !defined(_WIN32_WINNT_WIN8)
typedef struct _EXCEPTION_REGISTRATION_RECORD {
struct _EXCEPTION_REGISTRATION_RECORD* Next;
PVOID Handler;
} EXCEPTION_REGISTRATION_RECORD;
// VEH handler flags settings.
// These are grabbed from winnt.h for PocketPC.
// Only EXCEPTION_NONCONTINUABLE in defined in "regular" winnt.h
// #define EXCEPTION_NONCONTINUABLE 0x1 // Noncontinuable exception
#define EXCEPTION_UNWINDING 0x2 // Unwind is in progress
#define EXCEPTION_EXIT_UNWIND 0x4 // Exit unwind is in progress
#define EXCEPTION_STACK_INVALID 0x8 // Stack out of limits or unaligned
#define EXCEPTION_NESTED_CALL 0x10 // Nested exception handler call
#define EXCEPTION_TARGET_UNWIND 0x20 // Target unwind in progress
#define EXCEPTION_COLLIDED_UNWIND 0x40 // Collided exception handler call
#define EXCEPTION_UNWIND (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND | \
EXCEPTION_TARGET_UNWIND | EXCEPTION_COLLIDED_UNWIND)
#define IS_UNWINDING(Flag) (((Flag) & EXCEPTION_UNWIND) != 0)
#define IS_DISPATCHING(Flag) (((Flag) & EXCEPTION_UNWIND) == 0)
#define IS_TARGET_UNWIND(Flag) ((Flag) & EXCEPTION_TARGET_UNWIND)
#endif // !defined(_WIN32_WINNT_WIN8)
#endif // EXCEPTION_CHAIN_END
template <typename E>
VectoredHandlerT<E>::VectoredHandlerT(E* api) : exceptions_seen_(0), api_(api) {
}
template <typename E>
VectoredHandlerT<E>::~VectoredHandlerT() {
}
template <typename E>
LONG VectoredHandlerT<E>::Handler(EXCEPTION_POINTERS* exceptionInfo) {
// TODO(stoyan): Consider reentrancy
const DWORD exceptionCode = exceptionInfo->ExceptionRecord->ExceptionCode;
// Not interested in non-error exceptions. In this category falls exceptions
// like:
// 0x40010006 - OutputDebugStringA. Seen when no debugger is attached
// (otherwise debugger swallows the exception and prints
// the string).
// 0x406D1388 - DebuggerProbe. Used by debug CRT - for example see source
// code of isatty(). Used to name a thread as well.
// RPC_E_DISCONNECTED and Co. - COM IPC non-fatal warnings
// STATUS_BREAKPOINT and Co. - Debugger related breakpoints
if ((exceptionCode & ERROR_SEVERITY_ERROR) != ERROR_SEVERITY_ERROR) {
return ExceptionContinueSearch;
}
// Ignore custom exception codes.
// MSXML likes to raise 0xE0000001 while parsing.
// Note the C++ SEH (0xE06D7363) also fails in that range.
if (exceptionCode & APPLICATION_ERROR_MASK) {
return ExceptionContinueSearch;
}
exceptions_seen_++;
// If the exception code is STATUS_STACK_OVERFLOW then proceed as usual -
// we want to report it. Otherwise check whether guard page in stack is gone -
// i.e. stack overflow has been already observed and most probably we are
// seeing the follow-up STATUS_ACCESS_VIOLATION(s). See bug 32441.
if (exceptionCode != STATUS_STACK_OVERFLOW && api_->CheckForStackOverflow()) {
return ExceptionContinueSearch;
}
// Check whether exception will be handled by the module that cuased it.
// This should automatically handle the case of IsBadReadPtr and family.
const EXCEPTION_REGISTRATION_RECORD* seh = api_->RtlpGetExceptionList();
if (api_->ShouldIgnoreException(exceptionInfo, seh)) {
return ExceptionContinueSearch;
}
const DWORD exceptionFlags = exceptionInfo->ExceptionRecord->ExceptionFlags;
// I don't think VEH is called on unwind. Just to be safe.
if (IS_DISPATCHING(exceptionFlags)) {
if (ModuleHasInstalledSEHFilter())
return ExceptionContinueSearch;
if (api_->IsOurModule(exceptionInfo->ExceptionRecord->ExceptionAddress)) {
api_->WriteDump(exceptionInfo);
return ExceptionContinueSearch;
}
// See whether our module is somewhere in the call stack.
void* back_trace[E::max_back_trace] = {0};
DWORD captured = api_->RtlCaptureStackBackTrace(0, api_->max_back_trace,
&back_trace[0], NULL);
for (DWORD i = 0; i < captured; ++i) {
if (api_->IsOurModule(back_trace[i])) {
api_->WriteDump(exceptionInfo);
return ExceptionContinueSearch;
}
}
}
return ExceptionContinueSearch;
}
template <typename E>
bool VectoredHandlerT<E>::ModuleHasInstalledSEHFilter() {
const EXCEPTION_REGISTRATION_RECORD* RegistrationFrame =
api_->RtlpGetExceptionList();
// TODO(stoyan): Add the stack limits check and some sanity checks like
// decreasing addresses of registration records
while (RegistrationFrame != EXCEPTION_CHAIN_END) {
if (api_->IsOurModule(RegistrationFrame->Handler)) {
return true;
}
RegistrationFrame = RegistrationFrame->Next;
}
return false;
}
// Here comes the default Windows 32-bit implementation.
namespace {
__declspec(naked)
static EXCEPTION_REGISTRATION_RECORD* InternalRtlpGetExceptionList() {
__asm {
mov eax, fs:0
ret
}
}
__declspec(naked)
static char* GetStackTopLimit() {
__asm {
mov eax, fs:8
ret
}
}
} // end of namespace
// Class which methods simply forwards to Win32 API.
// Used as template (external interface) of VectoredHandlerT<E>.
class Win32VEHTraits {
public:
enum {max_back_trace = 62};
static inline
EXCEPTION_REGISTRATION_RECORD* RtlpGetExceptionList() {
return InternalRtlpGetExceptionList();
}
static inline WORD RtlCaptureStackBackTrace(DWORD FramesToSkip,
DWORD FramesToCapture, void** BackTrace, DWORD* BackTraceHash) {
return ::RtlCaptureStackBackTrace(FramesToSkip, FramesToCapture,
BackTrace, BackTraceHash);
}
static bool ShouldIgnoreException(const EXCEPTION_POINTERS* exceptionInfo,
const EXCEPTION_REGISTRATION_RECORD* seh_record) {
const void* address = exceptionInfo->ExceptionRecord->ExceptionAddress;
const DWORD flags = GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT |
GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS;
HMODULE crashing_module = GetModuleHandleFromAddress(address);
if (!crashing_module)
return false;
HMODULE top_seh_module = NULL;
if (EXCEPTION_CHAIN_END != seh_record)
top_seh_module = GetModuleHandleFromAddress(seh_record->Handler);
// check if the crash address belongs in a module that's expecting
// and handling it. This should address cases like kernel32!IsBadXXX
// and urlmon!IsValidInterface
if (crashing_module == top_seh_module)
return true;
// We don't want to report exceptions that occur during DLL loading,
// as those are captured and ignored by the NT loader. If this thread
// is holding the loader's lock, there's a possiblity that the crash
// is occurring in a loading DLL, in which case we resolve the
// crash address to a module and check on the module's status.
if (nt_loader::OwnsLoaderLock()) {
nt_loader::LDR_DATA_TABLE_ENTRY* entry =
nt_loader::GetLoaderEntry(crashing_module);
// If:
// 1. we found the entry in question, and
// 2. the entry is a DLL (has the IMAGE_DLL flag set), and
// 3. the DLL has a non-null entrypoint, and
// 4. the loader has not tagged it with the process attached called flag
// we conclude that the crash is most likely during the loading of the
// module and bail on reporting the crash to avoid false positives
// during crashes that occur during module loads, such as e.g. when
// the hook manager attempts to load buggy window hook DLLs.
if (entry &&
(entry->Flags & LDRP_IMAGE_DLL) != 0 &&
(entry->EntryPoint != NULL) &&
(entry->Flags & LDRP_PROCESS_ATTACH_CALLED) == 0)
return true;
}
return false;
}
static bool CheckForStackOverflow() {
MEMORY_BASIC_INFORMATION mi = {0};
const DWORD kPageSize = 0x1000;
void* stack_top = GetStackTopLimit() - kPageSize;
::VirtualQuery(stack_top, &mi, sizeof(mi));
// The above call may result in moving the top of the stack.
// Check once more.
void* stack_top2 = GetStackTopLimit() - kPageSize;
if (stack_top2 != stack_top)
::VirtualQuery(stack_top2, &mi, sizeof(mi));
return !(mi.Protect & PAGE_GUARD);
}
private:
static inline const void* CodeOffset(const void* code, int offset) {
return reinterpret_cast<const char*>(code) + offset;
}
static HMODULE GetModuleHandleFromAddress(const void* p) {
HMODULE module_at_address = NULL;
MEMORY_BASIC_INFORMATION mi = {0};
if (::VirtualQuery(p, &mi, sizeof(mi)) && (mi.Type & MEM_IMAGE)) {
module_at_address = reinterpret_cast<HMODULE>(mi.AllocationBase);
}
return module_at_address;
}
};
// Use Win32 API; checks for single (current) module. Will call a specified
// CrashHandlerTraits::DumpHandler when taking a dump.
class CrashHandlerTraits : public Win32VEHTraits,
public ModuleOfInterestWithExcludedRegion {
public:
typedef bool (*DumpHandler)(EXCEPTION_POINTERS* p);
CrashHandlerTraits() : dump_handler_(NULL) {}
// Note that breakpad_lock must be held when this is called.
void Init(const void* veh_segment_start, const void* veh_segment_end,
DumpHandler dump_handler) {
DCHECK(dump_handler);
dump_handler_ = dump_handler;
ModuleOfInterestWithExcludedRegion::SetCurrentModule();
// Pointers to static (non-extern) functions take the address of the
// function's first byte, as opposed to an entry in the compiler generated
// JMP table. In release builds /OPT:REF wipes away the JMP table, but debug
// builds are not so lucky.
ModuleOfInterestWithExcludedRegion::SetExcludedRegion(veh_segment_start,
veh_segment_end);
}
void Shutdown() {
}
inline bool WriteDump(EXCEPTION_POINTERS* p) {
if (dump_handler_) {
return dump_handler_(p);
} else {
return false;
}
}
private:
DumpHandler dump_handler_;
};
#endif // CHROME_FRAME_CRASH_REPORTING_VECTORED_HANDLER_IMPL_H_