Home | History | Annotate | Download | only in crash_reporting
      1 // Copyright (c) 2009 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #ifndef CHROME_FRAME_CRASH_REPORTING_VECTORED_HANDLER_IMPL_H_
      6 #define CHROME_FRAME_CRASH_REPORTING_VECTORED_HANDLER_IMPL_H_
      7 
      8 #include "base/logging.h"
      9 #include "chrome_frame/crash_reporting/vectored_handler.h"
     10 #include "chrome_frame/crash_reporting/nt_loader.h"
     11 
     12 #if !defined(_M_IX86)
     13 #error only x86 is supported for now.
     14 #endif
     15 
     16 #ifndef EXCEPTION_CHAIN_END
     17 #define EXCEPTION_CHAIN_END ((struct _EXCEPTION_REGISTRATION_RECORD*)-1)
     18 #if !defined(_WIN32_WINNT_WIN8)
     19 typedef struct _EXCEPTION_REGISTRATION_RECORD {
     20   struct _EXCEPTION_REGISTRATION_RECORD* Next;
     21   PVOID Handler;
     22 } EXCEPTION_REGISTRATION_RECORD;
     23 // VEH handler flags settings.
     24 // These are grabbed from winnt.h for PocketPC.
     25 // Only EXCEPTION_NONCONTINUABLE in defined in "regular" winnt.h
     26 // #define EXCEPTION_NONCONTINUABLE 0x1    // Noncontinuable exception
     27 #define EXCEPTION_UNWINDING 0x2         // Unwind is in progress
     28 #define EXCEPTION_EXIT_UNWIND 0x4       // Exit unwind is in progress
     29 #define EXCEPTION_STACK_INVALID 0x8     // Stack out of limits or unaligned
     30 #define EXCEPTION_NESTED_CALL 0x10      // Nested exception handler call
     31 #define EXCEPTION_TARGET_UNWIND 0x20    // Target unwind in progress
     32 #define EXCEPTION_COLLIDED_UNWIND 0x40  // Collided exception handler call
     33 
     34 #define EXCEPTION_UNWIND (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND | \
     35     EXCEPTION_TARGET_UNWIND | EXCEPTION_COLLIDED_UNWIND)
     36 
     37 #define IS_UNWINDING(Flag)  (((Flag) & EXCEPTION_UNWIND) != 0)
     38 #define IS_DISPATCHING(Flag)  (((Flag) & EXCEPTION_UNWIND) == 0)
     39 #define IS_TARGET_UNWIND(Flag)  ((Flag) & EXCEPTION_TARGET_UNWIND)
     40 #endif  // !defined(_WIN32_WINNT_WIN8)
     41 #endif  // EXCEPTION_CHAIN_END
     42 
     43 template <typename E>
     44 VectoredHandlerT<E>::VectoredHandlerT(E* api) : exceptions_seen_(0), api_(api) {
     45 }
     46 
     47 template <typename E>
     48 VectoredHandlerT<E>::~VectoredHandlerT() {
     49 }
     50 
     51 template <typename E>
     52 LONG VectoredHandlerT<E>::Handler(EXCEPTION_POINTERS* exceptionInfo) {
     53   // TODO(stoyan): Consider reentrancy
     54   const DWORD exceptionCode = exceptionInfo->ExceptionRecord->ExceptionCode;
     55 
     56   // Not interested in non-error exceptions. In this category falls exceptions
     57   // like:
     58   // 0x40010006 - OutputDebugStringA. Seen when no debugger is attached
     59   //              (otherwise debugger swallows the exception and prints
     60   //              the string).
     61   // 0x406D1388 - DebuggerProbe. Used by debug CRT - for example see source
     62   //              code of isatty(). Used to name a thread as well.
     63   // RPC_E_DISCONNECTED and Co. - COM IPC non-fatal warnings
     64   // STATUS_BREAKPOINT and Co. - Debugger related breakpoints
     65   if ((exceptionCode & ERROR_SEVERITY_ERROR) != ERROR_SEVERITY_ERROR) {
     66     return ExceptionContinueSearch;
     67   }
     68 
     69   // Ignore custom exception codes.
     70   // MSXML likes to raise 0xE0000001 while parsing.
     71   // Note the C++ SEH (0xE06D7363) also fails in that range.
     72   if (exceptionCode & APPLICATION_ERROR_MASK) {
     73     return ExceptionContinueSearch;
     74   }
     75 
     76   exceptions_seen_++;
     77 
     78   // If the exception code is STATUS_STACK_OVERFLOW then proceed as usual -
     79   // we want to report it. Otherwise check whether guard page in stack is gone -
     80   // i.e. stack overflow has been already observed and most probably we are
     81   // seeing the follow-up STATUS_ACCESS_VIOLATION(s). See bug 32441.
     82   if (exceptionCode != STATUS_STACK_OVERFLOW && api_->CheckForStackOverflow()) {
     83     return ExceptionContinueSearch;
     84   }
     85 
     86   // Check whether exception will be handled by the module that cuased it.
     87   // This should automatically handle the case of IsBadReadPtr and family.
     88   const EXCEPTION_REGISTRATION_RECORD* seh = api_->RtlpGetExceptionList();
     89   if (api_->ShouldIgnoreException(exceptionInfo, seh)) {
     90     return ExceptionContinueSearch;
     91   }
     92 
     93   const DWORD exceptionFlags = exceptionInfo->ExceptionRecord->ExceptionFlags;
     94   // I don't think VEH is called on unwind. Just to be safe.
     95   if (IS_DISPATCHING(exceptionFlags)) {
     96     if (ModuleHasInstalledSEHFilter())
     97       return ExceptionContinueSearch;
     98 
     99     if (api_->IsOurModule(exceptionInfo->ExceptionRecord->ExceptionAddress)) {
    100       api_->WriteDump(exceptionInfo);
    101       return ExceptionContinueSearch;
    102     }
    103 
    104     // See whether our module is somewhere in the call stack.
    105     void* back_trace[E::max_back_trace] = {0};
    106     DWORD captured = api_->RtlCaptureStackBackTrace(0, api_->max_back_trace,
    107                                                     &back_trace[0], NULL);
    108     for (DWORD i = 0; i < captured; ++i) {
    109       if (api_->IsOurModule(back_trace[i])) {
    110         api_->WriteDump(exceptionInfo);
    111         return ExceptionContinueSearch;
    112       }
    113     }
    114   }
    115 
    116   return ExceptionContinueSearch;
    117 }
    118 
    119 template <typename E>
    120 bool VectoredHandlerT<E>::ModuleHasInstalledSEHFilter() {
    121   const EXCEPTION_REGISTRATION_RECORD* RegistrationFrame =
    122       api_->RtlpGetExceptionList();
    123   // TODO(stoyan): Add the stack limits check and some sanity checks like
    124   // decreasing addresses of registration records
    125   while (RegistrationFrame != EXCEPTION_CHAIN_END) {
    126     if (api_->IsOurModule(RegistrationFrame->Handler)) {
    127       return true;
    128     }
    129 
    130     RegistrationFrame = RegistrationFrame->Next;
    131   }
    132 
    133   return false;
    134 }
    135 
    136 
    137 // Here comes the default Windows 32-bit implementation.
    138 namespace {
    139   __declspec(naked)
    140     static EXCEPTION_REGISTRATION_RECORD* InternalRtlpGetExceptionList() {
    141       __asm {
    142         mov eax, fs:0
    143         ret
    144       }
    145   }
    146 
    147   __declspec(naked)
    148   static char* GetStackTopLimit() {
    149     __asm {
    150       mov eax, fs:8
    151       ret
    152     }
    153   }
    154 }  // end of namespace
    155 
    156 // Class which methods simply forwards to Win32 API.
    157 // Used as template (external interface) of VectoredHandlerT<E>.
    158 class Win32VEHTraits {
    159  public:
    160   enum {max_back_trace = 62};
    161 
    162   static inline
    163   EXCEPTION_REGISTRATION_RECORD* RtlpGetExceptionList() {
    164     return InternalRtlpGetExceptionList();
    165   }
    166 
    167   static inline WORD RtlCaptureStackBackTrace(DWORD FramesToSkip,
    168       DWORD FramesToCapture, void** BackTrace, DWORD* BackTraceHash) {
    169     return ::RtlCaptureStackBackTrace(FramesToSkip, FramesToCapture,
    170                                       BackTrace, BackTraceHash);
    171   }
    172 
    173   static bool ShouldIgnoreException(const EXCEPTION_POINTERS* exceptionInfo,
    174       const EXCEPTION_REGISTRATION_RECORD* seh_record) {
    175     const void* address = exceptionInfo->ExceptionRecord->ExceptionAddress;
    176     const DWORD flags = GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT |
    177         GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS;
    178     HMODULE crashing_module = GetModuleHandleFromAddress(address);
    179     if (!crashing_module)
    180       return false;
    181 
    182     HMODULE top_seh_module = NULL;
    183     if (EXCEPTION_CHAIN_END != seh_record)
    184       top_seh_module = GetModuleHandleFromAddress(seh_record->Handler);
    185 
    186     // check if the crash address belongs in a module that's expecting
    187     // and handling it. This should address cases like kernel32!IsBadXXX
    188     // and urlmon!IsValidInterface
    189     if (crashing_module == top_seh_module)
    190       return true;
    191 
    192     // We don't want to report exceptions that occur during DLL loading,
    193     // as those are captured and ignored by the NT loader. If this thread
    194     // is holding the loader's lock, there's a possiblity that the crash
    195     // is occurring in a loading DLL, in which case we resolve the
    196     // crash address to a module and check on the module's status.
    197     if (nt_loader::OwnsLoaderLock()) {
    198       nt_loader::LDR_DATA_TABLE_ENTRY* entry =
    199           nt_loader::GetLoaderEntry(crashing_module);
    200 
    201       // If:
    202       //  1. we found the entry in question, and
    203       //  2. the entry is a DLL (has the IMAGE_DLL flag set), and
    204       //  3. the DLL has a non-null entrypoint, and
    205       //  4. the loader has not tagged it with the process attached called flag
    206       // we conclude that the crash is most likely during the loading of the
    207       // module and bail on reporting the crash to avoid false positives
    208       // during crashes that occur during module loads, such as e.g. when
    209       // the hook manager attempts to load buggy window hook DLLs.
    210       if (entry &&
    211           (entry->Flags & LDRP_IMAGE_DLL) != 0 &&
    212           (entry->EntryPoint != NULL) &&
    213           (entry->Flags & LDRP_PROCESS_ATTACH_CALLED) == 0)
    214         return true;
    215     }
    216 
    217     return false;
    218   }
    219 
    220   static bool CheckForStackOverflow() {
    221     MEMORY_BASIC_INFORMATION mi = {0};
    222     const DWORD kPageSize = 0x1000;
    223     void* stack_top = GetStackTopLimit() - kPageSize;
    224     ::VirtualQuery(stack_top, &mi, sizeof(mi));
    225     // The above call may result in moving the top of the stack.
    226     // Check once more.
    227     void* stack_top2 = GetStackTopLimit() - kPageSize;
    228     if (stack_top2 != stack_top)
    229       ::VirtualQuery(stack_top2, &mi, sizeof(mi));
    230     return !(mi.Protect & PAGE_GUARD);
    231   }
    232 
    233  private:
    234   static inline const void* CodeOffset(const void* code, int offset) {
    235     return reinterpret_cast<const char*>(code) + offset;
    236   }
    237 
    238   static HMODULE GetModuleHandleFromAddress(const void* p) {
    239     HMODULE module_at_address = NULL;
    240     MEMORY_BASIC_INFORMATION mi = {0};
    241     if (::VirtualQuery(p, &mi, sizeof(mi)) && (mi.Type & MEM_IMAGE)) {
    242       module_at_address = reinterpret_cast<HMODULE>(mi.AllocationBase);
    243     }
    244 
    245     return module_at_address;
    246   }
    247 };
    248 
    249 
    250 // Use Win32 API; checks for single (current) module. Will call a specified
    251 // CrashHandlerTraits::DumpHandler when taking a dump.
    252 class CrashHandlerTraits : public Win32VEHTraits,
    253                            public ModuleOfInterestWithExcludedRegion {
    254  public:
    255 
    256   typedef bool (*DumpHandler)(EXCEPTION_POINTERS* p);
    257 
    258   CrashHandlerTraits() : dump_handler_(NULL) {}
    259 
    260   // Note that breakpad_lock must be held when this is called.
    261   void Init(const void* veh_segment_start, const void* veh_segment_end,
    262             DumpHandler dump_handler) {
    263     DCHECK(dump_handler);
    264     dump_handler_ = dump_handler;
    265     ModuleOfInterestWithExcludedRegion::SetCurrentModule();
    266     // Pointers to static (non-extern) functions take the address of the
    267     // function's first byte, as opposed to an entry in the compiler generated
    268     // JMP table. In release builds /OPT:REF wipes away the JMP table, but debug
    269     // builds are not so lucky.
    270     ModuleOfInterestWithExcludedRegion::SetExcludedRegion(veh_segment_start,
    271                                                           veh_segment_end);
    272   }
    273 
    274   void Shutdown() {
    275   }
    276 
    277   inline bool WriteDump(EXCEPTION_POINTERS* p) {
    278     if (dump_handler_) {
    279       return dump_handler_(p);
    280     } else {
    281       return false;
    282     }
    283   }
    284 
    285  private:
    286   DumpHandler dump_handler_;
    287 };
    288 
    289 #endif  // CHROME_FRAME_CRASH_REPORTING_VECTORED_HANDLER_IMPL_H_
    290