diff --git a/src/client/ios/Breakpad.mm b/src/client/ios/Breakpad.mm index 8e29bd53..efcc35b5 100644 --- a/src/client/ios/Breakpad.mm +++ b/src/client/ios/Breakpad.mm @@ -45,6 +45,7 @@ #import "client/mac/handler/exception_handler.h" #import "client/mac/handler/minidump_generator.h" #import "client/ios/Breakpad.h" +#import "client/ios/handler/ios_exception_minidump_generator.h" #import "client/mac/handler/protected_memory_allocator.h" #import @@ -174,6 +175,12 @@ class Breakpad { bool HandleMinidump(const char *dump_dir, const char *minidump_id); + // NSException handler + static void UncaughtExceptionHandler(NSException *exception); + + // Handle an uncaught NSException. + void HandleUncaughtException(NSException *exception); + // Since ExceptionHandler (w/o namespace) is defined as typedef in OSX's // MachineExceptions.h, we have to explicitly name the handler. google_breakpad::ExceptionHandler *handler_; // The actual handler (STRONG) @@ -181,8 +188,14 @@ class Breakpad { SimpleStringDictionary *config_params_; // Create parameters (STRONG) ConfigFile config_file_; + + // A static reference to the current Breakpad instance. Used for handling + // NSException. + static Breakpad *current_breakpad_; }; +Breakpad *Breakpad::current_breakpad_ = NULL; + #pragma mark - #pragma mark Helper functions @@ -240,12 +253,21 @@ bool Breakpad::HandleMinidumpCallback(const char *dump_dir, return breakpad->HandleMinidump(dump_dir, minidump_id); } +//============================================================================= +void Breakpad::UncaughtExceptionHandler(NSException *exception) { + NSSetUncaughtExceptionHandler(NULL); + if (current_breakpad_) { + current_breakpad_->HandleUncaughtException(exception); + } +} + //============================================================================= #pragma mark - //============================================================================= bool Breakpad::Initialize(NSDictionary *parameters) { // Initialize + current_breakpad_ = this; config_params_ = NULL; handler_ = NULL; @@ -267,11 +289,14 @@ bool Breakpad::Initialize(NSDictionary *parameters) { google_breakpad::ExceptionHandler( config_params_->GetValueForKey(BREAKPAD_DUMP_DIRECTORY), 0, &HandleMinidumpCallback, this, true, 0); + NSSetUncaughtExceptionHandler(&Breakpad::UncaughtExceptionHandler); return true; } //============================================================================= Breakpad::~Breakpad() { + NSSetUncaughtExceptionHandler(NULL); + current_breakpad_ = NULL; // Note that we don't use operator delete() on these pointers, // since they were allocated by ProtectedMemoryAllocator objects. // @@ -499,6 +524,37 @@ bool Breakpad::HandleMinidump(const char *dump_dir, } //============================================================================= +void Breakpad::HandleUncaughtException(NSException *exception) { + // Generate the minidump. + google_breakpad::IosExceptionMinidumpGenerator generator(exception); + const char *minidump_path = + config_params_->GetValueForKey(BREAKPAD_DUMP_DIRECTORY); + std::string minidump_id; + std::string minidump_filename = generator.UniqueNameInDirectory(minidump_path, + &minidump_id); + generator.Write(minidump_filename.c_str()); + + // Copy the config params and our custom parameter. This is necessary for 2 + // reasons: + // 1- config_params_ is protected. + // 2- If the application crash while trying to handle this exception, a usual + // report will be generated. This report must not contain these special + // keys. + SimpleStringDictionary params = *config_params_; + params.SetKeyValue(BREAKPAD_SERVER_PARAMETER_PREFIX "type", "exception"); + params.SetKeyValue(BREAKPAD_SERVER_PARAMETER_PREFIX "exceptionName", + [[exception name] UTF8String]); + params.SetKeyValue(BREAKPAD_SERVER_PARAMETER_PREFIX "exceptionReason", + [[exception reason] UTF8String]); + + // And finally write the config file. + ConfigFile config_file; + config_file.WriteFile(minidump_path, + ¶ms, + minidump_path, + minidump_id.c_str()); +} + //============================================================================= #pragma mark - diff --git a/src/client/ios/Breakpad.xcodeproj/project.pbxproj b/src/client/ios/Breakpad.xcodeproj/project.pbxproj index c91de231..5baadf42 100644 --- a/src/client/ios/Breakpad.xcodeproj/project.pbxproj +++ b/src/client/ios/Breakpad.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 16BFA67014E195E9009704F8 /* ios_exception_minidump_generator.h in Headers */ = {isa = PBXBuildFile; fileRef = 16BFA66E14E195E9009704F8 /* ios_exception_minidump_generator.h */; }; + 16BFA67214E1965A009704F8 /* ios_exception_minidump_generator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 16BFA67114E1965A009704F8 /* ios_exception_minidump_generator.mm */; }; 16C7CCCB147D4A4300776EAD /* BreakpadDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7C968147D4A4200776EAD /* BreakpadDefines.h */; }; 16C7CCCC147D4A4300776EAD /* Breakpad.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7C96A147D4A4200776EAD /* Breakpad.h */; }; 16C7CCCD147D4A4300776EAD /* Breakpad.mm in Sources */ = {isa = PBXBuildFile; fileRef = 16C7C96B147D4A4200776EAD /* Breakpad.mm */; }; @@ -55,6 +57,8 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 16BFA66E14E195E9009704F8 /* ios_exception_minidump_generator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ios_exception_minidump_generator.h; sourceTree = ""; }; + 16BFA67114E1965A009704F8 /* ios_exception_minidump_generator.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ios_exception_minidump_generator.mm; sourceTree = ""; }; 16C7C968147D4A4200776EAD /* BreakpadDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BreakpadDefines.h; sourceTree = ""; }; 16C7C96A147D4A4200776EAD /* Breakpad.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Breakpad.h; sourceTree = ""; }; 16C7C96B147D4A4200776EAD /* Breakpad.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Breakpad.mm; sourceTree = ""; }; @@ -151,6 +155,15 @@ name = Classes; sourceTree = ""; }; + 16BFA66A14E195E9009704F8 /* handler */ = { + isa = PBXGroup; + children = ( + 16BFA67114E1965A009704F8 /* ios_exception_minidump_generator.mm */, + 16BFA66E14E195E9009704F8 /* ios_exception_minidump_generator.h */, + ); + path = handler; + sourceTree = ""; + }; 16C7C965147D4A4200776EAD /* client */ = { isa = PBXGroup; children = ( @@ -185,6 +198,7 @@ 16C7C969147D4A4200776EAD /* ios */ = { isa = PBXGroup; children = ( + 16BFA66A14E195E9009704F8 /* handler */, 16C7C96A147D4A4200776EAD /* Breakpad.h */, 16C7C96B147D4A4200776EAD /* Breakpad.mm */, ); @@ -312,6 +326,7 @@ 16C7CE90147D4A4300776EAD /* string_utilities.h in Headers */, 16C7CE94147D4A4300776EAD /* md5.h in Headers */, 16C7CEA8147D4A4300776EAD /* string_conversion.h in Headers */, + 16BFA67014E195E9009704F8 /* ios_exception_minidump_generator.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -397,6 +412,7 @@ 16C7CE8F147D4A4300776EAD /* string_utilities.cc in Sources */, 16C7CE93147D4A4300776EAD /* md5.cc in Sources */, 16C7CEA7147D4A4300776EAD /* string_conversion.cc in Sources */, + 16BFA67214E1965A009704F8 /* ios_exception_minidump_generator.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -415,7 +431,6 @@ "\"$(SRCROOT)/../mac/build/Debug\"", ); GCC_DYNAMIC_NO_PIC = NO; - GCC_ENABLE_FIX_AND_CONTINUE = YES; GCC_MODEL_TUNING = G5; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; @@ -479,7 +494,6 @@ ../../common/mac, ); OTHER_LDFLAGS = "-ObjC"; - PREBINDING = NO; SDKROOT = iphoneos; }; name = Debug; @@ -497,7 +511,6 @@ ../../common/mac, ); OTHER_LDFLAGS = "-ObjC"; - PREBINDING = NO; SDKROOT = iphoneos; }; name = Release; diff --git a/src/client/ios/handler/ios_exception_minidump_generator.h b/src/client/ios/handler/ios_exception_minidump_generator.h new file mode 100644 index 00000000..59f24819 --- /dev/null +++ b/src/client/ios/handler/ios_exception_minidump_generator.h @@ -0,0 +1,64 @@ +// Copyright (c) 2012, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// ios_exception_minidump_generator.h: Create a fake minidump from a +// NSException. + +#ifndef CLIENT_IOS_HANDLER_IOS_EXCEPTION_MINIDUMP_GENERATOR_H_ +#define CLIENT_IOS_HANDLER_IOS_EXCEPTION_MINIDUMP_GENERATOR_H_ + +#include + +#include "client/mac/handler/minidump_generator.h" + +namespace google_breakpad { + +class IosExceptionMinidumpGenerator : public MinidumpGenerator { + public: + explicit IosExceptionMinidumpGenerator(NSException *exception); + virtual ~IosExceptionMinidumpGenerator(); + + protected: + virtual bool WriteExceptionStream(MDRawDirectory *exception_stream); + virtual bool WriteThreadStream(mach_port_t thread_id, MDRawThread *thread); + + private: + + // Get the crashing program counter from the exception. + uint32_t GetPCFromException(); + + // Write a virtual thread context for the crashing site. + bool WriteCrashingContext(MDLocationDescriptor *register_location); + + NSArray *return_addresses_; +}; + +} // namespace google_breakpad + +#endif // CLIENT_IOS_HANDLER_IOS_EXCEPTION_MINIDUMP_GENERATOR_H_ diff --git a/src/client/ios/handler/ios_exception_minidump_generator.mm b/src/client/ios/handler/ios_exception_minidump_generator.mm new file mode 100644 index 00000000..6b8b1064 --- /dev/null +++ b/src/client/ios/handler/ios_exception_minidump_generator.mm @@ -0,0 +1,164 @@ +// Copyright (c) 2012, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "client/ios/handler/ios_exception_minidump_generator.h" + +#include "processor/scoped_ptr.h" + +namespace { + +const uint32_t kExpectedFinalFp = 4; +const uint32_t kExpectedFinalSp = 0; +const int kExceptionType = EXC_SOFTWARE; +const int kExceptionCode = 0xDEADBEEF; + +// Append the given 4 bytes value to the sp position of the stack represented +// by memory. +void AppendToMemory(uint8_t *memory, uint32_t sp, uint32_t data) { + assert(sizeof(data) == 4); + memcpy(memory + sp, &data, sizeof(data)); +} + +} // namespace + +namespace google_breakpad { + +IosExceptionMinidumpGenerator::IosExceptionMinidumpGenerator( + NSException *exception) + : MinidumpGenerator(mach_task_self(), 0) { + return_addresses_ = [[exception callStackReturnAddresses] retain]; + SetExceptionInformation(kExceptionType, + kExceptionCode, + 0, + pthread_mach_thread_np(pthread_self())); +} + +IosExceptionMinidumpGenerator::~IosExceptionMinidumpGenerator() { + [return_addresses_ release]; +} + +bool IosExceptionMinidumpGenerator::WriteCrashingContext( + MDLocationDescriptor *register_location) { +#ifdef HAS_ARM_SUPPORT + TypedMDRVA context(&writer_); + if (!context.Allocate()) + return false; + *register_location = context.location(); + MDRawContextARM *context_ptr = context.get(); + memset(context_ptr, 0, sizeof(MDRawContextARM)); + context_ptr->context_flags = MD_CONTEXT_ARM_FULL; + context_ptr->iregs[7] = kExpectedFinalFp; // FP + context_ptr->iregs[13] = kExpectedFinalSp; // SP + uint32_t pc = GetPCFromException(); + context_ptr->iregs[14] = pc; // LR + context_ptr->iregs[15] = pc; // PC + return true; +#else + assert(false); + return false; +#endif +} + +uint32_t IosExceptionMinidumpGenerator::GetPCFromException() { + return [[return_addresses_ objectAtIndex:0] unsignedIntegerValue]; +} + +bool IosExceptionMinidumpGenerator::WriteExceptionStream( + MDRawDirectory *exception_stream) { +#ifdef HAS_ARM_SUPPORT + TypedMDRVA exception(&writer_); + + if (!exception.Allocate()) + return false; + + exception_stream->stream_type = MD_EXCEPTION_STREAM; + exception_stream->location = exception.location(); + MDRawExceptionStream *exception_ptr = exception.get(); + exception_ptr->thread_id = pthread_mach_thread_np(pthread_self()); + + // This naming is confusing, but it is the proper translation from + // mach naming to minidump naming. + exception_ptr->exception_record.exception_code = kExceptionType; + exception_ptr->exception_record.exception_flags = kExceptionCode; + + if (!WriteCrashingContext(&exception_ptr->thread_context)) + return false; + + exception_ptr->exception_record.exception_address = GetPCFromException(); + return true; +#else + return MinidumpGenerator::WriteExceptionStream(exception_stream); +#endif +} + +bool IosExceptionMinidumpGenerator::WriteThreadStream(mach_port_t thread_id, + MDRawThread *thread) { +#ifdef HAS_ARM_SUPPORT + if (pthread_mach_thread_np(pthread_self()) != thread_id) + return MinidumpGenerator::WriteThreadStream(thread_id, thread); + + size_t frame_count = [return_addresses_ count]; + UntypedMDRVA memory(&writer_); + size_t size = 8 * (frame_count - 1) + 4; + if (!memory.Allocate(size)) + return false; + scoped_array stack_memory(new uint8_t[size]); + uint32_t sp = size - 4; + uint32_t fp = 0; + uint32_t lr = [[return_addresses_ lastObject] unsignedIntegerValue]; + for (int current_frame = frame_count - 2; + current_frame >= 0; + --current_frame) { + AppendToMemory(stack_memory.get(), sp, fp); + sp -= 4; + fp = sp; + AppendToMemory(stack_memory.get(), sp, lr); + sp -= 4; + lr = [[return_addresses_ objectAtIndex:current_frame] unsignedIntegerValue]; + } + if (!memory.Copy(stack_memory.get(), size)) + return false; + assert(sp == kExpectedFinalSp); + assert(fp == kExpectedFinalFp); + assert(lr == GetPCFromException()); + thread->stack.start_of_memory_range = sp; + thread->stack.memory = memory.location(); + memory_blocks_.push_back(thread->stack); + + if (!WriteCrashingContext(&thread->thread_context)) + return false; + + thread->thread_id = thread_id; + return true; +#else + return MinidumpGenerator::WriteThreadStream(thread_id, thread); +#endif +} + +} // namespace google_breakpad diff --git a/src/client/mac/handler/minidump_generator.h b/src/client/mac/handler/minidump_generator.h index f5a4d44a..80bb116c 100644 --- a/src/client/mac/handler/minidump_generator.h +++ b/src/client/mac/handler/minidump_generator.h @@ -82,7 +82,7 @@ class MinidumpGenerator { MinidumpGenerator(); MinidumpGenerator(mach_port_t crashing_task, mach_port_t handler_thread); - ~MinidumpGenerator(); + virtual ~MinidumpGenerator(); // Return /.dmp // Sets |unique_name| (if requested) to the unique name for the minidump @@ -106,13 +106,19 @@ class MinidumpGenerator { // the MinidumpGenerator class. static void GatherSystemInformation(); + protected: + // Overridable Stream writers + virtual bool WriteExceptionStream(MDRawDirectory *exception_stream); + + // Overridable Helper + virtual bool WriteThreadStream(mach_port_t thread_id, MDRawThread *thread); + private: - typedef bool (MinidumpGenerator::*WriteStreamFN)(MDRawDirectory *); + typedef bool (MinidumpGenerator::*WriteStreamFN)(MDRawDirectory *); // Stream writers bool WriteThreadListStream(MDRawDirectory *thread_list_stream); bool WriteMemoryListStream(MDRawDirectory *memory_list_stream); - bool WriteExceptionStream(MDRawDirectory *exception_stream); bool WriteSystemInfoStream(MDRawDirectory *system_info_stream); bool WriteModuleListStream(MDRawDirectory *module_list_stream); bool WriteMiscInfoStream(MDRawDirectory *misc_info_stream); @@ -128,7 +134,6 @@ class MinidumpGenerator { MDMemoryDescriptor *stack_location); bool WriteContext(breakpad_thread_state_data_t state, MDLocationDescriptor *register_location); - bool WriteThreadStream(mach_port_t thread_id, MDRawThread *thread); bool WriteCVRecord(MDRawModule *module, int cpu_type, const char *module_path, bool in_memory); bool WriteModuleStream(unsigned int index, MDRawModule *module); @@ -172,9 +177,11 @@ class MinidumpGenerator { explicit MinidumpGenerator(const MinidumpGenerator &); void operator=(const MinidumpGenerator &); + protected: // Use this writer to put the data to disk MinidumpFileWriter writer_; + private: // Exception information int exception_type_; int exception_code_; @@ -199,6 +206,7 @@ class MinidumpGenerator { // directly from the system, even while handling an exception. mutable PageAllocator allocator_; + protected: // Blocks of memory written to the dump. These are all currently // written while writing the thread list stream, but saved here // so a memory list stream can be written afterwards.