diff options
Diffstat (limited to 'src/client/ios')
-rw-r--r-- | src/client/ios/Breakpad.xcodeproj/project.pbxproj | 8 | ||||
-rw-r--r-- | src/client/ios/BreakpadController.h | 106 | ||||
-rw-r--r-- | src/client/ios/BreakpadController.mm | 266 | ||||
-rw-r--r-- | src/client/ios/handler/ios_exception_minidump_generator.mm | 3 |
4 files changed, 382 insertions, 1 deletions
diff --git a/src/client/ios/Breakpad.xcodeproj/project.pbxproj b/src/client/ios/Breakpad.xcodeproj/project.pbxproj index 5baadf42..b9dd76f3 100644 --- a/src/client/ios/Breakpad.xcodeproj/project.pbxproj +++ b/src/client/ios/Breakpad.xcodeproj/project.pbxproj @@ -52,6 +52,8 @@ 16C7CE94147D4A4300776EAD /* md5.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CCA5147D4A4300776EAD /* md5.h */; }; 16C7CEA7147D4A4300776EAD /* string_conversion.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CCB9147D4A4300776EAD /* string_conversion.cc */; }; 16C7CEA8147D4A4300776EAD /* string_conversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CCBA147D4A4300776EAD /* string_conversion.h */; }; + 16C92FAD150DF8330053D7BA /* BreakpadController.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C92FAB150DF8330053D7BA /* BreakpadController.h */; }; + 16C92FAE150DF8330053D7BA /* BreakpadController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 16C92FAC150DF8330053D7BA /* BreakpadController.mm */; }; AA747D9F0F9514B9006C5449 /* Breakpad_Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = AA747D9E0F9514B9006C5449 /* Breakpad_Prefix.pch */; }; AACBBE4A0F95108600F1A2B1 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AACBBE490F95108600F1A2B1 /* Foundation.framework */; }; /* End PBXBuildFile section */ @@ -102,6 +104,8 @@ 16C7CCA5147D4A4300776EAD /* md5.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = md5.h; sourceTree = "<group>"; }; 16C7CCB9147D4A4300776EAD /* string_conversion.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = string_conversion.cc; sourceTree = "<group>"; }; 16C7CCBA147D4A4300776EAD /* string_conversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = string_conversion.h; sourceTree = "<group>"; }; + 16C92FAB150DF8330053D7BA /* BreakpadController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BreakpadController.h; sourceTree = "<group>"; }; + 16C92FAC150DF8330053D7BA /* BreakpadController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = BreakpadController.mm; sourceTree = "<group>"; }; AA747D9E0F9514B9006C5449 /* Breakpad_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Breakpad_Prefix.pch; sourceTree = SOURCE_ROOT; }; AACBBE490F95108600F1A2B1 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; D2AAC07E0554694100DB518D /* libBreakpad.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libBreakpad.a; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -198,6 +202,8 @@ 16C7C969147D4A4200776EAD /* ios */ = { isa = PBXGroup; children = ( + 16C92FAB150DF8330053D7BA /* BreakpadController.h */, + 16C92FAC150DF8330053D7BA /* BreakpadController.mm */, 16BFA66A14E195E9009704F8 /* handler */, 16C7C96A147D4A4200776EAD /* Breakpad.h */, 16C7C96B147D4A4200776EAD /* Breakpad.mm */, @@ -327,6 +333,7 @@ 16C7CE94147D4A4300776EAD /* md5.h in Headers */, 16C7CEA8147D4A4300776EAD /* string_conversion.h in Headers */, 16BFA67014E195E9009704F8 /* ios_exception_minidump_generator.h in Headers */, + 16C92FAD150DF8330053D7BA /* BreakpadController.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -413,6 +420,7 @@ 16C7CE93147D4A4300776EAD /* md5.cc in Sources */, 16C7CEA7147D4A4300776EAD /* string_conversion.cc in Sources */, 16BFA67214E1965A009704F8 /* ios_exception_minidump_generator.mm in Sources */, + 16C92FAE150DF8330053D7BA /* BreakpadController.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/src/client/ios/BreakpadController.h b/src/client/ios/BreakpadController.h new file mode 100644 index 00000000..6eb826b3 --- /dev/null +++ b/src/client/ios/BreakpadController.h @@ -0,0 +1,106 @@ +// 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. + +#ifndef CLIENT_IOS_HANDLER_IOS_BREAKPAD_CONTROLLER_H_ +#define CLIENT_IOS_HANDLER_IOS_BREAKPAD_CONTROLLER_H_ + +#import <Foundation/Foundation.h> + +#import "client/ios/Breakpad.h" + +// This class is used to offer a higher level API around BreakpadRef. It +// configures it, ensures thread-safety, and sends crash reports back to the +// collecting server. By default, no crash reports are sent, the user must call +// |setUploadingEnabled:YES| to start the uploading. +@interface BreakpadController : NSObject { + @private + // The dispatch queue that will own the breakpad reference. + dispatch_queue_t queue_; + + // Instance of Breakpad crash reporter. This is owned by the queue, but can + // be created on the main thread at startup. + BreakpadRef breakpadRef_; + + // The dictionary that contains configuration for breakpad. Modifying it + // should only happen when the controller is not started. The initial value + // is the infoDictionary of the bundle of the application. + NSMutableDictionary* configuration_; + + // Whether or not crash reports should be uploaded. + BOOL enableUploads_; + + // The interval to wait between two uploads. Value is 0 if no upload must be + // done. + int uploadIntervalInSeconds_; +} + +// Singleton. ++ (BreakpadController*)sharedInstance; + +// Update the controller configuration. Merges its old configuration with the +// new one. Merge is done by replacing the old values by the new values. +- (void)updateConfiguration:(NSDictionary*)configuration; + +// Configure the URL to upload the report to. This must be called at least once +// if the URL is not in the bundle information. +- (void)setUploadingURL:(NSString*)url; + +// Set the minimal interval between two uploads in seconds. This must be called +// at least once if the interval is not in the bundle information. A value of 0 +// will prevent uploads. +- (void)setUploadInterval:(int)intervalInSeconds; + +// Specify a parameter that will be uploaded to the crash server. See +// |BreakpadAddUploadParameter|. +- (void)addUploadParameter:(NSString*)value forKey:(NSString*)key; + +// Remove a previously-added parameter from the upload parameter set. See +// |BreakpadRemoveUploadParameter|. +- (void)removeUploadParameterForKey:(NSString*)key; + +// Access the underlying BreakpadRef. This method is asynchronous, and will be +// executed on the thread owning the BreakpadRef variable. Moreover, if the +// controller is not started, the block will be called with a NULL parameter. +- (void)withBreakpadRef:(void(^)(BreakpadRef))callback; + +// Starts the BreakpadController by registering crash handlers. If +// |onCurrentThread| is YES, all setup is done on the current thread, otherwise +// it is done on a private queue. +- (void)start:(BOOL)onCurrentThread; + +// Unregisters the crash handlers. +- (void)stop; + +// Enables or disables uploading of crash reports, but does not stop the +// BreakpadController. +- (void)setUploadingEnabled:(BOOL)enabled; + +@end + +#endif // CLIENT_IOS_HANDLER_IOS_BREAKPAD_CONTROLLER_H_ diff --git a/src/client/ios/BreakpadController.mm b/src/client/ios/BreakpadController.mm new file mode 100644 index 00000000..d0735406 --- /dev/null +++ b/src/client/ios/BreakpadController.mm @@ -0,0 +1,266 @@ +// 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. + +#import "BreakpadController.h" + +#import <UIKit/UIKit.h> +#include <asl.h> +#include <execinfo.h> +#include <signal.h> +#include <unistd.h> +#include <sys/sysctl.h> + +#include <processor/scoped_ptr.h> + +#pragma mark - +#pragma mark Private Methods + +@interface BreakpadController () + +// Init the singleton instance. +- (id)initSingleton; + +// Load a crash report and send it to the server. +- (void)sendStoredCrashReports; + +// Returns when a report can be sent. |-1| means never, |0| means that a report +// can be sent immediately, a positive number is the number of seconds to wait +// before being allowed to upload a report. +- (int)sendDelay; + +// Notifies that a report will be sent, and update the last sending time +// accordingly. +- (void)reportWillBeSent; + +@end + +#pragma mark - +#pragma mark Anonymous namespace + +namespace { + +// The name of the user defaults key for the last submission to the crash +// server. +NSString* const kLastSubmission = @"com.google.Breakpad.LastSubmission"; + +// Returns a NSString describing the current platform. +NSString* GetPlatform() { + // Name of the system call for getting the platform. + static const char kHwMachineSysctlName[] = "hw.machine"; + + NSString* result = nil; + + size_t size = 0; + if (sysctlbyname(kHwMachineSysctlName, NULL, &size, NULL, 0) || size == 0) + return nil; + google_breakpad::scoped_array<char> machine(new char[size]); + if (sysctlbyname(kHwMachineSysctlName, machine.get(), &size, NULL, 0) == 0) + result = [NSString stringWithUTF8String:machine.get()]; + return result; +} + +} // namespace + +#pragma mark - +#pragma mark BreakpadController Implementation + +@implementation BreakpadController + ++ (BreakpadController*)sharedInstance { + @synchronized(self) { + static BreakpadController* sharedInstance_ = + [[BreakpadController alloc] initSingleton]; + return sharedInstance_; + } +} + +- (id)init { + return nil; +} + +- (id)initSingleton { + self = [super init]; + if (self) { + queue_ = dispatch_queue_create("com.google.BreakpadQueue", NULL); + configuration_ = [[[NSBundle mainBundle] infoDictionary] mutableCopy]; + enableUploads_ = NO; + NSString* uploadInterval = + [configuration_ valueForKey:@BREAKPAD_REPORT_INTERVAL]; + [self setUploadInterval:[uploadInterval intValue]]; + } + return self; +} + +// Since this class is a singleton, this method is not expected to be called. +- (void)dealloc { + assert(!breakpadRef_); + dispatch_release(queue_); + [configuration_ release]; + [super dealloc]; +} + +#pragma mark - + +- (void)start:(BOOL)onCurrentThread { + void(^startBlock)() = ^{ + assert(!breakpadRef_); + breakpadRef_ = BreakpadCreate(configuration_); + if (breakpadRef_) { + BreakpadAddUploadParameter(breakpadRef_, @"platform", GetPlatform()); + } + }; + if (onCurrentThread) + startBlock(); + else + dispatch_async(queue_, startBlock); +} + +- (void)stop { + dispatch_sync(queue_, ^{ + if (breakpadRef_) { + BreakpadRelease(breakpadRef_); + breakpadRef_ = NULL; + } + }); +} + +- (void)setUploadingEnabled:(BOOL)enabled { + dispatch_async(queue_, ^{ + if (enabled == enableUploads_) + return; + if (enabled) { + // Set this before calling doSendStoredCrashReport, because that + // calls sendDelay, which in turn checks this flag. + enableUploads_ = YES; + [self sendStoredCrashReports]; + } else { + enableUploads_ = NO; + [NSObject cancelPreviousPerformRequestsWithTarget:self + selector:@selector(sendStoredCrashReports) + object:nil]; + } + }); +} + +- (void)updateConfiguration:(NSDictionary*)configuration { + [configuration_ addEntriesFromDictionary:configuration]; + NSString* uploadInterval = + [configuration_ valueForKey:@BREAKPAD_REPORT_INTERVAL]; + if (uploadInterval) + [self setUploadInterval:[uploadInterval intValue]]; +} + +- (void)setUploadingURL:(NSString*)url { + [configuration_ setValue:url forKey:@BREAKPAD_URL]; +} + +- (void)setUploadInterval:(int)intervalInSeconds { + [configuration_ removeObjectForKey:@BREAKPAD_REPORT_INTERVAL]; + uploadIntervalInSeconds_ = intervalInSeconds; + if (uploadIntervalInSeconds_ < 0) + uploadIntervalInSeconds_ = 0; +} + +- (void)addUploadParameter:(NSString*)value forKey:(NSString*)key { + dispatch_async(queue_, ^{ + if (breakpadRef_) + BreakpadAddUploadParameter(breakpadRef_, key, value); + }); +} + +- (void)removeUploadParameterForKey:(NSString*)key { + dispatch_async(queue_, ^{ + if (breakpadRef_) + BreakpadRemoveUploadParameter(breakpadRef_, key); + }); +} + +- (void)withBreakpadRef:(void(^)(BreakpadRef))callback { + dispatch_async(queue_, ^{ + callback(breakpadRef_); + }); +} + + +#pragma mark - + +- (int)sendDelay { + if (!breakpadRef_ || uploadIntervalInSeconds_ <= 0 || !enableUploads_) + return -1; + + // To prevent overloading the crash server, crashes are not sent than one + // report every |uploadIntervalInSeconds_|. A value in the user defaults is + // used to keep the time of the last upload. + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + NSNumber *lastTimeNum = [userDefaults objectForKey:kLastSubmission]; + NSTimeInterval lastTime = lastTimeNum ? [lastTimeNum floatValue] : 0; + NSTimeInterval spanSeconds = CFAbsoluteTimeGetCurrent() - lastTime; + + if (spanSeconds >= uploadIntervalInSeconds_) + return 0; + return uploadIntervalInSeconds_ - spanSeconds; +} + +- (void)reportWillBeSent { + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + [userDefaults setObject:[NSNumber numberWithDouble:CFAbsoluteTimeGetCurrent()] + forKey:kLastSubmission]; + [userDefaults synchronize]; +} + +- (void)sendStoredCrashReports { + dispatch_async(queue_, ^{ + if (!BreakpadHasCrashReportToUpload(breakpadRef_)) + return; + + int timeToWait = [self sendDelay]; + + // Unable to ever send report. + if (timeToWait == -1) + return; + + // A report can be sent now. + if (timeToWait == 0) { + [self reportWillBeSent]; + BreakpadUploadNextReport(breakpadRef_); + + // If more reports must be sent, make sure this method is called again. + if (BreakpadHasCrashReportToUpload(breakpadRef_)) + timeToWait = uploadIntervalInSeconds_; + } + + // A report must be sent later. + if (timeToWait > 0) + [self performSelector:@selector(sendStoredCrashReports) + withObject:nil + afterDelay:timeToWait]; + }); +} + +@end diff --git a/src/client/ios/handler/ios_exception_minidump_generator.mm b/src/client/ios/handler/ios_exception_minidump_generator.mm index 0796c0ec..cd0fe65b 100644 --- a/src/client/ios/handler/ios_exception_minidump_generator.mm +++ b/src/client/ios/handler/ios_exception_minidump_generator.mm @@ -29,6 +29,7 @@ #include "client/ios/handler/ios_exception_minidump_generator.h" +#include "google_breakpad/common/minidump_exception_mac.h" #include "client/minidump_file_writer-inl.h" #include "processor/scoped_ptr.h" @@ -37,7 +38,7 @@ namespace { const uint32_t kExpectedFinalFp = 4; const uint32_t kExpectedFinalSp = 0; const int kExceptionType = EXC_SOFTWARE; -const int kExceptionCode = 0xDEADBEEF; +const int kExceptionCode = MD_EXCEPTION_CODE_MAC_NS_EXCEPTION; // Append the given 4 bytes value to the sp position of the stack represented // by memory. |