|  | // Copyright (c) 2011, 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. | 
|  |  | 
|  | #define IGNORE_DEBUGGER "BREAKPAD_IGNORE_DEBUGGER" | 
|  |  | 
|  | #import "client/ios/Breakpad.h" | 
|  |  | 
|  | #include <assert.h> | 
|  | #import <Foundation/Foundation.h> | 
|  | #include <pthread.h> | 
|  | #include <sys/stat.h> | 
|  | #include <sys/sysctl.h> | 
|  | #include <TargetConditionals.h> | 
|  |  | 
|  | #include <string> | 
|  |  | 
|  | #import "client/ios/handler/ios_exception_minidump_generator.h" | 
|  | #import "client/mac/crash_generation/ConfigFile.h" | 
|  | #import "client/mac/handler/minidump_generator.h" | 
|  | #import "client/mac/handler/protected_memory_allocator.h" | 
|  | #import "client/mac/sender/uploader.h" | 
|  | #import "common/long_string_dictionary.h" | 
|  |  | 
|  | #if !TARGET_OS_TV && !TARGET_OS_WATCH | 
|  | #import "client/mac/handler/exception_handler.h" | 
|  | #else | 
|  | #import "client/ios/exception_handler_no_mach.h" | 
|  | #endif  // !TARGET_OS_TV && !TARGET_OS_WATCH | 
|  |  | 
|  | #if !defined(__EXCEPTIONS) || (__clang__ && !__has_feature(cxx_exceptions)) | 
|  | // This file uses C++ try/catch (but shouldn't). Duplicate the macros from | 
|  | // <c++/4.2.1/exception_defines.h> allowing this file to work properly with | 
|  | // exceptions disabled even when other C++ libraries are used. #undef the try | 
|  | // and catch macros first in case libstdc++ is in use and has already provided | 
|  | // its own definitions. | 
|  | #undef try | 
|  | #define try       if (true) | 
|  | #undef catch | 
|  | #define catch(X)  if (false) | 
|  | #endif  // __EXCEPTIONS | 
|  |  | 
|  | using google_breakpad::ConfigFile; | 
|  | using google_breakpad::EnsureDirectoryPathExists; | 
|  | using google_breakpad::LongStringDictionary; | 
|  |  | 
|  | //============================================================================= | 
|  | // We want any memory allocations which are used by breakpad during the | 
|  | // exception handling process (after a crash has happened) to be read-only | 
|  | // to prevent them from being smashed before a crash occurs.  Unfortunately | 
|  | // we cannot protect against smashes to our exception handling thread's | 
|  | // stack. | 
|  | // | 
|  | // NOTE: Any memory allocations which are not used during the exception | 
|  | // handling process may be allocated in the normal ways. | 
|  | // | 
|  | // The ProtectedMemoryAllocator class provides an Allocate() method which | 
|  | // we'll using in conjunction with placement operator new() to control | 
|  | // allocation of C++ objects.  Note that we don't use operator delete() | 
|  | // but instead call the objects destructor directly:  object->~ClassName(); | 
|  | // | 
|  | ProtectedMemoryAllocator *gMasterAllocator = NULL; | 
|  | ProtectedMemoryAllocator *gKeyValueAllocator = NULL; | 
|  | ProtectedMemoryAllocator *gBreakpadAllocator = NULL; | 
|  |  | 
|  | // Mutex for thread-safe access to the key/value dictionary used by breakpad. | 
|  | // It's a global instead of an instance variable of Breakpad | 
|  | // since it can't live in a protected memory area. | 
|  | pthread_mutex_t gDictionaryMutex; | 
|  |  | 
|  | //============================================================================= | 
|  | // Stack-based object for thread-safe access to a memory-protected region. | 
|  | // It's assumed that normally the memory block (allocated by the allocator) | 
|  | // is protected (read-only).  Creating a stack-based instance of | 
|  | // ProtectedMemoryLocker will unprotect this block after taking the lock. | 
|  | // Its destructor will first re-protect the memory then release the lock. | 
|  | class ProtectedMemoryLocker { | 
|  | public: | 
|  | ProtectedMemoryLocker(pthread_mutex_t *mutex, | 
|  | ProtectedMemoryAllocator *allocator) | 
|  | : mutex_(mutex), | 
|  | allocator_(allocator) { | 
|  | // Lock the mutex | 
|  | __attribute__((unused)) int rv = pthread_mutex_lock(mutex_); | 
|  | assert(rv == 0); | 
|  |  | 
|  | // Unprotect the memory | 
|  | allocator_->Unprotect(); | 
|  | } | 
|  |  | 
|  | ~ProtectedMemoryLocker() { | 
|  | // First protect the memory | 
|  | allocator_->Protect(); | 
|  |  | 
|  | // Then unlock the mutex | 
|  | __attribute__((unused)) int rv = pthread_mutex_unlock(mutex_); | 
|  | assert(rv == 0); | 
|  | } | 
|  |  | 
|  | private: | 
|  | ProtectedMemoryLocker(); | 
|  | ProtectedMemoryLocker(const ProtectedMemoryLocker&); | 
|  | ProtectedMemoryLocker& operator=(const ProtectedMemoryLocker&); | 
|  |  | 
|  | pthread_mutex_t           *mutex_; | 
|  | ProtectedMemoryAllocator  *allocator_; | 
|  | }; | 
|  |  | 
|  | //============================================================================= | 
|  | class Breakpad { | 
|  | public: | 
|  | // factory method | 
|  | static Breakpad *Create(NSDictionary *parameters) { | 
|  | // Allocate from our special allocation pool | 
|  | Breakpad *breakpad = | 
|  | new (gBreakpadAllocator->Allocate(sizeof(Breakpad))) | 
|  | Breakpad(); | 
|  |  | 
|  | if (!breakpad) | 
|  | return NULL; | 
|  |  | 
|  | if (!breakpad->Initialize(parameters)) { | 
|  | // Don't use operator delete() here since we allocated from special pool | 
|  | breakpad->~Breakpad(); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | return breakpad; | 
|  | } | 
|  |  | 
|  | ~Breakpad(); | 
|  |  | 
|  | void SetKeyValue(NSString *key, NSString *value); | 
|  | NSString *KeyValue(NSString *key); | 
|  | void RemoveKeyValue(NSString *key); | 
|  | NSArray *CrashReportsToUpload(); | 
|  | NSString *NextCrashReportToUpload(); | 
|  | NSDictionary *NextCrashReportConfiguration(); | 
|  | NSDate *DateOfMostRecentCrashReport(); | 
|  | void UploadNextReport(NSDictionary *server_parameters); | 
|  | void UploadReportWithConfiguration(NSDictionary *configuration, | 
|  | NSDictionary *server_parameters); | 
|  | void UploadData(NSData *data, NSString *name, | 
|  | NSDictionary *server_parameters); | 
|  | void HandleNetworkResponse(NSDictionary *configuration, | 
|  | NSData *data, | 
|  | NSError *error); | 
|  | NSDictionary *GenerateReport(NSDictionary *server_parameters); | 
|  |  | 
|  | private: | 
|  | Breakpad() | 
|  | : handler_(NULL), | 
|  | config_params_(NULL) {} | 
|  |  | 
|  | bool Initialize(NSDictionary *parameters); | 
|  |  | 
|  | bool ExtractParameters(NSDictionary *parameters); | 
|  |  | 
|  | // Dispatches to HandleMinidump() | 
|  | static bool HandleMinidumpCallback(const char *dump_dir, | 
|  | const char *minidump_id, | 
|  | void *context, bool succeeded); | 
|  |  | 
|  | 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) | 
|  |  | 
|  | LongStringDictionary *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 | 
|  |  | 
|  | //============================================================================= | 
|  | // Helper functions | 
|  |  | 
|  | //============================================================================= | 
|  | static BOOL IsDebuggerActive() { | 
|  | BOOL result = NO; | 
|  | NSUserDefaults *stdDefaults = [NSUserDefaults standardUserDefaults]; | 
|  |  | 
|  | // We check both defaults and the environment variable here | 
|  |  | 
|  | BOOL ignoreDebugger = [stdDefaults boolForKey:@IGNORE_DEBUGGER]; | 
|  |  | 
|  | if (!ignoreDebugger) { | 
|  | char *ignoreDebuggerStr = getenv(IGNORE_DEBUGGER); | 
|  | ignoreDebugger = | 
|  | (ignoreDebuggerStr ? strtol(ignoreDebuggerStr, NULL, 10) : 0) != 0; | 
|  | } | 
|  |  | 
|  | if (!ignoreDebugger) { | 
|  | pid_t pid = getpid(); | 
|  | int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}; | 
|  | int mibSize = sizeof(mib) / sizeof(int); | 
|  | size_t actualSize; | 
|  |  | 
|  | if (sysctl(mib, mibSize, NULL, &actualSize, NULL, 0) == 0) { | 
|  | struct kinfo_proc *info = (struct kinfo_proc *)malloc(actualSize); | 
|  |  | 
|  | if (info) { | 
|  | // This comes from looking at the Darwin xnu Kernel | 
|  | if (sysctl(mib, mibSize, info, &actualSize, NULL, 0) == 0) | 
|  | result = (info->kp_proc.p_flag & P_TRACED) ? YES : NO; | 
|  |  | 
|  | free(info); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | bool Breakpad::HandleMinidumpCallback(const char *dump_dir, | 
|  | const char *minidump_id, | 
|  | void *context, bool succeeded) { | 
|  | Breakpad *breakpad = (Breakpad *)context; | 
|  |  | 
|  | // If our context is damaged or something, just return false to indicate that | 
|  | // the handler should continue without us. | 
|  | if (!breakpad || !succeeded) | 
|  | return false; | 
|  |  | 
|  | return breakpad->HandleMinidump(dump_dir, minidump_id); | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | void Breakpad::UncaughtExceptionHandler(NSException *exception) { | 
|  | NSSetUncaughtExceptionHandler(NULL); | 
|  | if (current_breakpad_) { | 
|  | current_breakpad_->HandleUncaughtException(exception); | 
|  | BreakpadRelease(current_breakpad_); | 
|  | } | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | #pragma mark - | 
|  |  | 
|  | //============================================================================= | 
|  | bool Breakpad::Initialize(NSDictionary *parameters) { | 
|  | // Initialize | 
|  | current_breakpad_ = this; | 
|  | config_params_ = NULL; | 
|  | handler_ = NULL; | 
|  |  | 
|  | // Gather any user specified parameters | 
|  | if (!ExtractParameters(parameters)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Check for debugger | 
|  | if (IsDebuggerActive()) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Create the handler (allocating it in our special protected pool) | 
|  | handler_ = | 
|  | new (gBreakpadAllocator->Allocate( | 
|  | sizeof(google_breakpad::ExceptionHandler))) | 
|  | 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. | 
|  | // | 
|  | if (config_params_) { | 
|  | config_params_->~LongStringDictionary(); | 
|  | } | 
|  |  | 
|  | if (handler_) | 
|  | handler_->~ExceptionHandler(); | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | bool Breakpad::ExtractParameters(NSDictionary *parameters) { | 
|  | NSString *serverType = [parameters objectForKey:@BREAKPAD_SERVER_TYPE]; | 
|  | NSString *display = [parameters objectForKey:@BREAKPAD_PRODUCT_DISPLAY]; | 
|  | NSString *product = [parameters objectForKey:@BREAKPAD_PRODUCT]; | 
|  | NSString *version = [parameters objectForKey:@BREAKPAD_VERSION]; | 
|  | NSString *urlStr = [parameters objectForKey:@BREAKPAD_URL]; | 
|  | NSString *vendor = | 
|  | [parameters objectForKey:@BREAKPAD_VENDOR]; | 
|  | // We check both parameters and the environment variable here. | 
|  | char *envVarDumpSubdirectory = getenv(BREAKPAD_DUMP_DIRECTORY); | 
|  | NSString *dumpSubdirectory = envVarDumpSubdirectory ? | 
|  | [NSString stringWithUTF8String:envVarDumpSubdirectory] : | 
|  | [parameters objectForKey:@BREAKPAD_DUMP_DIRECTORY]; | 
|  |  | 
|  | NSDictionary *serverParameters = | 
|  | [parameters objectForKey:@BREAKPAD_SERVER_PARAMETER_DICT]; | 
|  |  | 
|  | if (!product) | 
|  | product = [parameters objectForKey:@"CFBundleName"]; | 
|  |  | 
|  | if (!display) { | 
|  | display = [parameters objectForKey:@"CFBundleDisplayName"]; | 
|  | if (!display) { | 
|  | display = product; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!version.length)  // Default nil or empty string to CFBundleVersion | 
|  | version = [parameters objectForKey:@"CFBundleVersion"]; | 
|  |  | 
|  | if (!vendor) { | 
|  | vendor = @"Vendor not specified"; | 
|  | } | 
|  |  | 
|  | if (!dumpSubdirectory) { | 
|  | NSString *cachePath = | 
|  | [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, | 
|  | NSUserDomainMask, | 
|  | YES) | 
|  | objectAtIndex:0]; | 
|  | dumpSubdirectory = | 
|  | [cachePath stringByAppendingPathComponent:@kDefaultLibrarySubdirectory]; | 
|  |  | 
|  | EnsureDirectoryPathExists(dumpSubdirectory); | 
|  | } | 
|  |  | 
|  | // The product, version, and URL are required values. | 
|  | if (![product length]) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (![version length]) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (![urlStr length]) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | config_params_ = | 
|  | new (gKeyValueAllocator->Allocate(sizeof(LongStringDictionary))) | 
|  | LongStringDictionary(); | 
|  |  | 
|  | LongStringDictionary &dictionary = *config_params_; | 
|  |  | 
|  | dictionary.SetKeyValue(BREAKPAD_SERVER_TYPE,     [serverType UTF8String]); | 
|  | dictionary.SetKeyValue(BREAKPAD_PRODUCT_DISPLAY, [display UTF8String]); | 
|  | dictionary.SetKeyValue(BREAKPAD_PRODUCT,         [product UTF8String]); | 
|  | dictionary.SetKeyValue(BREAKPAD_VERSION,         [version UTF8String]); | 
|  | dictionary.SetKeyValue(BREAKPAD_URL,             [urlStr UTF8String]); | 
|  | dictionary.SetKeyValue(BREAKPAD_VENDOR,          [vendor UTF8String]); | 
|  | dictionary.SetKeyValue(BREAKPAD_DUMP_DIRECTORY, | 
|  | [dumpSubdirectory UTF8String]); | 
|  |  | 
|  | struct timeval tv; | 
|  | gettimeofday(&tv, NULL); | 
|  | char timeStartedString[32]; | 
|  | sprintf(timeStartedString, "%zd", tv.tv_sec); | 
|  | dictionary.SetKeyValue(BREAKPAD_PROCESS_START_TIME, timeStartedString); | 
|  |  | 
|  | if (serverParameters) { | 
|  | // For each key-value pair, call BreakpadAddUploadParameter() | 
|  | NSEnumerator *keyEnumerator = [serverParameters keyEnumerator]; | 
|  | NSString *aParameter; | 
|  | while ((aParameter = [keyEnumerator nextObject])) { | 
|  | BreakpadAddUploadParameter(this, aParameter, | 
|  | [serverParameters objectForKey:aParameter]); | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | void Breakpad::SetKeyValue(NSString *key, NSString *value) { | 
|  | // We allow nil values. This is the same as removing the keyvalue. | 
|  | if (!config_params_ || !key) | 
|  | return; | 
|  |  | 
|  | config_params_->SetKeyValue([key UTF8String], [value UTF8String]); | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | NSString *Breakpad::KeyValue(NSString *key) { | 
|  | if (!config_params_ || !key) | 
|  | return nil; | 
|  |  | 
|  | const std::string value = config_params_->GetValueForKey([key UTF8String]); | 
|  | return value.empty() ? nil : [NSString stringWithUTF8String:value.c_str()]; | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | void Breakpad::RemoveKeyValue(NSString *key) { | 
|  | if (!config_params_ || !key) return; | 
|  |  | 
|  | config_params_->RemoveKey([key UTF8String]); | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | NSArray *Breakpad::CrashReportsToUpload() { | 
|  | NSString *directory = KeyValue(@BREAKPAD_DUMP_DIRECTORY); | 
|  | if (!directory) | 
|  | return nil; | 
|  | NSArray *dirContents = [[NSFileManager defaultManager] | 
|  | contentsOfDirectoryAtPath:directory error:nil]; | 
|  | NSArray *configs = [dirContents filteredArrayUsingPredicate:[NSPredicate | 
|  | predicateWithFormat:@"self BEGINSWITH 'Config-'"]]; | 
|  | return configs; | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | NSString *Breakpad::NextCrashReportToUpload() { | 
|  | NSString *directory = KeyValue(@BREAKPAD_DUMP_DIRECTORY); | 
|  | if (!directory) | 
|  | return nil; | 
|  | NSString *config = [CrashReportsToUpload() lastObject]; | 
|  | if (!config) | 
|  | return nil; | 
|  | return [NSString stringWithFormat:@"%@/%@", directory, config]; | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | NSDictionary *Breakpad::NextCrashReportConfiguration() { | 
|  | return [Uploader readConfigurationDataFromFile:NextCrashReportToUpload()]; | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | NSDate *Breakpad::DateOfMostRecentCrashReport() { | 
|  | NSString *directory = KeyValue(@BREAKPAD_DUMP_DIRECTORY); | 
|  | if (!directory) { | 
|  | return nil; | 
|  | } | 
|  | NSFileManager *fileManager = [NSFileManager defaultManager]; | 
|  | NSArray *dirContents = [fileManager contentsOfDirectoryAtPath:directory error:nil]; | 
|  | NSArray *dumps = [dirContents filteredArrayUsingPredicate:[NSPredicate | 
|  | predicateWithFormat:@"self ENDSWITH '.dmp'"]]; | 
|  | NSDate *mostRecentCrashReportDate = nil; | 
|  | for (NSString *dump in dumps) { | 
|  | NSString *filePath = [directory stringByAppendingPathComponent:dump]; | 
|  | NSDate *crashReportDate = | 
|  | [[fileManager attributesOfItemAtPath:filePath error:nil] fileCreationDate]; | 
|  | if (!mostRecentCrashReportDate) { | 
|  | mostRecentCrashReportDate = crashReportDate; | 
|  | } else if (crashReportDate) { | 
|  | mostRecentCrashReportDate = [mostRecentCrashReportDate laterDate:crashReportDate]; | 
|  | } | 
|  | } | 
|  | return mostRecentCrashReportDate; | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | void Breakpad::HandleNetworkResponse(NSDictionary *configuration, | 
|  | NSData *data, | 
|  | NSError *error) { | 
|  | Uploader *uploader = [[[Uploader alloc] | 
|  | initWithConfig:configuration] autorelease]; | 
|  | [uploader handleNetworkResponse:data withError:error]; | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | void Breakpad::UploadReportWithConfiguration(NSDictionary *configuration, | 
|  | NSDictionary *server_parameters) { | 
|  | Uploader *uploader = [[[Uploader alloc] | 
|  | initWithConfig:configuration] autorelease]; | 
|  | if (!uploader) | 
|  | return; | 
|  | for (NSString *key in server_parameters) { | 
|  | [uploader addServerParameter:[server_parameters objectForKey:key] | 
|  | forKey:key]; | 
|  | } | 
|  | [uploader report]; | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | void Breakpad::UploadNextReport(NSDictionary *server_parameters) { | 
|  | NSDictionary *configuration = NextCrashReportConfiguration(); | 
|  | if (configuration) { | 
|  | return UploadReportWithConfiguration(configuration, server_parameters); | 
|  | } | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | void Breakpad::UploadData(NSData *data, NSString *name, | 
|  | NSDictionary *server_parameters) { | 
|  | NSMutableDictionary *config = [NSMutableDictionary dictionary]; | 
|  |  | 
|  | LongStringDictionary::Iterator it(*config_params_); | 
|  | while (const LongStringDictionary::Entry *next = it.Next()) { | 
|  | [config setValue:[NSString stringWithUTF8String:next->value] | 
|  | forKey:[NSString stringWithUTF8String:next->key]]; | 
|  | } | 
|  |  | 
|  | Uploader *uploader = | 
|  | [[[Uploader alloc] initWithConfig:config] autorelease]; | 
|  | for (NSString *key in server_parameters) { | 
|  | [uploader addServerParameter:[server_parameters objectForKey:key] | 
|  | forKey:key]; | 
|  | } | 
|  | [uploader uploadData:data name:name]; | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | NSDictionary *Breakpad::GenerateReport(NSDictionary *server_parameters) { | 
|  | NSString *dumpDirAsNSString = KeyValue(@BREAKPAD_DUMP_DIRECTORY); | 
|  | if (!dumpDirAsNSString) | 
|  | return nil; | 
|  | const char *dumpDir = [dumpDirAsNSString UTF8String]; | 
|  |  | 
|  | google_breakpad::MinidumpGenerator generator(mach_task_self(), | 
|  | MACH_PORT_NULL); | 
|  | std::string dumpId; | 
|  | std::string dumpFilename = generator.UniqueNameInDirectory(dumpDir, &dumpId); | 
|  | bool success = generator.Write(dumpFilename.c_str()); | 
|  | if (!success) | 
|  | return nil; | 
|  |  | 
|  | LongStringDictionary params = *config_params_; | 
|  | for (NSString *key in server_parameters) { | 
|  | params.SetKeyValue([key UTF8String], | 
|  | [[server_parameters objectForKey:key] UTF8String]); | 
|  | } | 
|  | ConfigFile config_file; | 
|  | config_file.WriteFile(dumpDir, ¶ms, dumpDir, dumpId.c_str()); | 
|  |  | 
|  | // Handle results. | 
|  | NSMutableDictionary *result = [NSMutableDictionary dictionary]; | 
|  | NSString *dumpFullPath = [NSString stringWithUTF8String:dumpFilename.c_str()]; | 
|  | [result setValue:dumpFullPath | 
|  | forKey:@BREAKPAD_OUTPUT_DUMP_FILE]; | 
|  | [result setValue:[NSString stringWithUTF8String:config_file.GetFilePath()] | 
|  | forKey:@BREAKPAD_OUTPUT_CONFIG_FILE]; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | bool Breakpad::HandleMinidump(const char *dump_dir, | 
|  | const char *minidump_id) { | 
|  | config_file_.WriteFile(dump_dir, | 
|  | config_params_, | 
|  | dump_dir, | 
|  | minidump_id); | 
|  |  | 
|  | // Return true here to indicate that we've processed things as much as we | 
|  | // want. | 
|  | return true; | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | void Breakpad::HandleUncaughtException(NSException *exception) { | 
|  | // Generate the minidump. | 
|  | google_breakpad::IosExceptionMinidumpGenerator generator(exception); | 
|  | const std::string 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. | 
|  | LongStringDictionary 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.c_str(), | 
|  | ¶ms, | 
|  | minidump_path.c_str(), | 
|  | minidump_id.c_str()); | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  |  | 
|  | #pragma mark - | 
|  | #pragma mark Public API | 
|  |  | 
|  | //============================================================================= | 
|  | BreakpadRef BreakpadCreate(NSDictionary *parameters) { | 
|  | try { | 
|  | // This is confusing.  Our two main allocators for breakpad memory are: | 
|  | //    - gKeyValueAllocator for the key/value memory | 
|  | //    - gBreakpadAllocator for the Breakpad, ExceptionHandler, and other | 
|  | //      breakpad allocations which are accessed at exception handling time. | 
|  | // | 
|  | // But in order to avoid these two allocators themselves from being smashed, | 
|  | // we'll protect them as well by allocating them with gMasterAllocator. | 
|  | // | 
|  | // gMasterAllocator itself will NOT be protected, but this doesn't matter, | 
|  | // since once it does its allocations and locks the memory, smashes to | 
|  | // itself don't affect anything we care about. | 
|  | gMasterAllocator = | 
|  | new ProtectedMemoryAllocator(sizeof(ProtectedMemoryAllocator) * 2); | 
|  |  | 
|  | gKeyValueAllocator = | 
|  | new (gMasterAllocator->Allocate(sizeof(ProtectedMemoryAllocator))) | 
|  | ProtectedMemoryAllocator(sizeof(LongStringDictionary)); | 
|  |  | 
|  | // Create a mutex for use in accessing the LongStringDictionary | 
|  | int mutexResult = pthread_mutex_init(&gDictionaryMutex, NULL); | 
|  | if (mutexResult == 0) { | 
|  |  | 
|  | // With the current compiler, gBreakpadAllocator is allocating 1444 bytes. | 
|  | // Let's round up to the nearest page size. | 
|  | // | 
|  | int breakpad_pool_size = 4096; | 
|  |  | 
|  | /* | 
|  | sizeof(Breakpad) | 
|  | + sizeof(google_breakpad::ExceptionHandler) | 
|  | + sizeof( STUFF ALLOCATED INSIDE ExceptionHandler ) | 
|  | */ | 
|  |  | 
|  | gBreakpadAllocator = | 
|  | new (gMasterAllocator->Allocate(sizeof(ProtectedMemoryAllocator))) | 
|  | ProtectedMemoryAllocator(breakpad_pool_size); | 
|  |  | 
|  | // Stack-based autorelease pool for Breakpad::Create() obj-c code. | 
|  | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; | 
|  | Breakpad *breakpad = Breakpad::Create(parameters); | 
|  |  | 
|  | if (breakpad) { | 
|  | // Make read-only to protect against memory smashers | 
|  | gMasterAllocator->Protect(); | 
|  | gKeyValueAllocator->Protect(); | 
|  | gBreakpadAllocator->Protect(); | 
|  | // Can uncomment this line to figure out how much space was actually | 
|  | // allocated using this allocator | 
|  | //     printf("gBreakpadAllocator allocated size = %d\n", | 
|  | //         gBreakpadAllocator->GetAllocatedSize() ); | 
|  | [pool release]; | 
|  | return (BreakpadRef)breakpad; | 
|  | } | 
|  |  | 
|  | [pool release]; | 
|  | } | 
|  | } catch(...) {    // don't let exceptions leave this C API | 
|  | fprintf(stderr, "BreakpadCreate() : error\n"); | 
|  | } | 
|  |  | 
|  | if (gKeyValueAllocator) { | 
|  | gKeyValueAllocator->~ProtectedMemoryAllocator(); | 
|  | gKeyValueAllocator = NULL; | 
|  | } | 
|  |  | 
|  | if (gBreakpadAllocator) { | 
|  | gBreakpadAllocator->~ProtectedMemoryAllocator(); | 
|  | gBreakpadAllocator = NULL; | 
|  | } | 
|  |  | 
|  | delete gMasterAllocator; | 
|  | gMasterAllocator = NULL; | 
|  |  | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | void BreakpadRelease(BreakpadRef ref) { | 
|  | try { | 
|  | Breakpad *breakpad = (Breakpad *)ref; | 
|  |  | 
|  | if (gMasterAllocator) { | 
|  | gMasterAllocator->Unprotect(); | 
|  | gKeyValueAllocator->Unprotect(); | 
|  | gBreakpadAllocator->Unprotect(); | 
|  |  | 
|  | breakpad->~Breakpad(); | 
|  |  | 
|  | // Unfortunately, it's not possible to deallocate this stuff | 
|  | // because the exception handling thread is still finishing up | 
|  | // asynchronously at this point...  OK, it could be done with | 
|  | // locks, etc.  But since BreakpadRelease() should usually only | 
|  | // be called right before the process exits, it's not worth | 
|  | // deallocating this stuff. | 
|  | #if 0 | 
|  | gKeyValueAllocator->~ProtectedMemoryAllocator(); | 
|  | gBreakpadAllocator->~ProtectedMemoryAllocator(); | 
|  | delete gMasterAllocator; | 
|  |  | 
|  | gMasterAllocator = NULL; | 
|  | gKeyValueAllocator = NULL; | 
|  | gBreakpadAllocator = NULL; | 
|  | #endif | 
|  |  | 
|  | pthread_mutex_destroy(&gDictionaryMutex); | 
|  | } | 
|  | } catch(...) {    // don't let exceptions leave this C API | 
|  | fprintf(stderr, "BreakpadRelease() : error\n"); | 
|  | } | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | void BreakpadSetKeyValue(BreakpadRef ref, NSString *key, NSString *value) { | 
|  | try { | 
|  | // Not called at exception time | 
|  | Breakpad *breakpad = (Breakpad *)ref; | 
|  |  | 
|  | if (breakpad && key && gKeyValueAllocator) { | 
|  | ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator); | 
|  |  | 
|  | breakpad->SetKeyValue(key, value); | 
|  | } | 
|  | } catch(...) {    // don't let exceptions leave this C API | 
|  | fprintf(stderr, "BreakpadSetKeyValue() : error\n"); | 
|  | } | 
|  | } | 
|  |  | 
|  | void BreakpadAddUploadParameter(BreakpadRef ref, | 
|  | NSString *key, | 
|  | NSString *value) { | 
|  | // The only difference, internally, between an upload parameter and | 
|  | // a key value one that is set with BreakpadSetKeyValue is that we | 
|  | // prepend the keyname with a special prefix.  This informs the | 
|  | // crash sender that the parameter should be sent along with the | 
|  | // POST of the crash dump upload. | 
|  | try { | 
|  | Breakpad *breakpad = (Breakpad *)ref; | 
|  |  | 
|  | if (breakpad && key && gKeyValueAllocator) { | 
|  | ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator); | 
|  |  | 
|  | NSString *prefixedKey = [@BREAKPAD_SERVER_PARAMETER_PREFIX | 
|  | stringByAppendingString:key]; | 
|  | breakpad->SetKeyValue(prefixedKey, value); | 
|  | } | 
|  | } catch(...) {    // don't let exceptions leave this C API | 
|  | fprintf(stderr, "BreakpadSetKeyValue() : error\n"); | 
|  | } | 
|  | } | 
|  |  | 
|  | void BreakpadRemoveUploadParameter(BreakpadRef ref, | 
|  | NSString *key) { | 
|  | try { | 
|  | // Not called at exception time | 
|  | Breakpad *breakpad = (Breakpad *)ref; | 
|  |  | 
|  | if (breakpad && key && gKeyValueAllocator) { | 
|  | ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator); | 
|  |  | 
|  | NSString *prefixedKey = [NSString stringWithFormat:@"%@%@", | 
|  | @BREAKPAD_SERVER_PARAMETER_PREFIX, key]; | 
|  | breakpad->RemoveKeyValue(prefixedKey); | 
|  | } | 
|  | } catch(...) {    // don't let exceptions leave this C API | 
|  | fprintf(stderr, "BreakpadRemoveKeyValue() : error\n"); | 
|  | } | 
|  | } | 
|  | //============================================================================= | 
|  | NSString *BreakpadKeyValue(BreakpadRef ref, NSString *key) { | 
|  | NSString *value = nil; | 
|  |  | 
|  | try { | 
|  | // Not called at exception time | 
|  | Breakpad *breakpad = (Breakpad *)ref; | 
|  |  | 
|  | if (!breakpad || !key || !gKeyValueAllocator) | 
|  | return nil; | 
|  |  | 
|  | ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator); | 
|  |  | 
|  | value = breakpad->KeyValue(key); | 
|  | } catch(...) {    // don't let exceptions leave this C API | 
|  | fprintf(stderr, "BreakpadKeyValue() : error\n"); | 
|  | } | 
|  |  | 
|  | return value; | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | void BreakpadRemoveKeyValue(BreakpadRef ref, NSString *key) { | 
|  | try { | 
|  | // Not called at exception time | 
|  | Breakpad *breakpad = (Breakpad *)ref; | 
|  |  | 
|  | if (breakpad && key && gKeyValueAllocator) { | 
|  | ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator); | 
|  |  | 
|  | breakpad->RemoveKeyValue(key); | 
|  | } | 
|  | } catch(...) {    // don't let exceptions leave this C API | 
|  | fprintf(stderr, "BreakpadRemoveKeyValue() : error\n"); | 
|  | } | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | int BreakpadGetCrashReportCount(BreakpadRef ref) { | 
|  | try { | 
|  | // Not called at exception time | 
|  | Breakpad *breakpad = (Breakpad *)ref; | 
|  |  | 
|  | if (breakpad) { | 
|  | return static_cast<int>([breakpad->CrashReportsToUpload() count]); | 
|  | } | 
|  | } catch(...) {    // don't let exceptions leave this C API | 
|  | fprintf(stderr, "BreakpadGetCrashReportCount() : error\n"); | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | void BreakpadUploadNextReport(BreakpadRef ref) { | 
|  | BreakpadUploadNextReportWithParameters(ref, nil); | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | NSDictionary *BreakpadGetNextReportConfiguration(BreakpadRef ref) { | 
|  | try { | 
|  | Breakpad *breakpad = (Breakpad *)ref; | 
|  | if (breakpad) | 
|  | return breakpad->NextCrashReportConfiguration(); | 
|  | } catch(...) {    // don't let exceptions leave this C API | 
|  | fprintf(stderr, "BreakpadGetNextReportConfiguration() : error\n"); | 
|  | } | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | NSDate *BreakpadGetDateOfMostRecentCrashReport(BreakpadRef ref) { | 
|  | try { | 
|  | Breakpad *breakpad = (Breakpad *)ref; | 
|  | if (breakpad) { | 
|  | return breakpad->DateOfMostRecentCrashReport(); | 
|  | } | 
|  | } catch (...) {    // don't let exceptions leave this C API | 
|  | fprintf(stderr, "BreakpadGetDateOfMostRecentCrashReport() : error\n"); | 
|  | } | 
|  | return nil; | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | void BreakpadUploadReportWithParametersAndConfiguration( | 
|  | BreakpadRef ref, | 
|  | NSDictionary *server_parameters, | 
|  | NSDictionary *configuration) { | 
|  | try { | 
|  | Breakpad *breakpad = (Breakpad *)ref; | 
|  | if (!breakpad || !configuration) | 
|  | return; | 
|  | breakpad->UploadReportWithConfiguration(configuration, server_parameters); | 
|  | } catch(...) {    // don't let exceptions leave this C API | 
|  | fprintf(stderr, | 
|  | "BreakpadUploadReportWithParametersAndConfiguration() : error\n"); | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | void BreakpadUploadNextReportWithParameters(BreakpadRef ref, | 
|  | NSDictionary *server_parameters) { | 
|  | try { | 
|  | Breakpad *breakpad = (Breakpad *)ref; | 
|  | if (!breakpad) | 
|  | return; | 
|  | NSDictionary *configuration = breakpad->NextCrashReportConfiguration(); | 
|  | if (!configuration) | 
|  | return; | 
|  | return BreakpadUploadReportWithParametersAndConfiguration(ref, | 
|  | server_parameters, | 
|  | configuration); | 
|  | } catch(...) {    // don't let exceptions leave this C API | 
|  | fprintf(stderr, "BreakpadUploadNextReportWithParameters() : error\n"); | 
|  | } | 
|  | } | 
|  |  | 
|  | void BreakpadHandleNetworkResponse(BreakpadRef ref, | 
|  | NSDictionary *configuration, | 
|  | NSData *data, | 
|  | NSError *error) { | 
|  | try { | 
|  | // Not called at exception time | 
|  | Breakpad *breakpad = (Breakpad *)ref; | 
|  | if (breakpad && configuration) | 
|  | breakpad->HandleNetworkResponse(configuration,data, error); | 
|  |  | 
|  | } catch(...) {    // don't let exceptions leave this C API | 
|  | fprintf(stderr, "BreakpadHandleNetworkResponse() : error\n"); | 
|  | } | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | void BreakpadUploadData(BreakpadRef ref, NSData *data, NSString *name, | 
|  | NSDictionary *server_parameters) { | 
|  | try { | 
|  | // Not called at exception time | 
|  | Breakpad *breakpad = (Breakpad *)ref; | 
|  |  | 
|  | if (breakpad) { | 
|  | breakpad->UploadData(data, name, server_parameters); | 
|  | } | 
|  | } catch(...) {    // don't let exceptions leave this C API | 
|  | fprintf(stderr, "BreakpadUploadData() : error\n"); | 
|  | } | 
|  | } | 
|  |  | 
|  | //============================================================================= | 
|  | NSDictionary *BreakpadGenerateReport(BreakpadRef ref, | 
|  | NSDictionary *server_parameters) { | 
|  | try { | 
|  | // Not called at exception time | 
|  | Breakpad *breakpad = (Breakpad *)ref; | 
|  |  | 
|  | if (breakpad) { | 
|  | return breakpad->GenerateReport(server_parameters); | 
|  | } else { | 
|  | return nil; | 
|  | } | 
|  | } catch(...) {    // don't let exceptions leave this C API | 
|  | fprintf(stderr, "BreakpadGenerateReport() : error\n"); | 
|  | return nil; | 
|  | } | 
|  | } |