diff options
author | stoyan@chromium.org <stoyan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-01-31 22:17:07 +0000 |
---|---|---|
committer | stoyan@chromium.org <stoyan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-01-31 22:17:07 +0000 |
commit | 30f9ac3cd37f6a4833be4ce0aefa969dea9a1808 (patch) | |
tree | af91ba96d5f2170bba3f63fbbe8726dbaf3a2c41 /chrome_frame | |
parent | b20074de864b34a617630fe8cbcb487d9edcca6e (diff) | |
download | chromium_src-30f9ac3cd37f6a4833be4ce0aefa969dea9a1808.zip chromium_src-30f9ac3cd37f6a4833be4ce0aefa969dea9a1808.tar.gz chromium_src-30f9ac3cd37f6a4833be4ce0aefa969dea9a1808.tar.bz2 |
Prevent excessive crash reporting due stack overflow (due exception in SEH filter).
BUG=32441
Review URL: http://codereview.chromium.org/557021
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@37673 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome_frame')
-rw-r--r-- | chrome_frame/crash_reporting/crash_report.cc | 110 | ||||
-rw-r--r-- | chrome_frame/crash_reporting/crash_report.h | 86 | ||||
-rw-r--r-- | chrome_frame/crash_reporting/crash_reporting.gyp | 2 | ||||
-rw-r--r-- | chrome_frame/crash_reporting/vectored_handler-impl.h | 166 | ||||
-rw-r--r-- | chrome_frame/crash_reporting/vectored_handler.h | 142 | ||||
-rw-r--r-- | chrome_frame/crash_reporting/vectored_handler_unittest.cc | 313 | ||||
-rw-r--r-- | chrome_frame/crash_reporting/veh_test.cc | 104 | ||||
-rw-r--r-- | chrome_frame/crash_reporting/veh_test.h | 80 |
8 files changed, 645 insertions, 358 deletions
diff --git a/chrome_frame/crash_reporting/crash_report.cc b/chrome_frame/crash_reporting/crash_report.cc index 2a1b88b..7c2302b 100644 --- a/chrome_frame/crash_reporting/crash_report.cc +++ b/chrome_frame/crash_reporting/crash_report.cc @@ -8,19 +8,96 @@ #include "base/basictypes.h" #include "breakpad/src/client/windows/handler/exception_handler.h" -#include "chrome_frame/crash_reporting/vectored_handler.h" // TODO(joshia): factor out common code with chrome used for crash reporting const wchar_t kGoogleUpdatePipeName[] = L"\\\\.\\pipe\\GoogleCrashServices\\"; -google_breakpad::ExceptionHandler* g_breakpad = NULL; +static google_breakpad::ExceptionHandler * g_breakpad = NULL; -Win32VEHTraits::CodeBlock Win32VEHTraits::IgnoreExceptions[kIgnoreEntries] = { - { "kernel32.dll", "IsBadReadPtr", 0, 100, NULL }, - { "kernel32.dll", "IsBadWritePtr", 0, 100, NULL }, - { "kernel32.dll", "IsBadStringPtrA", 0, 100, NULL }, - { "kernel32.dll", "IsBadStringPtrW", 0, 100, NULL }, +#pragma code_seg(push, ".text$va") +static void veh_segment_start() {} +#pragma code_seg(pop) + +#pragma code_seg(push, ".text$vz") +static void veh_segment_end() {} +#pragma code_seg(pop) + +// Place code in .text$veh_m. +#pragma code_seg(push, ".text$vm") +#include "chrome_frame/crash_reporting/vectored_handler-impl.h" + +// Use Win32 API; use breakpad for dumps; checks for single (current) module. +class CrashHandlerTraits : public Win32VEHTraits, + public ModuleOfInterestWithExcludedRegion { + public: + CrashHandlerTraits() : breakpad_(NULL) {} + void Init(google_breakpad::ExceptionHandler* breakpad) { + breakpad_ = breakpad; + Win32VEHTraits::InitializeIgnoredBlocks(); + 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() { + breakpad_ = 0; + } + + inline bool WriteDump(EXCEPTION_POINTERS* p) { + return breakpad_->WriteMinidumpForException(p); + } + + private: + google_breakpad::ExceptionHandler* breakpad_; +}; + +class CrashHandler { + public: + CrashHandler() : veh_id_(NULL), handler_(&crash_api_) {} + bool Init(google_breakpad::ExceptionHandler* breakpad); + void Shutdown(); + private: + VectoredHandlerT<CrashHandlerTraits> handler_; + CrashHandlerTraits crash_api_; + void* veh_id_; + + static LONG WINAPI VectoredHandlerEntryPoint(EXCEPTION_POINTERS* exptrs); }; +static CrashHandler g_crash_handler; + +LONG WINAPI CrashHandler::VectoredHandlerEntryPoint( + EXCEPTION_POINTERS* exptrs) { + return g_crash_handler.handler_.Handler(exptrs); +} +#pragma code_seg(pop) + +bool CrashHandler::Init(google_breakpad::ExceptionHandler* breakpad) { + if (veh_id_) + return true; + + void* id = ::AddVectoredExceptionHandler(FALSE, &VectoredHandlerEntryPoint); + if (id != NULL) { + veh_id_ = id; + crash_api_.Init(breakpad); + return true; + } + + return false; +} + +void CrashHandler::Shutdown() { + if (veh_id_) { + ::RemoveVectoredExceptionHandler(veh_id_); + veh_id_ = NULL; + } + + crash_api_.Shutdown(); +} + std::wstring GetCrashServerPipeName(const std::wstring& user_sid) { std::wstring pipe_name = kGoogleUpdatePipeName; pipe_name += user_sid; @@ -46,17 +123,16 @@ bool InitializeVectoredCrashReportingWithPipeName( google_breakpad::ExceptionHandler::HANDLER_PURECALL, dump_type, pipe_name, client_info); - if (g_breakpad) { - // Find current module boundaries. - const void* start = &__ImageBase; - const char* s = reinterpret_cast<const char*>(start); - const IMAGE_NT_HEADERS32* nt = reinterpret_cast<const IMAGE_NT_HEADERS32*> - (s + __ImageBase.e_lfanew); - const void* end = s + nt->OptionalHeader.SizeOfImage; - VectoredHandler::Register(start, end); + if (!g_breakpad) + return false; + + if (!g_crash_handler.Init(g_breakpad)) { + delete g_breakpad; + g_breakpad = NULL; + return false; } - return g_breakpad != NULL; + return true; } bool InitializeVectoredCrashReporting( @@ -76,7 +152,7 @@ bool InitializeVectoredCrashReporting( } bool ShutdownVectoredCrashReporting() { - VectoredHandler::Unregister(); + g_crash_handler.Shutdown(); delete g_breakpad; g_breakpad = NULL; return true; diff --git a/chrome_frame/crash_reporting/crash_report.h b/chrome_frame/crash_reporting/crash_report.h index 829dce2..d9fd42f 100644 --- a/chrome_frame/crash_reporting/crash_report.h +++ b/chrome_frame/crash_reporting/crash_report.h @@ -11,7 +11,6 @@ #include "base/logging.h" #include "breakpad/src/client/windows/handler/exception_handler.h" -#include "chrome_frame/crash_reporting/vectored_handler-impl.h" extern google_breakpad::ExceptionHandler* g_breakpad; extern "C" IMAGE_DOS_HEADER __ImageBase; @@ -30,89 +29,4 @@ bool InitializeVectoredCrashReportingWithPipeName( bool ShutdownVectoredCrashReporting(); -namespace { -__declspec(naked) -static EXCEPTION_REGISTRATION_RECORD* InternalRtlpGetExceptionList() { - __asm { - mov eax, fs:0 - ret - } -} -} // end of namespace - -// Class which methods simply forwards to Win32 API and uses breakpad to write -// a minidump. Used as template (external interface) of VectoredHandlerT<E>. -class Win32VEHTraits : public VEHTraitsBase { - public: - static inline void* Register(PVECTORED_EXCEPTION_HANDLER func, - const void* module_start, const void* module_end) { - InitializeIgnoredBlocks(); - VEHTraitsBase::SetModule(module_start, module_end); - return ::AddVectoredExceptionHandler(1, func); - } - - static inline ULONG Unregister(void* handle) { - return ::RemoveVectoredExceptionHandler(handle); - } - - static inline bool WriteDump(EXCEPTION_POINTERS* p) { - return g_breakpad->WriteMinidumpForException(p); - } - - 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 void* address = exceptionInfo->ExceptionRecord->ExceptionAddress; - for (int i = 0; i < kIgnoreEntries; i++) { - const CodeBlock& code_block = IgnoreExceptions[i]; - if (code_block.code && - (CodeOffset(code_block.code, code_block.begin_offset) <= address) && - (address < CodeOffset(code_block.code, code_block.end_offset))) { - return true; - } - } - - return false; - } - - static inline void InitializeIgnoredBlocks() { - // Initialize ignored exception list - for (int i = 0; i < kIgnoreEntries; i++) { - CodeBlock& code_block = IgnoreExceptions[i]; - if (!code_block.code) { - HMODULE module = GetModuleHandleA(code_block.module); - DCHECK(module) << "GetModuleHandle error: " << GetLastError(); - code_block.code = GetProcAddress(module, code_block.function); - DCHECK(code_block.code) << "GetProcAddress error: "<< GetLastError(); - } - } - } - - static inline const void* CodeOffset(const void* code, int offset) { - return reinterpret_cast<const char*>(code) + offset; - } - - private: - // Block of code to be ignored for exceptions - struct CodeBlock { - char* module; - char* function; - int begin_offset; - int end_offset; - const void* code; - }; - - static const int kIgnoreEntries = 4; - static CodeBlock IgnoreExceptions[kIgnoreEntries]; -}; - - #endif // CHROME_FRAME_CRASH_REPORTING_CRASH_REPORT_H_ diff --git a/chrome_frame/crash_reporting/crash_reporting.gyp b/chrome_frame/crash_reporting/crash_reporting.gyp index 6cd712c..020c462 100644 --- a/chrome_frame/crash_reporting/crash_reporting.gyp +++ b/chrome_frame/crash_reporting/crash_reporting.gyp @@ -35,6 +35,8 @@ 'type': 'executable', 'sources': [ 'vectored_handler_unittest.cc', + 'veh_test.cc', + 'veh_test.h', ], 'dependencies': [ 'crash_report', diff --git a/chrome_frame/crash_reporting/vectored_handler-impl.h b/chrome_frame/crash_reporting/vectored_handler-impl.h index c6d7d8e..9c0e97d 100644 --- a/chrome_frame/crash_reporting/vectored_handler-impl.h +++ b/chrome_frame/crash_reporting/vectored_handler-impl.h @@ -7,16 +7,17 @@ #include "chrome_frame/crash_reporting/vectored_handler.h" #if defined(_M_IX86) +#ifndef EXCEPTION_CHAIN_END +#define EXCEPTION_CHAIN_END ((struct _EXCEPTION_REGISTRATION_RECORD*)-1) typedef struct _EXCEPTION_REGISTRATION_RECORD { struct _EXCEPTION_REGISTRATION_RECORD* Next; PVOID Handler; } EXCEPTION_REGISTRATION_RECORD; -#define EXCEPTION_CHAIN_END ((struct _EXCEPTION_REGISTRATION_RECORD*)-1) +#endif // EXCEPTION_CHAIN_END #else #error only x86 is supported for now. #endif - // VEH handler flags settings. // These are grabbed from winnt.h for PocketPC. // Only EXCEPTION_NONCONTINUABLE in defined in "regular" winnt.h @@ -36,9 +37,16 @@ typedef struct _EXCEPTION_REGISTRATION_RECORD { #define IS_TARGET_UNWIND(Flag) ((Flag) & EXCEPTION_TARGET_UNWIND) // End of grabbed section -template <class E> -LONG WINAPI VectoredHandlerT<E>::VectoredHandler( - EXCEPTION_POINTERS* exceptionInfo) { +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; @@ -51,7 +59,6 @@ LONG WINAPI VectoredHandlerT<E>::VectoredHandler( // 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; } @@ -63,11 +70,19 @@ LONG WINAPI VectoredHandlerT<E>::VectoredHandler( return ExceptionContinueSearch; } - ++VectoredHandlerT<E>::g_exceptions_seen; + 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 address is inbetween // [IsBadReadPtr, IsBadReadPtr + 0xXX] - if (E::ShouldIgnoreException(exceptionInfo)) { + if (api_->ShouldIgnoreException(exceptionInfo)) { return ExceptionContinueSearch; } @@ -77,40 +92,143 @@ LONG WINAPI VectoredHandlerT<E>::VectoredHandler( if (ModuleHasInstalledSEHFilter()) return ExceptionContinueSearch; - if (E::IsOurModule(exceptionInfo->ExceptionRecord->ExceptionAddress)) { - E::WriteDump(exceptionInfo); + if (api_->IsOurModule(exceptionInfo->ExceptionRecord->ExceptionAddress)) { + api_->WriteDump(exceptionInfo); return ExceptionContinueSearch; } // See whether our module is somewhere in the call stack. - void* back_trace[max_back_trace] = {0}; - // Skip RtlCaptureStackBackTrace and VectoredHandler itself. - DWORD captured = E::RtlCaptureStackBackTrace(2, max_back_trace - 2, - &back_trace[0], NULL); + void* back_trace[api_->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 (E::IsOurModule(back_trace[i])) { - E::WriteDump(exceptionInfo); - return ExceptionContinueSearch; - } + if (api_->IsOurModule(back_trace[i])) { + api_->WriteDump(exceptionInfo); + return ExceptionContinueSearch; + } } } return ExceptionContinueSearch; } -template <class E> -BOOL VectoredHandlerT<E>::ModuleHasInstalledSEHFilter() { - EXCEPTION_REGISTRATION_RECORD* RegistrationFrame = E::RtlpGetExceptionList(); +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 (E::IsOurModule(RegistrationFrame->Handler)) { - return TRUE; + if (api_->IsOurModule(RegistrationFrame->Handler)) { + return true; } RegistrationFrame = RegistrationFrame->Next; } - return FALSE; + 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 void* address = exceptionInfo->ExceptionRecord->ExceptionAddress; + for (int i = 0; i < kIgnoreEntries; i++) { + const CodeBlock& code_block = IgnoreExceptions[i]; + DCHECK(code_block.code) << "Win32VEHTraits::CodeBlocks not initialized!"; + if ((CodeOffset(code_block.code, code_block.begin_offset) <= address) && + (address < CodeOffset(code_block.code, code_block.end_offset))) { + return true; + } + } + + return false; + } + + static bool CheckForStackOverflow() { + MEMORY_BASIC_INFORMATION mi; + 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); + } + + static void InitializeIgnoredBlocks() { + // Initialize ignored exception list + for (int i = 0; i < kIgnoreEntries; i++) { + CodeBlock& code_block = IgnoreExceptions[i]; + if (!code_block.code) { + HMODULE module = GetModuleHandleA(code_block.module); + DCHECK(module) << "GetModuleHandle error: " << GetLastError(); + code_block.code = GetProcAddress(module, code_block.function); + DCHECK(code_block.code) << "GetProcAddress error: "<< GetLastError(); + } + } + } + + private: + static inline const void* CodeOffset(const void* code, int offset) { + return reinterpret_cast<const char*>(code) + offset; + } + + // Block of code to be ignored for exceptions + struct CodeBlock { + char* module; + char* function; + int begin_offset; + int end_offset; + const void* code; + }; + + static const int kIgnoreEntries = 4; + static CodeBlock IgnoreExceptions[kIgnoreEntries]; +}; + +DECLSPEC_SELECTANY Win32VEHTraits::CodeBlock +Win32VEHTraits::IgnoreExceptions[kIgnoreEntries] = { + { "kernel32.dll", "IsBadReadPtr", 0, 100, NULL }, + { "kernel32.dll", "IsBadWritePtr", 0, 100, NULL }, + { "kernel32.dll", "IsBadStringPtrA", 0, 100, NULL }, + { "kernel32.dll", "IsBadStringPtrW", 0, 100, NULL }, +}; + #endif // CHROME_FRAME_CRASH_REPORTING_VECTORED_HANDLER_IMPL_H_ diff --git a/chrome_frame/crash_reporting/vectored_handler.h b/chrome_frame/crash_reporting/vectored_handler.h index 7447bba..1466bb5 100644 --- a/chrome_frame/crash_reporting/vectored_handler.h +++ b/chrome_frame/crash_reporting/vectored_handler.h @@ -5,87 +5,109 @@ #ifndef CHROME_FRAME_CRASH_REPORTING_VECTORED_HANDLER_H_ #define CHROME_FRAME_CRASH_REPORTING_VECTORED_HANDLER_H_ -// Base class for VectoredHandlerT just to hold some members (independent of -// template parameter) -class VectoredHandlerBase { - public: - // For RtlCaptureStackBackTrace MSDN says: - // Windows Server 2003 and Windows XP: The sum of the FramesToSkip and - // FramesToCapture parameters must be less than 64. - // In practice (on XPSP2) it has to be less than 63, hence leaving us with - // max back trace of 62. - static const DWORD max_back_trace = 62; - static unsigned long g_exceptions_seen; - protected: - static void* g_handler; -}; -DECLSPEC_SELECTANY void* VectoredHandlerBase::g_handler; -DECLSPEC_SELECTANY unsigned long VectoredHandlerBase::g_exceptions_seen; +#if !defined(_M_IX86) +#error only x86 is supported for now. +#endif -// The E class is supposed to provide external/API functions. Using template -// make testability easier. It shall confirm the following concept/archetype: -// void* Register(PVECTORED_EXCEPTION_HANDLER, -// const void* module_start, const void* module_end) -// Registers Vectored Exception Handler, non-unittest implementation shall call -// ::AddVectoredExceptionHandler Win32 API -// ULONG Unregister(void*) - ::RemoveVectoredExceptionHandler Win32 API -// int IsOurModule(const void* address) - -// void WriteDump(EXCEPTION_POINTERS*) - -// WORD RtlCaptureStackBackTrace(..) - same as Win32 API -// EXCEPTION_REGISTRATION_RECORD* RtlpGetExceptionList() - same as Win32 API -// You may want to derive own External class by deriving from -// VEHExternalBase helper (see below). // Create dump policy: // 1. Scan SEH chain, if there is a handler/filter that belongs to our // module - assume we expect this one and hence do nothing here. // 2. If the address of the exception is in our module - create dump. // 3. If our module is in somewhere in callstack - create dump. -template <class E> -class VectoredHandlerT : public VectoredHandlerBase { +// The E class is supposed to provide external/API functions. Using template +// make testability easier. It shall confirm the following concept/archetype: +//struct E { +// void WriteDump(EXCEPTION_POINTERS* p) { +// } +// +// // Used mainly to ignore exceptions from IsBadRead/Write/Ptr. +// bool ShouldIgnoreException(const EXCEPTION_POINTERS* exptr) { +// return 0; +// } +// +// // Retrieve the SEH list head. +// EXCEPTION_REGISTRATION_RECORD* RtlpGetExceptionList() { +// return NULL; +// } +// +// // Get the stack trace as correctly as possible. +// WORD RtlCaptureStackBackTrace(DWORD FramesToSkip, DWORD FramesToCapture, +// void** BackTrace, DWORD* BackTraceHash) { +// return 0; +// } +// +// // Check whether the stack guard page is in place. +// bool CheckForStackOverflow(EXCEPTION_POINTERS* p) { +// return 0; +// } +// +// bool IsOurModule(const void* address) { +// return 0; +// } +//}; +// The methods shall be placed in .text$veh_m +template <typename E> +class VectoredHandlerT { public: - static void* Register(const void* module_start, const void* module_end) { - g_exceptions_seen = 0; - g_handler = E::Register(&VectoredHandler, module_start, module_end); - return g_handler; - } + VectoredHandlerT(E* api); + ~VectoredHandlerT(); - static ULONG Unregister() { - if (g_handler) - return E::Unregister(g_handler); - return 0; + // TODO(stoyan): Come with better way to skip initial stack frames. + FORCEINLINE LONG Handler(EXCEPTION_POINTERS* exceptionInfo); + long get_exceptions_seen() const { + return exceptions_seen_; } - static LONG WINAPI VectoredHandler(EXCEPTION_POINTERS* exceptionInfo); private: - static BOOL ModuleHasInstalledSEHFilter(); + bool ModuleHasInstalledSEHFilter(); + E* api_; + long exceptions_seen_; }; -// Handy class supposed to act as a base class for classes used as template -// parameter of VectoredHandlerT<E> -class VEHTraitsBase { - public: - static const void* g_module_start; - static const void* g_module_end; +// Maintains start and end address of a single module of interest. If we want +// do check for multiple modules, this class has to be extended to support a +// list of modules (DLLs). +struct ModuleOfInterest { + // The callback from VectoredHandlerT::Handler(). + inline bool IsOurModule(const void* address) { + return (start_ <= address && address < end_); + } - static inline int IsOurModule(const void* address) { - return (g_module_start <= address && address < g_module_end); + // Helpers. + inline void SetModule(const void* module_start, const void* module_end) { + start_ = module_start; + end_ = module_end; } - static inline void SetModule(const void* module_start, - const void* module_end) { - g_module_start = module_start; - g_module_end = module_end; + inline void SetCurrentModule() { + // Find current module boundaries. + const void* start = &__ImageBase; + const char* s = reinterpret_cast<const char*>(start); + const IMAGE_NT_HEADERS32* nt = reinterpret_cast<const IMAGE_NT_HEADERS32*> + (s + __ImageBase.e_lfanew); + const void* end = s + nt->OptionalHeader.SizeOfImage; + SetModule(start, end); } - static bool ShouldIgnoreException(const EXCEPTION_POINTERS* exceptionInfo) { - return false; + const void* start_; + const void* end_; +}; + +struct ModuleOfInterestWithExcludedRegion : public ModuleOfInterest { + inline bool IsOurModule(const void* address) { + return (start_ <= address && address < end_) && + (address < special_region_start_ || special_region_end_ <= address); } + + inline void SetExcludedRegion(const void* start, const void* end) { + special_region_start_ = start; + special_region_end_ = end; + } + + const void* special_region_start_; + const void* special_region_end_; }; -DECLSPEC_SELECTANY const void* VEHTraitsBase::g_module_start; -DECLSPEC_SELECTANY const void* VEHTraitsBase::g_module_end; -class Win32VEHTraits; -typedef class VectoredHandlerT<Win32VEHTraits> VectoredHandler; #endif // CHROME_FRAME_CRASH_REPORTING_VECTORED_HANDLER_H_ diff --git a/chrome_frame/crash_reporting/vectored_handler_unittest.cc b/chrome_frame/crash_reporting/vectored_handler_unittest.cc index 0b87f04..3df4608 100644 --- a/chrome_frame/crash_reporting/vectored_handler_unittest.cc +++ b/chrome_frame/crash_reporting/vectored_handler_unittest.cc @@ -6,209 +6,180 @@ #include "base/logging.h" #include "chrome_frame/crash_reporting/vectored_handler-impl.h" -#include "chrome_frame/crash_reporting/crash_report.h" -#include "gmock/gmock.h" +#include "chrome_frame/crash_reporting/veh_test.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" -// Class that mocks external call from VectoredHandlerT for testing purposes. -class EMock : public Win32VEHTraits { +using testing::_; + +ACTION_P(StackTraceDump, s) { + memcpy(arg2, s->stack_, s->count_ * sizeof(s->stack_[0])); + return s->count_; +} +namespace { +class MockApi : public Win32VEHTraits, public ModuleOfInterest { public: - static inline bool WriteDump(EXCEPTION_POINTERS* p) { - g_dump_made = true; - return true; + MockApi() { + Win32VEHTraits::InitializeIgnoredBlocks(); } - static inline void* Register(PVECTORED_EXCEPTION_HANDLER func, - const void* module_start, - const void* module_end) { - InitializeIgnoredBlocks(); - VEHTraitsBase::SetModule(module_start, module_end); - // Return some arbitrary number, expecting to get the same on Unregister() - return reinterpret_cast<void*>(4); - } + MOCK_METHOD1(WriteDump, void(const EXCEPTION_POINTERS*)); + MOCK_METHOD0(RtlpGetExceptionList, const EXCEPTION_REGISTRATION_RECORD*()); + MOCK_METHOD4(RtlCaptureStackBackTrace, WORD(DWORD FramesToSkip, + DWORD FramesToCapture, void** BackTrace, DWORD* BackTraceHash)); - static inline ULONG Unregister(void* handle) { - EXPECT_EQ(handle, reinterpret_cast<void*>(4)); - return 1; + // Helpers + void SetSEH(const SEHChain& sehchain) { + EXPECT_CALL(*this, RtlpGetExceptionList()) + .Times(testing::AnyNumber()) + .WillRepeatedly(testing::Return(sehchain.chain_)); } - static inline WORD RtlCaptureStackBackTrace(DWORD FramesToSkip, - DWORD FramesToCapture, void** BackTrace, DWORD* BackTraceHash) { - EXPECT_EQ(2, FramesToSkip); - EXPECT_LE(FramesToSkip + FramesToCapture, - VectoredHandlerBase::max_back_trace); - memcpy(BackTrace, g_stack, g_stack_entries * sizeof(BackTrace[0])); - return g_stack_entries; + void SetStack(const StackHelper& s) { + EXPECT_CALL(*this, RtlCaptureStackBackTrace(_, _, _, _)) + .Times(testing::AnyNumber()) + .WillRepeatedly(StackTraceDump(&s)); } - static inline EXCEPTION_REGISTRATION_RECORD* RtlpGetExceptionList() { - return g_seh_chain; - } + enum {max_back_trace = 15}; +}; +}; // namespace - // Test helpers - // Create fake SEH chain of random filters - with and without our module. - static void SetHaveSEHFilter() { - SetSEHChain(reinterpret_cast<const char*>(g_module_start) - 0x1000, - reinterpret_cast<const char*>(g_module_start) + 0x1000, - reinterpret_cast<const char*>(g_module_end) + 0x7127, - NULL); - } +typedef VectoredHandlerT<MockApi> VectoredHandlerMock; +TEST(ChromeFrame, ExceptionReport) { + MockApi api; + VectoredHandlerMock veh(&api); - static void SetNoSEHFilter() { - SetSEHChain(reinterpret_cast<const char*>(g_module_start) - 0x1000, - reinterpret_cast<const char*>(g_module_end) + 0x7127, - NULL); - } + // Start address of our module. + char* s = reinterpret_cast<char*>(0x30000000); + char *e = s + 0x10000; + api.SetModule(s, e); - // Create fake stack - with and without our module. - static void SetOnStack() { - SetStack(reinterpret_cast<const char*>(g_module_start) - 0x11283, - reinterpret_cast<const char*>(g_module_start) - 0x278361, - reinterpret_cast<const char*>(g_module_start) + 0x9171, - reinterpret_cast<const char*>(g_module_end) + 1231, - NULL); - } + SEHChain have_seh(s - 0x1000, s + 0x1000, e + 0x7127, NULL); + SEHChain no_seh(s - 0x1000, e + 0x7127, NULL); + StackHelper on_stack(s - 0x11283, s - 0x278361, s + 0x9171, e + 1231, NULL); + StackHelper not_on_stack(s - 0x11283, s - 0x278361, e + 1231, NULL); - static void SetNotOnStack() { - SetStack(reinterpret_cast<const char*>(g_module_start) - 0x11283, - reinterpret_cast<const char*>(g_module_start) - 0x278361, - reinterpret_cast<const char*>(g_module_end) + 1231, - NULL); - } + char* our_code = s + 0x1111; + char* not_our_code = s - 0x5555; + char* not_our_code2 = e + 0x5555; - // Populate stack array - static void SetStack(const void* p, ...) { - va_list vl; - va_start(vl, p); - g_stack_entries = 0; - for (; p; ++g_stack_entries) { - CHECK(g_stack_entries < arraysize(g_stack)); - g_stack[g_stack_entries] = p; - p = va_arg(vl, const void*); - } - } + // Exception in our code, but we have SEH filter => no dump. + api.SetSEH(have_seh); + api.SetStack(on_stack); + EXPECT_CALL(api, WriteDump(_)).Times(0); + EXPECT_EQ(ExceptionContinueSearch, + veh.Handler(&ExceptionInfo(STATUS_ACCESS_VIOLATION, our_code))); + testing::Mock::VerifyAndClearExpectations(&api); - static void SetSEHChain(const void* p, ...) { - va_list vl; - va_start(vl, p); - int i = 0; - for (; p; ++i) { - CHECK(i + 1 < arraysize(g_seh_chain)); - g_seh_chain[i].Handler = const_cast<void*>(p); - g_seh_chain[i].Next = &g_seh_chain[i + 1]; - p = va_arg(vl, const void*); - } - - g_seh_chain[i].Next = EXCEPTION_CHAIN_END; - } + // RPC_E_DISCONNECTED (0x80010108) is "The object invoked has disconnected + // from its clients", shall not be caught since it's a warning only. + EXPECT_CALL(api, WriteDump(_)).Times(0); + EXPECT_EQ(ExceptionContinueSearch, + veh.Handler(&ExceptionInfo(RPC_E_DISCONNECTED, our_code))); + testing::Mock::VerifyAndClearExpectations(&api); - static EXCEPTION_REGISTRATION_RECORD g_seh_chain[25]; - static const void* g_stack[VectoredHandlerBase::max_back_trace]; - static WORD g_stack_entries; - static bool g_dump_made; -}; + // Exception, not in our code, we do not have SEH and we are not in stack. + api.SetSEH(no_seh); + api.SetStack(not_on_stack); + EXPECT_CALL(api, WriteDump(_)).Times(0); + EXPECT_EQ(ExceptionContinueSearch, + veh.Handler(&ExceptionInfo(STATUS_INTEGER_DIVIDE_BY_ZERO, not_our_code))); + testing::Mock::VerifyAndClearExpectations(&api); -EXCEPTION_REGISTRATION_RECORD EMock::g_seh_chain[25]; -const void* EMock::g_stack[VectoredHandlerBase::max_back_trace]; -WORD EMock::g_stack_entries; -bool EMock::g_dump_made; + // Exception, not in our code, no SEH, but we are on the stack. + api.SetSEH(no_seh); + api.SetStack(on_stack); + EXPECT_CALL(api, WriteDump(_)).Times(1); + EXPECT_EQ(ExceptionContinueSearch, + veh.Handler(&ExceptionInfo(STATUS_INTEGER_DIVIDE_BY_ZERO, + not_our_code2))); + testing::Mock::VerifyAndClearExpectations(&api); -typedef VectoredHandlerT<EMock> VectoredHandlerMock; + // Exception, in our code, no SEH, not on stack (assume FPO screwed us) + api.SetSEH(no_seh); + api.SetStack(not_on_stack); + EXPECT_CALL(api, WriteDump(_)).Times(1); + EXPECT_EQ(ExceptionContinueSearch, + veh.Handler(&ExceptionInfo(STATUS_PRIVILEGED_INSTRUCTION, our_code))); + testing::Mock::VerifyAndClearExpectations(&api); -class ExPtrsHelper : public _EXCEPTION_POINTERS { - public: - ExPtrsHelper() { - ExceptionRecord = &er_; - ContextRecord = &ctx_; - ZeroMemory(&er_, sizeof(er_)); - ZeroMemory(&ctx_, sizeof(ctx_)); - } + // Exception, in IsBadStringPtrA, we are on the stack. + api.SetSEH(no_seh); + api.SetStack(on_stack); + EXPECT_CALL(api, WriteDump(_)).Times(0); + char* ignore_address = reinterpret_cast<char*>(GetProcAddress( + GetModuleHandleA("kernel32.dll"), "IsBadStringPtrA")) + 10; + EXPECT_EQ(ExceptionContinueSearch, + veh.Handler(&ExceptionInfo(STATUS_ACCESS_VIOLATION, ignore_address))); + testing::Mock::VerifyAndClearExpectations(&api); + + // Exception, in IsBadStringPtrA, we are not on the stack. + api.SetSEH(no_seh); + api.SetStack(not_on_stack); + EXPECT_CALL(api, WriteDump(_)).Times(0); + EXPECT_EQ(ExceptionContinueSearch, + veh.Handler(&ExceptionInfo(STATUS_ACCESS_VIOLATION, ignore_address))); + testing::Mock::VerifyAndClearExpectations(&api); +} + +MATCHER_P(ExceptionCodeIs, code, "") { + return (arg->ExceptionRecord->ExceptionCode == code); +} + +void OverflowStack() { + char tmp[1024 * 2048]; + ZeroMemory(tmp, sizeof(tmp)); +} + +DWORD WINAPI CrashingThread(PVOID tmp) { + __try { + OverflowStack(); + } __except(EXCEPTION_EXECUTE_HANDLER) { - void Set(DWORD code, void* address, DWORD flags) { - er_.ExceptionCode = code; - er_.ExceptionAddress = address; - er_.ExceptionFlags = flags; } - EXCEPTION_RECORD er_; - CONTEXT ctx_; -}; + // This will cause STATUS_ACCESS_VIOLATION + __try { + OverflowStack(); + } __except(EXCEPTION_EXECUTE_HANDLER) { + } -TEST(ChromeFrame, ExceptionReport) { - char* s = reinterpret_cast<char*>(0x30000000); - char* e = s + 0x10000; - void* handler = VectoredHandlerMock::Register(s, e); - char* our_code = s + 0x1111; - char* not_our_code = s - 0x5555; - char* not_our_code2 = e + 0x5555; + return 0; +} - ExPtrsHelper ex; - // Exception in our code, but we have SEH filter - ex.Set(STATUS_ACCESS_VIOLATION, our_code, 0); - EMock::SetHaveSEHFilter(); - EMock::SetOnStack(); - EXPECT_EQ(ExceptionContinueSearch, VectoredHandlerMock::VectoredHandler(&ex)); - EXPECT_EQ(1, VectoredHandlerMock::g_exceptions_seen); - EXPECT_FALSE(EMock::g_dump_made); +static VectoredHandlerMock* g_mock_veh = NULL; +static LONG WINAPI VEH(EXCEPTION_POINTERS* exptrs) { + return g_mock_veh->Handler(exptrs); +} - // RPC_E_DISCONNECTED (0x80010108) is "The object invoked has disconnected - // from its clients", shall not be caught since it's a warning only. - ex.Set(RPC_E_DISCONNECTED, our_code, 0); - EMock::SetHaveSEHFilter(); - EMock::SetOnStack(); - EXPECT_EQ(ExceptionContinueSearch, VectoredHandlerMock::VectoredHandler(&ex)); - EXPECT_EQ(1, VectoredHandlerMock::g_exceptions_seen); - EXPECT_FALSE(EMock::g_dump_made); +TEST(ChromeFrame, TrickyStackOverflow) { + MockApi api; + VectoredHandlerMock veh(&api); + // Start address of our module. + char* s = reinterpret_cast<char*>(0x30000000); + char *e = s + 0x10000; + api.SetModule(s, e); - // Exception, not in our code, we do not have SEH and we are not in stack. - ex.Set(STATUS_INTEGER_DIVIDE_BY_ZERO, not_our_code, 0); - EMock::SetNoSEHFilter(); - EMock::SetNotOnStack(); - EXPECT_EQ(ExceptionContinueSearch, VectoredHandlerMock::VectoredHandler(&ex)); - EXPECT_EQ(2, VectoredHandlerMock::g_exceptions_seen); - EXPECT_FALSE(EMock::g_dump_made); + SEHChain no_seh(s - 0x1000, e + 0x7127, NULL); + StackHelper on_stack(s - 0x11283, s - 0x278361, s + 0x9171, e + 1231, NULL); + api.SetSEH(no_seh); + api.SetStack(on_stack); - // Exception, not in our code, no SEH, but we are on the stack. - ex.Set(STATUS_INTEGER_DIVIDE_BY_ZERO, not_our_code2, 0); - EMock::SetNoSEHFilter(); - EMock::SetOnStack(); - EXPECT_EQ(ExceptionContinueSearch, VectoredHandlerMock::VectoredHandler(&ex)); - EXPECT_EQ(3, VectoredHandlerMock::g_exceptions_seen); - EXPECT_TRUE(EMock::g_dump_made); - EMock::g_dump_made = false; + EXPECT_CALL(api, WriteDump(ExceptionCodeIs(STATUS_STACK_OVERFLOW))).Times(1); + g_mock_veh = &veh; + void* id = ::AddVectoredExceptionHandler(FALSE, VEH); - // Exception, in our code, no SEH, not on stack (assume FPO screwed us) - ex.Set(STATUS_INTEGER_DIVIDE_BY_ZERO, our_code, 0); - EMock::SetNoSEHFilter(); - EMock::SetNotOnStack(); - EXPECT_EQ(ExceptionContinueSearch, VectoredHandlerMock::VectoredHandler(&ex)); - EXPECT_EQ(4, VectoredHandlerMock::g_exceptions_seen); - EXPECT_TRUE(EMock::g_dump_made); - EMock::g_dump_made = false; + DWORD tid; + HANDLE h = ::CreateThread(0, 0, CrashingThread, 0, 0, &tid); + ::WaitForSingleObject(h, INFINITE); + ::CloseHandle(h); - // Exception, in IsBadStringPtrA, we are on the stack. - char* ignore_address = reinterpret_cast<char*>(GetProcAddress( - GetModuleHandleA("kernel32.dll"), "IsBadStringPtrA")) + 10; - ex.Set(STATUS_ACCESS_VIOLATION, ignore_address + 10, 0); - EMock::SetNoSEHFilter(); - EMock::SetOnStack(); - EXPECT_EQ(ExceptionContinueSearch, VectoredHandlerMock::VectoredHandler(&ex)); - EXPECT_EQ(5, VectoredHandlerMock::g_exceptions_seen); - EXPECT_FALSE(EMock::g_dump_made); - EMock::g_dump_made = false; - - // Exception, in IsBadStringPtrA, we are not in stack. - ex.Set(STATUS_ACCESS_VIOLATION, ignore_address + 10, 0); - EMock::SetNoSEHFilter(); - EMock::SetNotOnStack(); - EXPECT_EQ(ExceptionContinueSearch, VectoredHandlerMock::VectoredHandler(&ex)); - EXPECT_EQ(6, VectoredHandlerMock::g_exceptions_seen); - EXPECT_FALSE(EMock::g_dump_made); - EMock::g_dump_made = false; - - VectoredHandlerMock::Unregister(); + EXPECT_EQ(2, veh.get_exceptions_seen()); + ::RemoveVectoredExceptionHandler(id); + g_mock_veh = NULL; } diff --git a/chrome_frame/crash_reporting/veh_test.cc b/chrome_frame/crash_reporting/veh_test.cc new file mode 100644 index 0000000..7f844e9 --- /dev/null +++ b/chrome_frame/crash_reporting/veh_test.cc @@ -0,0 +1,104 @@ +// Copyright (c) 2010 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. + +#include <atlbase.h> +#include "chrome_frame/crash_reporting/veh_test.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "chrome_frame/crash_reporting/vectored_handler-impl.h" + +#pragma code_seg(push, ".m$_0") +static void ModuleStart() {} +#pragma code_seg(pop) + +#pragma code_seg(push, ".m$_2") +static void Undetectable(DWORD code) { + __try { + ::RaiseException(code, 0, 0, NULL); + } __except(EXCEPTION_EXECUTE_HANDLER) { + + } +}; +#pragma code_seg(pop) + +#pragma code_seg(push, ".m$_3") +static void UndetectableEnd() {} +#pragma code_seg(pop) + +#pragma code_seg(push, ".m$_4") +static void CatchThis() { + __try { + ::RaiseException(STATUS_ACCESS_VIOLATION, 0, 0, NULL); + } __except(EXCEPTION_EXECUTE_HANDLER) { + + } + + // this will be detected since we are on the stack! + Undetectable(STATUS_ILLEGAL_INSTRUCTION); +} + +#pragma code_seg(pop) + +#pragma code_seg(push, ".m$_9") +static void ModuleEnd() {} +#pragma code_seg(pop) + +using testing::_; +namespace { +MATCHER_P(ExceptionCodeIs, code, "") { + return (arg->ExceptionRecord->ExceptionCode == code); +} + +class MockApi : public Win32VEHTraits, + public ModuleOfInterestWithExcludedRegion { + public: + MockApi() { + Win32VEHTraits::InitializeIgnoredBlocks(); + ModuleOfInterestWithExcludedRegion::SetModule(&ModuleStart, &ModuleEnd); + ModuleOfInterestWithExcludedRegion::SetExcludedRegion(&Undetectable, + &UndetectableEnd); + } + + MOCK_METHOD1(WriteDump, void(const EXCEPTION_POINTERS*)); + MOCK_METHOD0(RtlpGetExceptionList, const EXCEPTION_REGISTRATION_RECORD*()); +}; +}; // namespace + +typedef VectoredHandlerT<MockApi> VectoredHandlerMock; + +static VectoredHandlerMock* g_mock_veh = NULL; +static LONG WINAPI VEH(EXCEPTION_POINTERS* exptrs) { + return g_mock_veh->Handler(exptrs); +} + +TEST(ChromeFrame, ExceptionExcludedCode) { + MockApi api; + VectoredHandlerMock veh(&api); + + g_mock_veh = &veh; + void* id = ::AddVectoredExceptionHandler(FALSE, VEH); + + EXPECT_CALL(api, RtlpGetExceptionList()) + .WillRepeatedly(testing::Return(EXCEPTION_CHAIN_END)); + + testing::Sequence s; + + EXPECT_CALL(api, WriteDump(ExceptionCodeIs(STATUS_ACCESS_VIOLATION))) + .Times(1); + + EXPECT_CALL(api, WriteDump(ExceptionCodeIs(STATUS_ILLEGAL_INSTRUCTION))) + .Times(1); + + CatchThis(); + EXPECT_EQ(2, veh.get_exceptions_seen()); + + // Not detected since we are not on the stack. + Undetectable(STATUS_INTEGER_DIVIDE_BY_ZERO); + EXPECT_EQ(3, veh.get_exceptions_seen()); + + ::RemoveVectoredExceptionHandler(id); + g_mock_veh = NULL; +} + + diff --git a/chrome_frame/crash_reporting/veh_test.h b/chrome_frame/crash_reporting/veh_test.h new file mode 100644 index 0000000..3f9cd45 --- /dev/null +++ b/chrome_frame/crash_reporting/veh_test.h @@ -0,0 +1,80 @@ +// Copyright (c) 2010 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_VEH_TEST_H_ +#define CHROME_FRAME_CRASH_REPORTING_VEH_TEST_H_ + +#include <windows.h> +#include "base/logging.h" + +#ifndef EXCEPTION_CHAIN_END +#define EXCEPTION_CHAIN_END ((struct _EXCEPTION_REGISTRATION_RECORD*)-1) +typedef struct _EXCEPTION_REGISTRATION_RECORD { + struct _EXCEPTION_REGISTRATION_RECORD* Next; + PVOID Handler; +} EXCEPTION_REGISTRATION_RECORD; +#endif // EXCEPTION_CHAIN_END + +class ExceptionInfo : public _EXCEPTION_POINTERS { + public: + ExceptionInfo() { + Clear(); + } + + ExceptionInfo(DWORD code, void* address) { + Clear(); + Set(code, address, 0); + } + + void Set(DWORD code, void* address, DWORD flags) { + er_.ExceptionCode = code; + er_.ExceptionAddress = address; + er_.ExceptionFlags = flags; + ctx_.Eip = reinterpret_cast<DWORD>(address); + } + + EXCEPTION_RECORD er_; + CONTEXT ctx_; + private: + void Clear() { + ExceptionRecord = &er_; + ContextRecord = &ctx_; + ZeroMemory(&er_, sizeof(er_)); + ZeroMemory(&ctx_, sizeof(ctx_)); + } +}; + +struct SEHChain { + SEHChain(const void* p, ...) { + va_list vl; + va_start(vl, p); + int i = 0; + for (; p; ++i) { + CHECK(i + 1 < arraysize(chain_)); + chain_[i].Handler = const_cast<void*>(p); + chain_[i].Next = &chain_[i + 1]; + p = va_arg(vl, const void*); + } + + chain_[i].Next = EXCEPTION_CHAIN_END; + } + + EXCEPTION_REGISTRATION_RECORD chain_[25]; +}; + +struct StackHelper { + StackHelper(const void* p, ...) { + va_list vl; + va_start(vl, p); + count_ = 0; + for (; p; ++count_) { + CHECK(count_ < arraysize(stack_)); + stack_[count_] = p; + p = va_arg(vl, const void*); + } + } + const void* stack_[64]; + WORD count_; +}; + +#endif // CHROME_FRAME_CRASH_REPORTING_VEH_TEST_H_
\ No newline at end of file |