diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /toolkit/crashreporter/google-breakpad/src/client | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'toolkit/crashreporter/google-breakpad/src/client')
208 files changed, 54529 insertions, 0 deletions
diff --git a/toolkit/crashreporter/google-breakpad/src/client/apple/Framework/BreakpadDefines.h b/toolkit/crashreporter/google-breakpad/src/client/apple/Framework/BreakpadDefines.h new file mode 100644 index 000000000..410a5a6f3 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/apple/Framework/BreakpadDefines.h @@ -0,0 +1,73 @@ +// 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. + +// Keys for configuration file +#define kReporterMinidumpDirectoryKey "MinidumpDir" +#define kReporterMinidumpIDKey "MinidumpID" + +// Filename for recording uploaded IDs +#define kReporterLogFilename "uploads.log" + +// The default subdirectory of the Library to put crash dumps in +// The subdirectory is +// ~/Library/<kDefaultLibrarySubdirectory>/<GoogleBreakpadProduct> +#define kDefaultLibrarySubdirectory "Breakpad" + +// Specify some special keys to be used in the configuration file that is +// generated by Breakpad and consumed by the crash_sender. +#define BREAKPAD_PRODUCT "BreakpadProduct" +#define BREAKPAD_PRODUCT_DISPLAY "BreakpadProductDisplay" +#define BREAKPAD_VERSION "BreakpadVersion" +#define BREAKPAD_VENDOR "BreakpadVendor" +#define BREAKPAD_URL "BreakpadURL" +#define BREAKPAD_REPORT_INTERVAL "BreakpadReportInterval" +#define BREAKPAD_SKIP_CONFIRM "BreakpadSkipConfirm" +#define BREAKPAD_CONFIRM_TIMEOUT "BreakpadConfirmTimeout" +#define BREAKPAD_SEND_AND_EXIT "BreakpadSendAndExit" +#define BREAKPAD_DUMP_DIRECTORY "BreakpadMinidumpLocation" +#define BREAKPAD_INSPECTOR_LOCATION "BreakpadInspectorLocation" +#define BREAKPAD_REPORTER_EXE_LOCATION \ + "BreakpadReporterExeLocation" +#define BREAKPAD_LOGFILES "BreakpadLogFiles" +#define BREAKPAD_LOGFILE_UPLOAD_SIZE "BreakpadLogFileTailSize" +#define BREAKPAD_REQUEST_COMMENTS "BreakpadRequestComments" +#define BREAKPAD_COMMENTS "BreakpadComments" +#define BREAKPAD_REQUEST_EMAIL "BreakpadRequestEmail" +#define BREAKPAD_EMAIL "BreakpadEmail" +#define BREAKPAD_SERVER_TYPE "BreakpadServerType" +#define BREAKPAD_SERVER_PARAMETER_DICT "BreakpadServerParameters" +#define BREAKPAD_IN_PROCESS "BreakpadInProcess" + +// The keys below are NOT user supplied, and are used internally. +#define BREAKPAD_PROCESS_START_TIME "BreakpadProcStartTime" +#define BREAKPAD_PROCESS_UP_TIME "BreakpadProcessUpTime" +#define BREAKPAD_PROCESS_CRASH_TIME "BreakpadProcessCrashTime" +#define BREAKPAD_LOGFILE_KEY_PREFIX "BreakpadAppLogFile" +#define BREAKPAD_SERVER_PARAMETER_PREFIX "BreakpadServerParameterPrefix_" +#define BREAKPAD_ON_DEMAND "BreakpadOnDemand" diff --git a/toolkit/crashreporter/google-breakpad/src/client/ios/Breakpad.h b/toolkit/crashreporter/google-breakpad/src/client/ios/Breakpad.h new file mode 100644 index 000000000..c099ad07c --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/ios/Breakpad.h @@ -0,0 +1,246 @@ +// 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. + +// Framework to provide a simple C API to crash reporting for +// applications. By default, if any machine-level exception (e.g., +// EXC_BAD_ACCESS) occurs, it will be handled by the BreakpadRef +// object as follows: +// +// 1. Create a minidump file (see Breakpad for details) +// 2. Create a config file. +// +// These files can then be uploaded to a server. + +typedef void *BreakpadRef; + +#ifdef __cplusplus +extern "C" { +#endif + +#include <Foundation/Foundation.h> + +#include <client/apple/Framework/BreakpadDefines.h> + +// The keys in the dictionary returned by |BreakpadGenerateReport|. +#define BREAKPAD_OUTPUT_DUMP_FILE "BreakpadDumpFile" +#define BREAKPAD_OUTPUT_CONFIG_FILE "BreakpadConfigFile" + +// Optional user-defined function to decide if we should handle this crash or +// forward it along. +// Return true if you want Breakpad to handle it. +// Return false if you want Breakpad to skip it +// The exception handler always returns false, as if SEND_AND_EXIT were false +// (which means the next exception handler will take the exception) +typedef bool (*BreakpadFilterCallback)(int exception_type, + int exception_code, + mach_port_t crashing_thread, + void *context); + +// Create a new BreakpadRef object and install it as an exception +// handler. The |parameters| will typically be the contents of your +// bundle's Info.plist. +// +// You can also specify these additional keys for customizable behavior: +// Key: Value: +// BREAKPAD_PRODUCT Product name (e.g., "MyAwesomeProduct") +// This one is used as the key to identify +// the product when uploading. Falls back to +// CFBundleName if not specified. +// REQUIRED +// +// BREAKPAD_PRODUCT_DISPLAY This is the display name, e.g. a pretty +// name for the product when the crash_sender +// pops up UI for the user. Falls back first to +// CFBundleDisplayName and then to +// BREAKPAD_PRODUCT if not specified. +// +// BREAKPAD_VERSION Product version (e.g., 1.2.3), used +// as metadata for crash report. Falls back to +// CFBundleVersion if not specified. +// REQUIRED +// +// BREAKPAD_VENDOR Vendor name, used in UI (e.g. "A report has +// been created that you can send to <vendor>") +// +// BREAKPAD_URL URL destination for reporting +// REQUIRED +// +// BREAKPAD_DUMP_DIRECTORY The directory to store crash-dumps +// in. By default, we use +// ~/Library/Cache/Breakpad/<BREAKPAD_PRODUCT> +// The path you specify here is tilde-expanded. +// +// BREAKPAD_SERVER_TYPE A parameter that tells Breakpad how to +// rewrite the upload parameters for a specific +// server type. The currently valid values are +// 'socorro' or 'google'. If you want to add +// other types, see the function in +// crash_report_sender.m that maps parameters to +// URL parameters. Defaults to 'google'. +// +// BREAKPAD_SERVER_PARAMETER_DICT A plist dictionary of static +// parameters that are uploaded to the +// server. The parameters are sent as +// is to the crash server. Their +// content isn't added to the minidump +// but pass as URL parameters when +// uploading theminidump to the crash +// server. +//============================================================================= +// The BREAKPAD_PRODUCT, BREAKPAD_VERSION and BREAKPAD_URL are +// required to have non-NULL values. By default, the BREAKPAD_PRODUCT +// will be the CFBundleName and the BREAKPAD_VERSION will be the +// CFBundleVersion when these keys are present in the bundle's +// Info.plist, which is usually passed in to BreakpadCreate() as an +// NSDictionary (you could also pass in another dictionary that had +// the same keys configured). If the BREAKPAD_PRODUCT or +// BREAKPAD_VERSION are ultimately undefined, BreakpadCreate() will +// fail. You have been warned. +// +// If you are running in a debugger, Breakpad will not install, unless the +// BREAKPAD_IGNORE_DEBUGGER envionment variable is set and/or non-zero. +// +//============================================================================= +// The following are NOT user-supplied but are documented here for +// completeness. They are calculated by Breakpad during initialization & +// crash-dump generation, or entered in by the user. +// +// BREAKPAD_PROCESS_START_TIME The time, in seconds since the Epoch, the +// process started +// +// BREAKPAD_PROCESS_CRASH_TIME The time, in seconds since the Epoch, the +// process crashed. +// +// BREAKPAD_PROCESS_UP_TIME The total time in milliseconds the process +// has been running. This parameter is not +// set until the crash-dump-generation phase. +// +// BREAKPAD_SERVER_PARAMETER_PREFIX This prefix is used by Breakpad +// internally, because Breakpad uses +// the same dictionary internally to +// track both its internal +// configuration parameters and +// parameters meant to be uploaded +// to the server. This string is +// used internally by Breakpad to +// prefix user-supplied parameter +// names so those can be sent to the +// server without leaking Breakpad's +// internal values. + +// Returns a new BreakpadRef object on success, NULL otherwise. +BreakpadRef BreakpadCreate(NSDictionary *parameters); + +// Uninstall and release the data associated with |ref|. +void BreakpadRelease(BreakpadRef ref); + +// User defined key and value string storage. Generally this is used +// to configure Breakpad's internal operation, such as whether the +// crash_sender should prompt the user, or the filesystem location for +// the minidump file. See Breakpad.h for some parameters that can be +// set. Anything longer than 255 bytes will be truncated. Note that +// the string is converted to UTF8 before truncation, so any multibyte +// character that straddles the 255(256 - 1 for terminator) byte limit +// will be mangled. +// +// A maximum number of 64 key/value pairs are supported. An assert() +// will fire if more than this number are set. Unfortunately, right +// now, the same dictionary is used for both Breakpad's parameters AND +// the Upload parameters. +// +// TODO (nealsid): Investigate how necessary this is if we don't +// automatically upload parameters to the server anymore. +// TODO (nealsid): separate server parameter dictionary from the +// dictionary used to configure Breakpad, and document limits for each +// independently. +void BreakpadSetKeyValue(BreakpadRef ref, NSString *key, NSString *value); +NSString *BreakpadKeyValue(BreakpadRef ref, NSString *key); +void BreakpadRemoveKeyValue(BreakpadRef ref, NSString *key); + +// You can use this method to specify parameters that will be uploaded +// to the crash server. They will be automatically encoded as +// necessary. Note that as mentioned above there are limits on both +// the number of keys and their length. +void BreakpadAddUploadParameter(BreakpadRef ref, NSString *key, + NSString *value); + +// This method will remove a previously-added parameter from the +// upload parameter set. +void BreakpadRemoveUploadParameter(BreakpadRef ref, NSString *key); + +// Method to handle uploading data to the server + +// Returns the number of crash reports waiting to send to the server. +int BreakpadGetCrashReportCount(BreakpadRef ref); + +// Returns the next upload configuration. The report file is deleted. +NSDictionary *BreakpadGetNextReportConfiguration(BreakpadRef ref); + +// Upload next report to the server. +void BreakpadUploadNextReport(BreakpadRef ref); + +// Upload next report to the server. +// |server_parameters| is additional server parameters to send. +void BreakpadUploadNextReportWithParameters(BreakpadRef ref, + NSDictionary *server_parameters); + +// Upload a report to the server. +// |server_parameters| is additional server parameters to send. +// |configuration| is the configuration of the breakpad report to send. +void BreakpadUploadReportWithParametersAndConfiguration( + BreakpadRef ref, + NSDictionary *server_parameters, + NSDictionary *configuration); + +// Handles the network response of a breakpad upload. This function is needed if +// the actual upload is done by the Breakpad client. +// |configuration| is the configuration of the upload. It must contain the same +// fields as the configuration passed to +// BreakpadUploadReportWithParametersAndConfiguration. +// |data| and |error| contain the network response. +void BreakpadHandleNetworkResponse(BreakpadRef ref, + NSDictionary *configuration, + NSData *data, + NSError *error); + +// Upload a file to the server. |data| is the content of the file to sent. +// |server_parameters| is additional server parameters to send. +void BreakpadUploadData(BreakpadRef ref, NSData *data, NSString *name, + NSDictionary *server_parameters); + +// Generate a breakpad minidump and configuration file in the dump directory. +// The report will be available for uploading. The paths of the created files +// are returned in the dictionary. |server_parameters| is additional server +// parameters to add in the config file. +NSDictionary *BreakpadGenerateReport(BreakpadRef ref, + NSDictionary *server_parameters); + +#ifdef __cplusplus +} +#endif diff --git a/toolkit/crashreporter/google-breakpad/src/client/ios/Breakpad.mm b/toolkit/crashreporter/google-breakpad/src/client/ios/Breakpad.mm new file mode 100644 index 000000000..ce635bd27 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/ios/Breakpad.mm @@ -0,0 +1,916 @@ +// 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> + +#import "client/ios/handler/ios_exception_minidump_generator.h" +#import "client/mac/crash_generation/ConfigFile.h" +#import "client/mac/handler/exception_handler.h" +#import "client/mac/handler/minidump_generator.h" +#import "client/mac/sender/uploader.h" +#import "client/mac/handler/protected_memory_allocator.h" +#import "common/simple_string_dictionary.h" + +#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::SimpleStringDictionary; + +//============================================================================= +// 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(); + 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) + + 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 + +//============================================================================= +// 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_->~SimpleStringDictionary(); + } + + 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(SimpleStringDictionary)) ) + SimpleStringDictionary(); + + SimpleStringDictionary &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 char *value = config_params_->GetValueForKey([key UTF8String]); + return value ? [NSString stringWithUTF8String:value] : nil; +} + +//============================================================================= +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()]; +} + +//============================================================================= +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]; + + SimpleStringDictionary::Iterator it(*config_params_); + while (const SimpleStringDictionary::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; + + SimpleStringDictionary 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 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 - +#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(SimpleStringDictionary)); + + // Create a mutex for use in accessing the SimpleStringDictionary + 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; +} + +//============================================================================= +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; + } +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/ios/Breakpad.xcodeproj/project.pbxproj b/toolkit/crashreporter/google-breakpad/src/client/ios/Breakpad.xcodeproj/project.pbxproj new file mode 100644 index 000000000..e9fcae3f9 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/ios/Breakpad.xcodeproj/project.pbxproj @@ -0,0 +1,578 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 14569321182CE29F0029C465 /* ucontext_compat.h in Headers */ = {isa = PBXBuildFile; fileRef = 14569320182CE29F0029C465 /* ucontext_compat.h */; }; + 14569323182CE2C10029C465 /* mach_vm_compat.h in Headers */ = {isa = PBXBuildFile; fileRef = 14569322182CE2C10029C465 /* mach_vm_compat.h */; }; + 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 */; }; + 16C7CDE8147D4A4300776EAD /* ConfigFile.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CB9E147D4A4300776EAD /* ConfigFile.h */; }; + 16C7CDE9147D4A4300776EAD /* ConfigFile.mm in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CB9F147D4A4300776EAD /* ConfigFile.mm */; }; + 16C7CDF5147D4A4300776EAD /* breakpad_nlist_64.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CBAD147D4A4300776EAD /* breakpad_nlist_64.cc */; }; + 16C7CDF6147D4A4300776EAD /* breakpad_nlist_64.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CBAE147D4A4300776EAD /* breakpad_nlist_64.h */; }; + 16C7CDF7147D4A4300776EAD /* dynamic_images.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CBAF147D4A4300776EAD /* dynamic_images.cc */; }; + 16C7CDF8147D4A4300776EAD /* dynamic_images.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CBB0147D4A4300776EAD /* dynamic_images.h */; }; + 16C7CDF9147D4A4300776EAD /* exception_handler.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CBB1147D4A4300776EAD /* exception_handler.cc */; }; + 16C7CDFA147D4A4300776EAD /* exception_handler.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CBB2147D4A4300776EAD /* exception_handler.h */; }; + 16C7CDFC147D4A4300776EAD /* minidump_generator.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CBB4147D4A4300776EAD /* minidump_generator.cc */; }; + 16C7CDFD147D4A4300776EAD /* minidump_generator.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CBB5147D4A4300776EAD /* minidump_generator.h */; }; + 16C7CDFE147D4A4300776EAD /* protected_memory_allocator.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CBBC147D4A4300776EAD /* protected_memory_allocator.cc */; }; + 16C7CDFF147D4A4300776EAD /* protected_memory_allocator.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CBBD147D4A4300776EAD /* protected_memory_allocator.h */; }; + 16C7CE08147D4A4300776EAD /* uploader.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CBEA147D4A4300776EAD /* uploader.h */; }; + 16C7CE09147D4A4300776EAD /* uploader.mm in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CBEB147D4A4300776EAD /* uploader.mm */; }; + 16C7CE18147D4A4300776EAD /* minidump_file_writer-inl.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CC04147D4A4300776EAD /* minidump_file_writer-inl.h */; }; + 16C7CE19147D4A4300776EAD /* minidump_file_writer.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CC05147D4A4300776EAD /* minidump_file_writer.cc */; }; + 16C7CE1A147D4A4300776EAD /* minidump_file_writer.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CC06147D4A4300776EAD /* minidump_file_writer.h */; }; + 16C7CE40147D4A4300776EAD /* convert_UTF.c in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CC4A147D4A4300776EAD /* convert_UTF.c */; }; + 16C7CE41147D4A4300776EAD /* convert_UTF.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CC4B147D4A4300776EAD /* convert_UTF.h */; }; + 16C7CE78147D4A4300776EAD /* GTMLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CC88147D4A4300776EAD /* GTMLogger.h */; }; + 16C7CE79147D4A4300776EAD /* GTMLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CC89147D4A4300776EAD /* GTMLogger.m */; }; + 16C7CE7A147D4A4300776EAD /* HTTPMultipartUpload.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CC8A147D4A4300776EAD /* HTTPMultipartUpload.h */; }; + 16C7CE7B147D4A4300776EAD /* HTTPMultipartUpload.m in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CC8B147D4A4300776EAD /* HTTPMultipartUpload.m */; }; + 16C7CE83147D4A4300776EAD /* file_id.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CC93147D4A4300776EAD /* file_id.cc */; }; + 16C7CE84147D4A4300776EAD /* file_id.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CC94147D4A4300776EAD /* file_id.h */; }; + 16C7CE85147D4A4300776EAD /* macho_id.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CC95147D4A4300776EAD /* macho_id.cc */; }; + 16C7CE86147D4A4300776EAD /* macho_id.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CC96147D4A4300776EAD /* macho_id.h */; }; + 16C7CE8A147D4A4300776EAD /* macho_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CC9A147D4A4300776EAD /* macho_utilities.cc */; }; + 16C7CE8B147D4A4300776EAD /* macho_utilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CC9B147D4A4300776EAD /* macho_utilities.h */; }; + 16C7CE8C147D4A4300776EAD /* macho_walker.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CC9C147D4A4300776EAD /* macho_walker.cc */; }; + 16C7CE8D147D4A4300776EAD /* macho_walker.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CC9D147D4A4300776EAD /* macho_walker.h */; }; + 16C7CE8F147D4A4300776EAD /* string_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CC9F147D4A4300776EAD /* string_utilities.cc */; }; + 16C7CE90147D4A4300776EAD /* string_utilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7CCA0147D4A4300776EAD /* string_utilities.h */; }; + 16C7CE93147D4A4300776EAD /* md5.cc in Sources */ = {isa = PBXBuildFile; fileRef = 16C7CCA4147D4A4300776EAD /* md5.cc */; }; + 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 */; }; + 1EEEB60F1720821900F7E689 /* simple_string_dictionary.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1EEEB60C1720821900F7E689 /* simple_string_dictionary.cc */; }; + 1EEEB6101720821900F7E689 /* simple_string_dictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 1EEEB60D1720821900F7E689 /* simple_string_dictionary.h */; }; + 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 */ + +/* Begin PBXFileReference section */ + 14569320182CE29F0029C465 /* ucontext_compat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ucontext_compat.h; sourceTree = "<group>"; }; + 14569322182CE2C10029C465 /* mach_vm_compat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mach_vm_compat.h; sourceTree = "<group>"; }; + 16BFA66E14E195E9009704F8 /* ios_exception_minidump_generator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ios_exception_minidump_generator.h; sourceTree = "<group>"; }; + 16BFA67114E1965A009704F8 /* ios_exception_minidump_generator.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ios_exception_minidump_generator.mm; sourceTree = "<group>"; }; + 16C7C968147D4A4200776EAD /* BreakpadDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BreakpadDefines.h; sourceTree = "<group>"; }; + 16C7C96A147D4A4200776EAD /* Breakpad.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Breakpad.h; sourceTree = "<group>"; }; + 16C7C96B147D4A4200776EAD /* Breakpad.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Breakpad.mm; sourceTree = "<group>"; }; + 16C7CB9E147D4A4300776EAD /* ConfigFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConfigFile.h; sourceTree = "<group>"; }; + 16C7CB9F147D4A4300776EAD /* ConfigFile.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ConfigFile.mm; sourceTree = "<group>"; }; + 16C7CBAD147D4A4300776EAD /* breakpad_nlist_64.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = breakpad_nlist_64.cc; sourceTree = "<group>"; }; + 16C7CBAE147D4A4300776EAD /* breakpad_nlist_64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = breakpad_nlist_64.h; sourceTree = "<group>"; }; + 16C7CBAF147D4A4300776EAD /* dynamic_images.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = dynamic_images.cc; sourceTree = "<group>"; }; + 16C7CBB0147D4A4300776EAD /* dynamic_images.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dynamic_images.h; sourceTree = "<group>"; }; + 16C7CBB1147D4A4300776EAD /* exception_handler.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = exception_handler.cc; sourceTree = "<group>"; }; + 16C7CBB2147D4A4300776EAD /* exception_handler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = exception_handler.h; sourceTree = "<group>"; }; + 16C7CBB4147D4A4300776EAD /* minidump_generator.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = minidump_generator.cc; sourceTree = "<group>"; }; + 16C7CBB5147D4A4300776EAD /* minidump_generator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = minidump_generator.h; sourceTree = "<group>"; }; + 16C7CBBC147D4A4300776EAD /* protected_memory_allocator.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = protected_memory_allocator.cc; sourceTree = "<group>"; }; + 16C7CBBD147D4A4300776EAD /* protected_memory_allocator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = protected_memory_allocator.h; sourceTree = "<group>"; }; + 16C7CBEA147D4A4300776EAD /* uploader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = uploader.h; sourceTree = "<group>"; }; + 16C7CBEB147D4A4300776EAD /* uploader.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = uploader.mm; sourceTree = "<group>"; }; + 16C7CC04147D4A4300776EAD /* minidump_file_writer-inl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "minidump_file_writer-inl.h"; sourceTree = "<group>"; }; + 16C7CC05147D4A4300776EAD /* minidump_file_writer.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = minidump_file_writer.cc; sourceTree = "<group>"; }; + 16C7CC06147D4A4300776EAD /* minidump_file_writer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = minidump_file_writer.h; sourceTree = "<group>"; }; + 16C7CC07147D4A4300776EAD /* minidump_file_writer_unittest.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = minidump_file_writer_unittest.cc; sourceTree = "<group>"; }; + 16C7CC4A147D4A4300776EAD /* convert_UTF.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = convert_UTF.c; sourceTree = "<group>"; }; + 16C7CC4B147D4A4300776EAD /* convert_UTF.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = convert_UTF.h; sourceTree = "<group>"; }; + 16C7CC88147D4A4300776EAD /* GTMLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GTMLogger.h; sourceTree = "<group>"; }; + 16C7CC89147D4A4300776EAD /* GTMLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GTMLogger.m; sourceTree = "<group>"; }; + 16C7CC8A147D4A4300776EAD /* HTTPMultipartUpload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPMultipartUpload.h; sourceTree = "<group>"; }; + 16C7CC8B147D4A4300776EAD /* HTTPMultipartUpload.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTTPMultipartUpload.m; sourceTree = "<group>"; }; + 16C7CC93147D4A4300776EAD /* file_id.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = file_id.cc; sourceTree = "<group>"; }; + 16C7CC94147D4A4300776EAD /* file_id.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = file_id.h; sourceTree = "<group>"; }; + 16C7CC95147D4A4300776EAD /* macho_id.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = macho_id.cc; sourceTree = "<group>"; }; + 16C7CC96147D4A4300776EAD /* macho_id.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = macho_id.h; sourceTree = "<group>"; }; + 16C7CC9A147D4A4300776EAD /* macho_utilities.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = macho_utilities.cc; sourceTree = "<group>"; }; + 16C7CC9B147D4A4300776EAD /* macho_utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = macho_utilities.h; sourceTree = "<group>"; }; + 16C7CC9C147D4A4300776EAD /* macho_walker.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = macho_walker.cc; sourceTree = "<group>"; }; + 16C7CC9D147D4A4300776EAD /* macho_walker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = macho_walker.h; sourceTree = "<group>"; }; + 16C7CC9F147D4A4300776EAD /* string_utilities.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = string_utilities.cc; sourceTree = "<group>"; }; + 16C7CCA0147D4A4300776EAD /* string_utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = string_utilities.h; sourceTree = "<group>"; }; + 16C7CCA4147D4A4300776EAD /* md5.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = md5.cc; sourceTree = "<group>"; }; + 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>"; }; + 1EEEB60C1720821900F7E689 /* simple_string_dictionary.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = simple_string_dictionary.cc; sourceTree = "<group>"; }; + 1EEEB60D1720821900F7E689 /* simple_string_dictionary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = simple_string_dictionary.h; 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; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D2AAC07C0554694100DB518D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + AACBBE4A0F95108600F1A2B1 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 034768DFFF38A50411DB9C8B /* Products */ = { + isa = PBXGroup; + children = ( + D2AAC07E0554694100DB518D /* libBreakpad.a */, + ); + name = Products; + sourceTree = "<group>"; + }; + 0867D691FE84028FC02AAC07 /* Breakpad */ = { + isa = PBXGroup; + children = ( + 08FB77AEFE84172EC02AAC07 /* Classes */, + 32C88DFF0371C24200C91783 /* Other Sources */, + 0867D69AFE84028FC02AAC07 /* Frameworks */, + 034768DFFF38A50411DB9C8B /* Products */, + ); + name = Breakpad; + sourceTree = "<group>"; + }; + 0867D69AFE84028FC02AAC07 /* Frameworks */ = { + isa = PBXGroup; + children = ( + AACBBE490F95108600F1A2B1 /* Foundation.framework */, + ); + name = Frameworks; + sourceTree = "<group>"; + }; + 08FB77AEFE84172EC02AAC07 /* Classes */ = { + isa = PBXGroup; + children = ( + 16C7C965147D4A4200776EAD /* client */, + 16C7CC47147D4A4300776EAD /* common */, + ); + name = Classes; + sourceTree = "<group>"; + }; + 16BFA66A14E195E9009704F8 /* handler */ = { + isa = PBXGroup; + children = ( + 16BFA67114E1965A009704F8 /* ios_exception_minidump_generator.mm */, + 16BFA66E14E195E9009704F8 /* ios_exception_minidump_generator.h */, + ); + path = handler; + sourceTree = "<group>"; + }; + 16C7C965147D4A4200776EAD /* client */ = { + isa = PBXGroup; + children = ( + 16C7C966147D4A4200776EAD /* apple */, + 16C7C969147D4A4200776EAD /* ios */, + 16C7C99E147D4A4200776EAD /* mac */, + 16C7CC04147D4A4300776EAD /* minidump_file_writer-inl.h */, + 16C7CC05147D4A4300776EAD /* minidump_file_writer.cc */, + 16C7CC06147D4A4300776EAD /* minidump_file_writer.h */, + 16C7CC07147D4A4300776EAD /* minidump_file_writer_unittest.cc */, + ); + name = client; + path = ..; + sourceTree = SOURCE_ROOT; + }; + 16C7C966147D4A4200776EAD /* apple */ = { + isa = PBXGroup; + children = ( + 16C7C967147D4A4200776EAD /* Framework */, + ); + path = apple; + sourceTree = "<group>"; + }; + 16C7C967147D4A4200776EAD /* Framework */ = { + isa = PBXGroup; + children = ( + 16C7C968147D4A4200776EAD /* BreakpadDefines.h */, + ); + path = Framework; + sourceTree = "<group>"; + }; + 16C7C969147D4A4200776EAD /* ios */ = { + isa = PBXGroup; + children = ( + 16C92FAB150DF8330053D7BA /* BreakpadController.h */, + 16C92FAC150DF8330053D7BA /* BreakpadController.mm */, + 16BFA66A14E195E9009704F8 /* handler */, + 16C7C96A147D4A4200776EAD /* Breakpad.h */, + 16C7C96B147D4A4200776EAD /* Breakpad.mm */, + ); + path = ios; + sourceTree = "<group>"; + }; + 16C7C99E147D4A4200776EAD /* mac */ = { + isa = PBXGroup; + children = ( + 16C7CB9D147D4A4300776EAD /* crash_generation */, + 16C7CBAA147D4A4300776EAD /* handler */, + 16C7CBC8147D4A4300776EAD /* sender */, + ); + path = mac; + sourceTree = "<group>"; + }; + 16C7CB9D147D4A4300776EAD /* crash_generation */ = { + isa = PBXGroup; + children = ( + 16C7CB9E147D4A4300776EAD /* ConfigFile.h */, + 16C7CB9F147D4A4300776EAD /* ConfigFile.mm */, + ); + path = crash_generation; + sourceTree = "<group>"; + }; + 16C7CBAA147D4A4300776EAD /* handler */ = { + isa = PBXGroup; + children = ( + 16C7CBAD147D4A4300776EAD /* breakpad_nlist_64.cc */, + 16C7CBAE147D4A4300776EAD /* breakpad_nlist_64.h */, + 16C7CBAF147D4A4300776EAD /* dynamic_images.cc */, + 16C7CBB0147D4A4300776EAD /* dynamic_images.h */, + 16C7CBB1147D4A4300776EAD /* exception_handler.cc */, + 16C7CBB2147D4A4300776EAD /* exception_handler.h */, + 14569322182CE2C10029C465 /* mach_vm_compat.h */, + 16C7CBB4147D4A4300776EAD /* minidump_generator.cc */, + 16C7CBB5147D4A4300776EAD /* minidump_generator.h */, + 16C7CBBC147D4A4300776EAD /* protected_memory_allocator.cc */, + 16C7CBBD147D4A4300776EAD /* protected_memory_allocator.h */, + 14569320182CE29F0029C465 /* ucontext_compat.h */, + ); + path = handler; + sourceTree = "<group>"; + }; + 16C7CBC8147D4A4300776EAD /* sender */ = { + isa = PBXGroup; + children = ( + 16C7CBEA147D4A4300776EAD /* uploader.h */, + 16C7CBEB147D4A4300776EAD /* uploader.mm */, + ); + path = sender; + sourceTree = "<group>"; + }; + 16C7CC47147D4A4300776EAD /* common */ = { + isa = PBXGroup; + children = ( + 1EEEB60C1720821900F7E689 /* simple_string_dictionary.cc */, + 1EEEB60D1720821900F7E689 /* simple_string_dictionary.h */, + 16C7CC4A147D4A4300776EAD /* convert_UTF.c */, + 16C7CC4B147D4A4300776EAD /* convert_UTF.h */, + 16C7CC82147D4A4300776EAD /* mac */, + 16C7CCA4147D4A4300776EAD /* md5.cc */, + 16C7CCA5147D4A4300776EAD /* md5.h */, + 16C7CCB9147D4A4300776EAD /* string_conversion.cc */, + 16C7CCBA147D4A4300776EAD /* string_conversion.h */, + ); + name = common; + path = ../../common; + sourceTree = SOURCE_ROOT; + }; + 16C7CC82147D4A4300776EAD /* mac */ = { + isa = PBXGroup; + children = ( + 16C7CC88147D4A4300776EAD /* GTMLogger.h */, + 16C7CC89147D4A4300776EAD /* GTMLogger.m */, + 16C7CC8A147D4A4300776EAD /* HTTPMultipartUpload.h */, + 16C7CC8B147D4A4300776EAD /* HTTPMultipartUpload.m */, + 16C7CC93147D4A4300776EAD /* file_id.cc */, + 16C7CC94147D4A4300776EAD /* file_id.h */, + 16C7CC95147D4A4300776EAD /* macho_id.cc */, + 16C7CC96147D4A4300776EAD /* macho_id.h */, + 16C7CC9A147D4A4300776EAD /* macho_utilities.cc */, + 16C7CC9B147D4A4300776EAD /* macho_utilities.h */, + 16C7CC9C147D4A4300776EAD /* macho_walker.cc */, + 16C7CC9D147D4A4300776EAD /* macho_walker.h */, + 16C7CC9F147D4A4300776EAD /* string_utilities.cc */, + 16C7CCA0147D4A4300776EAD /* string_utilities.h */, + ); + path = mac; + sourceTree = "<group>"; + }; + 32C88DFF0371C24200C91783 /* Other Sources */ = { + isa = PBXGroup; + children = ( + AA747D9E0F9514B9006C5449 /* Breakpad_Prefix.pch */, + ); + name = "Other Sources"; + sourceTree = "<group>"; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + D2AAC07A0554694100DB518D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + AA747D9F0F9514B9006C5449 /* Breakpad_Prefix.pch in Headers */, + 16C7CCCB147D4A4300776EAD /* BreakpadDefines.h in Headers */, + 16C7CCCC147D4A4300776EAD /* Breakpad.h in Headers */, + 16C7CDE8147D4A4300776EAD /* ConfigFile.h in Headers */, + 14569321182CE29F0029C465 /* ucontext_compat.h in Headers */, + 16C7CDF6147D4A4300776EAD /* breakpad_nlist_64.h in Headers */, + 16C7CDF8147D4A4300776EAD /* dynamic_images.h in Headers */, + 16C7CDFA147D4A4300776EAD /* exception_handler.h in Headers */, + 16C7CDFD147D4A4300776EAD /* minidump_generator.h in Headers */, + 16C7CDFF147D4A4300776EAD /* protected_memory_allocator.h in Headers */, + 16C7CE08147D4A4300776EAD /* uploader.h in Headers */, + 16C7CE18147D4A4300776EAD /* minidump_file_writer-inl.h in Headers */, + 16C7CE1A147D4A4300776EAD /* minidump_file_writer.h in Headers */, + 16C7CE41147D4A4300776EAD /* convert_UTF.h in Headers */, + 16C7CE78147D4A4300776EAD /* GTMLogger.h in Headers */, + 16C7CE7A147D4A4300776EAD /* HTTPMultipartUpload.h in Headers */, + 16C7CE84147D4A4300776EAD /* file_id.h in Headers */, + 16C7CE86147D4A4300776EAD /* macho_id.h in Headers */, + 16C7CE8B147D4A4300776EAD /* macho_utilities.h in Headers */, + 16C7CE8D147D4A4300776EAD /* macho_walker.h in Headers */, + 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 */, + 16C92FAD150DF8330053D7BA /* BreakpadController.h in Headers */, + 1EEEB6101720821900F7E689 /* simple_string_dictionary.h in Headers */, + 14569323182CE2C10029C465 /* mach_vm_compat.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + D2AAC07D0554694100DB518D /* Breakpad */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1DEB921E08733DC00010E9CD /* Build configuration list for PBXNativeTarget "Breakpad" */; + buildPhases = ( + D2AAC07A0554694100DB518D /* Headers */, + D2AAC07B0554694100DB518D /* Sources */, + D2AAC07C0554694100DB518D /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Breakpad; + productName = Breakpad; + productReference = D2AAC07E0554694100DB518D /* libBreakpad.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 0867D690FE84028FC02AAC07 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0510; + }; + buildConfigurationList = 1DEB922208733DC00010E9CD /* Build configuration list for PBXProject "Breakpad" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 1; + knownRegions = ( + English, + Japanese, + French, + German, + da, + de, + es, + fr, + it, + ja, + nl, + no, + sl, + sv, + tr, + ); + mainGroup = 0867D691FE84028FC02AAC07 /* Breakpad */; + productRefGroup = 034768DFFF38A50411DB9C8B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D2AAC07D0554694100DB518D /* Breakpad */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + D2AAC07B0554694100DB518D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 16C7CCCD147D4A4300776EAD /* Breakpad.mm in Sources */, + 16C7CDE9147D4A4300776EAD /* ConfigFile.mm in Sources */, + 16C7CDF5147D4A4300776EAD /* breakpad_nlist_64.cc in Sources */, + 16C7CDF7147D4A4300776EAD /* dynamic_images.cc in Sources */, + 16C7CDF9147D4A4300776EAD /* exception_handler.cc in Sources */, + 16C7CDFC147D4A4300776EAD /* minidump_generator.cc in Sources */, + 16C7CDFE147D4A4300776EAD /* protected_memory_allocator.cc in Sources */, + 16C7CE09147D4A4300776EAD /* uploader.mm in Sources */, + 16C7CE19147D4A4300776EAD /* minidump_file_writer.cc in Sources */, + 16C7CE40147D4A4300776EAD /* convert_UTF.c in Sources */, + 16C7CE79147D4A4300776EAD /* GTMLogger.m in Sources */, + 16C7CE7B147D4A4300776EAD /* HTTPMultipartUpload.m in Sources */, + 16C7CE83147D4A4300776EAD /* file_id.cc in Sources */, + 16C7CE85147D4A4300776EAD /* macho_id.cc in Sources */, + 16C7CE8A147D4A4300776EAD /* macho_utilities.cc in Sources */, + 16C7CE8C147D4A4300776EAD /* macho_walker.cc in Sources */, + 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 */, + 16C92FAE150DF8330053D7BA /* BreakpadController.mm in Sources */, + 1EEEB60F1720821900F7E689 /* simple_string_dictionary.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1DEB921F08733DC00010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = NO; + DSTROOT = /tmp/Breakpad.dst; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/../mac/build/Debug\"", + ); + GCC_DYNAMIC_NO_PIC = NO; + GCC_MODEL_TUNING = G5; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = Breakpad_Prefix.pch; + INSTALL_PATH = /usr/local/lib; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/../mac/build/Breakpad.build/Debug/Breakpad.build/Objects-normal/i386\"", + "\"$(SRCROOT)/../mac/build/Breakpad.build/Debug/Breakpad.build/Objects-normal/x86_64\"", + "\"$(SRCROOT)/../mac/build/Breakpad.build/Debug/breakpadUtilities.build/Objects-normal/i386\"", + "\"$(SRCROOT)/../mac/build/Breakpad.build/Debug/breakpadUtilities.build/Objects-normal/x86_64\"", + "\"$(SRCROOT)/../mac/build/Breakpad.build/Debug/gtest.build/Objects-normal/i386\"", + "\"$(SRCROOT)/../mac/build/Breakpad.build/Debug/gtest.build/Objects-normal/x86_64\"", + "\"$(SRCROOT)/../mac/build/Debug\"", + "\"$(SRCROOT)/../mac/gcov\"", + ); + PRODUCT_NAME = Breakpad; + }; + name = Debug; + }; + 1DEB922008733DC00010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + DSTROOT = /tmp/Breakpad.dst; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/../mac/build/Debug\"", + ); + GCC_MODEL_TUNING = G5; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = Breakpad_Prefix.pch; + INSTALL_PATH = /usr/local/lib; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/../mac/build/Breakpad.build/Debug/Breakpad.build/Objects-normal/i386\"", + "\"$(SRCROOT)/../mac/build/Breakpad.build/Debug/Breakpad.build/Objects-normal/x86_64\"", + "\"$(SRCROOT)/../mac/build/Breakpad.build/Debug/breakpadUtilities.build/Objects-normal/i386\"", + "\"$(SRCROOT)/../mac/build/Breakpad.build/Debug/breakpadUtilities.build/Objects-normal/x86_64\"", + "\"$(SRCROOT)/../mac/build/Breakpad.build/Debug/gtest.build/Objects-normal/i386\"", + "\"$(SRCROOT)/../mac/build/Breakpad.build/Debug/gtest.build/Objects-normal/x86_64\"", + "\"$(SRCROOT)/../mac/build/Debug\"", + "\"$(SRCROOT)/../mac/gcov\"", + ); + PRODUCT_NAME = Breakpad; + }; + name = Release; + }; + 1DEB922308733DC00010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_SHADOW = YES; + GCC_WARN_SIGN_COMPARE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNKNOWN_PRAGMAS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_LABEL = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + ../../, + ../../client/apple/Framework, + ../../common/mac, + ); + IPHONEOS_DEPLOYMENT_TARGET = 5.0; + ONLY_ACTIVE_ARCH = YES; + OTHER_LDFLAGS = "-ObjC"; + SDKROOT = iphoneos; + WARNING_CFLAGS = "-Wundef"; + }; + name = Debug; + }; + 1DEB922408733DC00010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++0x"; + CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_SHADOW = YES; + GCC_WARN_SIGN_COMPARE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNKNOWN_PRAGMAS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_LABEL = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + ../../, + ../../client/apple/Framework, + ../../common/mac, + ); + IPHONEOS_DEPLOYMENT_TARGET = 5.0; + OTHER_LDFLAGS = "-ObjC"; + SDKROOT = iphoneos; + WARNING_CFLAGS = "-Wundef"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DEB921E08733DC00010E9CD /* Build configuration list for PBXNativeTarget "Breakpad" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB921F08733DC00010E9CD /* Debug */, + 1DEB922008733DC00010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1DEB922208733DC00010E9CD /* Build configuration list for PBXProject "Breakpad" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB922308733DC00010E9CD /* Debug */, + 1DEB922408733DC00010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 0867D690FE84028FC02AAC07 /* Project object */; +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/ios/BreakpadController.h b/toolkit/crashreporter/google-breakpad/src/client/ios/BreakpadController.h new file mode 100644 index 000000000..13609cb8d --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/ios/BreakpadController.h @@ -0,0 +1,141 @@ +// 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_; + + // Whether the controller has been started on the main thread. This is only + // used to assert the initialization order is correct. + BOOL started_; + + // The interval to wait between two uploads. Value is 0 if no upload must be + // done. + int uploadIntervalInSeconds_; + + // The dictionary that contains additional server parameters to send when + // uploading crash reports. + NSDictionary* uploadTimeParameters_; +} + +// 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; + +// Reset the controller configuration to its initial value, which is the +// infoDictionary of the bundle of the application. +- (void)resetConfiguration; + +// 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; + +// Set additional server parameters to send when uploading crash reports. +- (void)setParametersToAddAtUploadTime:(NSDictionary*)uploadTimeParameters; + +// Specify an upload parameter that will be added to the crash report when a +// crash report is generated. 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; + +// Check if there is currently a crash report to upload. +- (void)hasReportToUpload:(void(^)(BOOL))callback; + +// Get the number of crash reports waiting to upload. +- (void)getCrashReportCount:(void(^)(int))callback; + +// Get the next report to upload. +// - If upload is disabled, callback will be called with (nil, -1). +// - If a delay is to be waited before sending, callback will be called with +// (nil, n), with n (> 0) being the number of seconds to wait. +// - if no delay is needed, callback will be called with (0, configuration), +// configuration being next report to upload, or nil if none is pending. +- (void)getNextReportConfigurationOrSendDelay: + (void(^)(NSDictionary*, int))callback; + +// Sends synchronously the report specified by |configuration|. This method is +// NOT thread safe and must be called from the breakpad thread. +- (void)threadUnsafeSendReportWithConfiguration:(NSDictionary*)configuration + withBreakpadRef:(BreakpadRef)ref; + +@end + +#endif // CLIENT_IOS_HANDLER_IOS_BREAKPAD_CONTROLLER_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/ios/BreakpadController.mm b/toolkit/crashreporter/google-breakpad/src/client/ios/BreakpadController.mm new file mode 100644 index 000000000..dd71cff68 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/ios/BreakpadController.mm @@ -0,0 +1,354 @@ +// 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 <common/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); + enableUploads_ = NO; + started_ = NO; + [self resetConfiguration]; + } + 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]; + [uploadTimeParameters_ release]; + [super dealloc]; +} + +#pragma mark - + +- (void)start:(BOOL)onCurrentThread { + if (started_) + return; + started_ = YES; + void(^startBlock)() = ^{ + assert(!breakpadRef_); + breakpadRef_ = BreakpadCreate(configuration_); + if (breakpadRef_) { + BreakpadAddUploadParameter(breakpadRef_, @"platform", GetPlatform()); + } + }; + if (onCurrentThread) + startBlock(); + else + dispatch_async(queue_, startBlock); +} + +- (void)stop { + if (!started_) + return; + started_ = NO; + dispatch_sync(queue_, ^{ + if (breakpadRef_) { + BreakpadRelease(breakpadRef_); + breakpadRef_ = NULL; + } + }); +} + +// This method must be called from the breakpad queue. +- (void)threadUnsafeSendReportWithConfiguration:(NSDictionary*)configuration + withBreakpadRef:(BreakpadRef)ref { + NSAssert(started_, @"The controller must be started before " + "threadUnsafeSendReportWithConfiguration is called"); + if (breakpadRef_) { + BreakpadUploadReportWithParametersAndConfiguration(breakpadRef_, + uploadTimeParameters_, + configuration); + } +} + +- (void)setUploadingEnabled:(BOOL)enabled { + NSAssert(started_, + @"The controller must be started before setUploadingEnabled is called"); + 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 { + NSAssert(!started_, + @"The controller must not be started when updateConfiguration is called"); + [configuration_ addEntriesFromDictionary:configuration]; + NSString* uploadInterval = + [configuration_ valueForKey:@BREAKPAD_REPORT_INTERVAL]; + if (uploadInterval) + [self setUploadInterval:[uploadInterval intValue]]; +} + +- (void)resetConfiguration { + NSAssert(!started_, + @"The controller must not be started when resetConfiguration is called"); + [configuration_ autorelease]; + configuration_ = [[[NSBundle mainBundle] infoDictionary] mutableCopy]; + NSString* uploadInterval = + [configuration_ valueForKey:@BREAKPAD_REPORT_INTERVAL]; + [self setUploadInterval:[uploadInterval intValue]]; + [self setParametersToAddAtUploadTime:nil]; +} + +- (void)setUploadingURL:(NSString*)url { + NSAssert(!started_, + @"The controller must not be started when setUploadingURL is called"); + [configuration_ setValue:url forKey:@BREAKPAD_URL]; +} + +- (void)setUploadInterval:(int)intervalInSeconds { + NSAssert(!started_, + @"The controller must not be started when setUploadInterval is called"); + [configuration_ removeObjectForKey:@BREAKPAD_REPORT_INTERVAL]; + uploadIntervalInSeconds_ = intervalInSeconds; + if (uploadIntervalInSeconds_ < 0) + uploadIntervalInSeconds_ = 0; +} + +- (void)setParametersToAddAtUploadTime:(NSDictionary*)uploadTimeParameters { + NSAssert(!started_, @"The controller must not be started when " + "setParametersToAddAtUploadTime is called"); + [uploadTimeParameters_ autorelease]; + uploadTimeParameters_ = [uploadTimeParameters copy]; +} + +- (void)addUploadParameter:(NSString*)value forKey:(NSString*)key { + NSAssert(started_, + @"The controller must be started before addUploadParameter is called"); + dispatch_async(queue_, ^{ + if (breakpadRef_) + BreakpadAddUploadParameter(breakpadRef_, key, value); + }); +} + +- (void)removeUploadParameterForKey:(NSString*)key { + NSAssert(started_, @"The controller must be started before " + "removeUploadParameterForKey is called"); + dispatch_async(queue_, ^{ + if (breakpadRef_) + BreakpadRemoveUploadParameter(breakpadRef_, key); + }); +} + +- (void)withBreakpadRef:(void(^)(BreakpadRef))callback { + NSAssert(started_, + @"The controller must be started before withBreakpadRef is called"); + dispatch_async(queue_, ^{ + callback(breakpadRef_); + }); +} + +- (void)hasReportToUpload:(void(^)(BOOL))callback { + NSAssert(started_, @"The controller must be started before " + "hasReportToUpload is called"); + dispatch_async(queue_, ^{ + callback(breakpadRef_ && (BreakpadGetCrashReportCount(breakpadRef_) > 0)); + }); +} + +- (void)getCrashReportCount:(void(^)(int))callback { + NSAssert(started_, @"The controller must be started before " + "getCrashReportCount is called"); + dispatch_async(queue_, ^{ + callback(breakpadRef_ ? BreakpadGetCrashReportCount(breakpadRef_) : 0); + }); +} + +- (void)getNextReportConfigurationOrSendDelay: + (void(^)(NSDictionary*, int))callback { + NSAssert(started_, @"The controller must be started before " + "getNextReportConfigurationOrSendDelay is called"); + dispatch_async(queue_, ^{ + if (!breakpadRef_) { + callback(nil, -1); + return; + } + int delay = [self sendDelay]; + if (delay != 0) { + callback(nil, delay); + return; + } + [self reportWillBeSent]; + callback(BreakpadGetNextReportConfiguration(breakpadRef_), 0); + }); +} + +#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_ - static_cast<int>(spanSeconds); +} + +- (void)reportWillBeSent { + NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; + [userDefaults setObject:[NSNumber numberWithDouble:CFAbsoluteTimeGetCurrent()] + forKey:kLastSubmission]; + [userDefaults synchronize]; +} + +- (void)sendStoredCrashReports { + dispatch_async(queue_, ^{ + if (BreakpadGetCrashReportCount(breakpadRef_) == 0) + 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]; + BreakpadUploadNextReportWithParameters(breakpadRef_, + uploadTimeParameters_); + + // If more reports must be sent, make sure this method is called again. + if (BreakpadGetCrashReportCount(breakpadRef_) > 0) + timeToWait = uploadIntervalInSeconds_; + } + + // A report must be sent later. + if (timeToWait > 0) { + // performSelector: doesn't work on queue_ + dispatch_async(dispatch_get_main_queue(), ^{ + [self performSelector:@selector(sendStoredCrashReports) + withObject:nil + afterDelay:timeToWait]; + }); + } + }); +} + +@end diff --git a/toolkit/crashreporter/google-breakpad/src/client/ios/Breakpad_Prefix.pch b/toolkit/crashreporter/google-breakpad/src/client/ios/Breakpad_Prefix.pch new file mode 100644 index 000000000..bfb739423 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/ios/Breakpad_Prefix.pch @@ -0,0 +1,7 @@ +// +// Prefix header for all source files of the 'CocoaTouchStaticLibrary' target in the 'CocoaTouchStaticLibrary' project. +// + +#ifdef __OBJC__ + #import <Foundation/Foundation.h> +#endif diff --git a/toolkit/crashreporter/google-breakpad/src/client/ios/handler/ios_exception_minidump_generator.h b/toolkit/crashreporter/google-breakpad/src/client/ios/handler/ios_exception_minidump_generator.h new file mode 100644 index 000000000..21133e632 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/ios/handler/ios_exception_minidump_generator.h @@ -0,0 +1,74 @@ +// 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 <Foundation/Foundation.h> + +#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. + uintptr_t GetPCFromException(); + + // Get the crashing link register from the exception. + uintptr_t GetLRFromException(); + + // Write a virtual thread context for the crashing site. + bool WriteCrashingContext(MDLocationDescriptor *register_location); + // Per-CPU implementations of the above method. +#ifdef HAS_ARM_SUPPORT + bool WriteCrashingContextARM(MDLocationDescriptor *register_location); +#endif +#ifdef HAS_ARM64_SUPPORT + bool WriteCrashingContextARM64(MDLocationDescriptor *register_location); +#endif + + NSArray *return_addresses_; +}; + +} // namespace google_breakpad + +#endif // CLIENT_IOS_HANDLER_IOS_EXCEPTION_MINIDUMP_GENERATOR_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/ios/handler/ios_exception_minidump_generator.mm b/toolkit/crashreporter/google-breakpad/src/client/ios/handler/ios_exception_minidump_generator.mm new file mode 100644 index 000000000..82ea5bb5d --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/ios/handler/ios_exception_minidump_generator.mm @@ -0,0 +1,210 @@ +// 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 <pthread.h> + +#include "google_breakpad/common/minidump_cpu_arm.h" +#include "google_breakpad/common/minidump_cpu_arm64.h" +#include "google_breakpad/common/minidump_exception_mac.h" +#include "client/minidump_file_writer-inl.h" +#include "common/scoped_ptr.h" + +#if defined(HAS_ARM_SUPPORT) && defined(HAS_ARM64_SUPPORT) +#error "This file should be compiled for only one architecture at a time" +#endif + +namespace { + +const int kExceptionType = EXC_SOFTWARE; +const int kExceptionCode = MD_EXCEPTION_CODE_MAC_NS_EXCEPTION; + +#if defined(HAS_ARM_SUPPORT) || defined(HAS_ARM64_SUPPORT) +const uintptr_t kExpectedFinalFp = sizeof(uintptr_t); +const uintptr_t kExpectedFinalSp = 0; + +// Append the given value to the sp position of the stack represented +// by memory. +void AppendToMemory(uint8_t *memory, uintptr_t sp, uintptr_t data) { + memcpy(memory + sp, &data, sizeof(data)); +} +#endif + +} // 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 + return WriteCrashingContextARM(register_location); +#elif defined(HAS_ARM64_SUPPORT) + return WriteCrashingContextARM64(register_location); +#else + assert(false); + return false; +#endif +} + +#ifdef HAS_ARM_SUPPORT +bool IosExceptionMinidumpGenerator::WriteCrashingContextARM( + MDLocationDescriptor *register_location) { + TypedMDRVA<MDRawContextARM> 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[MD_CONTEXT_ARM_REG_IOS_FP] = kExpectedFinalFp; // FP + context_ptr->iregs[MD_CONTEXT_ARM_REG_SP] = kExpectedFinalSp; // SP + context_ptr->iregs[MD_CONTEXT_ARM_REG_LR] = GetLRFromException(); // LR + context_ptr->iregs[MD_CONTEXT_ARM_REG_PC] = GetPCFromException(); // PC + return true; +} +#endif + +#ifdef HAS_ARM64_SUPPORT +bool IosExceptionMinidumpGenerator::WriteCrashingContextARM64( + MDLocationDescriptor *register_location) { + TypedMDRVA<MDRawContextARM64> context(&writer_); + if (!context.Allocate()) + return false; + *register_location = context.location(); + MDRawContextARM64 *context_ptr = context.get(); + memset(context_ptr, 0, sizeof(*context_ptr)); + context_ptr->context_flags = MD_CONTEXT_ARM64_FULL; + context_ptr->iregs[MD_CONTEXT_ARM64_REG_FP] = kExpectedFinalFp; // FP + context_ptr->iregs[MD_CONTEXT_ARM64_REG_SP] = kExpectedFinalSp; // SP + context_ptr->iregs[MD_CONTEXT_ARM64_REG_LR] = GetLRFromException(); // LR + context_ptr->iregs[MD_CONTEXT_ARM64_REG_PC] = GetPCFromException(); // PC + return true; +} +#endif + +uintptr_t IosExceptionMinidumpGenerator::GetPCFromException() { + return [[return_addresses_ objectAtIndex:0] unsignedIntegerValue]; +} + +uintptr_t IosExceptionMinidumpGenerator::GetLRFromException() { + return [[return_addresses_ objectAtIndex:1] unsignedIntegerValue]; +} + +bool IosExceptionMinidumpGenerator::WriteExceptionStream( + MDRawDirectory *exception_stream) { +#if defined(HAS_ARM_SUPPORT) || defined(HAS_ARM64_SUPPORT) + TypedMDRVA<MDRawExceptionStream> 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) { +#if defined(HAS_ARM_SUPPORT) || defined(HAS_ARM64_SUPPORT) + if (pthread_mach_thread_np(pthread_self()) != thread_id) + return MinidumpGenerator::WriteThreadStream(thread_id, thread); + + size_t frame_count = [return_addresses_ count]; + if (frame_count == 0) + return false; + UntypedMDRVA memory(&writer_); + size_t pointer_size = sizeof(uintptr_t); + size_t frame_record_size = 2 * pointer_size; + size_t stack_size = frame_record_size * (frame_count - 1) + pointer_size; + if (!memory.Allocate(stack_size)) + return false; + scoped_array<uint8_t> stack_memory(new uint8_t[stack_size]); + uintptr_t sp = stack_size - pointer_size; + uintptr_t fp = 0; + uintptr_t lr = 0; + for (size_t current_frame = frame_count - 1; + current_frame > 0; + --current_frame) { + AppendToMemory(stack_memory.get(), sp, lr); + sp -= pointer_size; + AppendToMemory(stack_memory.get(), sp, fp); + fp = sp; + sp -= pointer_size; + lr = [[return_addresses_ objectAtIndex:current_frame] unsignedIntegerValue]; + } + if (!memory.Copy(stack_memory.get(), stack_size)) + return false; + assert(sp == kExpectedFinalSp); + assert(fp == kExpectedFinalFp); + assert(lr == GetLRFromException()); + 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/toolkit/crashreporter/google-breakpad/src/client/linux/crash_generation/client_info.h b/toolkit/crashreporter/google-breakpad/src/client/linux/crash_generation/client_info.h new file mode 100644 index 000000000..d0a184a63 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/crash_generation/client_info.h @@ -0,0 +1,53 @@ +// Copyright (c) 2010 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_LINUX_CRASH_GENERATION_CLIENT_INFO_H_ +#define CLIENT_LINUX_CRASH_GENERATION_CLIENT_INFO_H_ + +namespace google_breakpad { + +class CrashGenerationServer; + +class ClientInfo { + public: + ClientInfo(pid_t pid, CrashGenerationServer* crash_server) + : crash_server_(crash_server), + pid_(pid) {} + + CrashGenerationServer* crash_server() const { return crash_server_; } + pid_t pid() const { return pid_; } + + private: + CrashGenerationServer* crash_server_; + pid_t pid_; +}; + +} + +#endif // CLIENT_LINUX_CRASH_GENERATION_CLIENT_INFO_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/crash_generation/crash_generation_client.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/crash_generation/crash_generation_client.cc new file mode 100644 index 000000000..d8bfbbad2 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/crash_generation/crash_generation_client.cc @@ -0,0 +1,105 @@ +// Copyright (c) 2010 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/linux/crash_generation/crash_generation_client.h" + +#include <stdio.h> +#include <sys/socket.h> +#include <sys/types.h> + +#include <algorithm> + +#include "common/linux/eintr_wrapper.h" +#include "common/linux/ignore_ret.h" +#include "third_party/lss/linux_syscall_support.h" + +namespace google_breakpad { + +namespace { + +class CrashGenerationClientImpl : public CrashGenerationClient { + public: + explicit CrashGenerationClientImpl(int server_fd) : server_fd_(server_fd) {} + virtual ~CrashGenerationClientImpl() {} + + virtual bool RequestDump(const void* blob, size_t blob_size) { + int fds[2]; + if (sys_pipe(fds) < 0) + return false; + static const unsigned kControlMsgSize = CMSG_SPACE(sizeof(int)); + + struct kernel_iovec iov; + iov.iov_base = const_cast<void*>(blob); + iov.iov_len = blob_size; + + struct kernel_msghdr msg = { 0 }; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + char cmsg[kControlMsgSize] = ""; + msg.msg_control = cmsg; + msg.msg_controllen = sizeof(cmsg); + + struct cmsghdr* hdr = CMSG_FIRSTHDR(&msg); + hdr->cmsg_level = SOL_SOCKET; + hdr->cmsg_type = SCM_RIGHTS; + hdr->cmsg_len = CMSG_LEN(sizeof(int)); + int* p = reinterpret_cast<int*>(CMSG_DATA(hdr)); + *p = fds[1]; + + ssize_t ret = HANDLE_EINTR(sys_sendmsg(server_fd_, &msg, 0)); + sys_close(fds[1]); + if (ret < 0) { + sys_close(fds[0]); + return false; + } + + // Wait for an ACK from the server. + char b; + IGNORE_RET(HANDLE_EINTR(sys_read(fds[0], &b, 1))); + sys_close(fds[0]); + + return true; + } + + private: + int server_fd_; + + DISALLOW_COPY_AND_ASSIGN(CrashGenerationClientImpl); +}; + +} // namespace + +// static +CrashGenerationClient* CrashGenerationClient::TryCreate(int server_fd) { + if (server_fd < 0) + return NULL; + return new CrashGenerationClientImpl(server_fd); +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/crash_generation/crash_generation_client.h b/toolkit/crashreporter/google-breakpad/src/client/linux/crash_generation/crash_generation_client.h new file mode 100644 index 000000000..4e68424ae --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/crash_generation/crash_generation_client.h @@ -0,0 +1,65 @@ +// Copyright (c) 2010 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_LINUX_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_ +#define CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_ + +#include "common/basictypes.h" + +#include <stddef.h> + +namespace google_breakpad { + +// CrashGenerationClient is an interface for implementing out-of-process crash +// dumping. The default implementation, accessed via the TryCreate() factory, +// works in conjunction with the CrashGenerationServer to generate a minidump +// via a remote process. +class CrashGenerationClient { + public: + CrashGenerationClient() {} + virtual ~CrashGenerationClient() {} + + // Request the crash server to generate a dump. |blob| is an opaque + // CrashContext pointer from exception_handler.h. + // Returns true if the dump was successful; false otherwise. + virtual bool RequestDump(const void* blob, size_t blob_size) = 0; + + // Returns a new CrashGenerationClient if |server_fd| is valid and + // connects to a CrashGenerationServer. Otherwise, return NULL. + // The returned CrashGenerationClient* is owned by the caller of + // this function. + static CrashGenerationClient* TryCreate(int server_fd); + + private: + DISALLOW_COPY_AND_ASSIGN(CrashGenerationClient); +}; + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/crash_generation/crash_generation_server.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/crash_generation/crash_generation_server.cc new file mode 100644 index 000000000..1d7d93b99 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/crash_generation/crash_generation_server.cc @@ -0,0 +1,333 @@ +// Copyright (c) 2010 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 <assert.h> +#include <dirent.h> +#include <fcntl.h> +#include <limits.h> +#include <poll.h> +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <vector> + +#include "client/linux/crash_generation/crash_generation_server.h" +#include "client/linux/crash_generation/client_info.h" +#include "client/linux/handler/exception_handler.h" +#include "client/linux/minidump_writer/minidump_writer.h" +#include "common/linux/eintr_wrapper.h" +#include "common/linux/guid_creator.h" +#include "common/linux/safe_readlink.h" + +static const char kCommandQuit = 'x'; + +namespace google_breakpad { + +CrashGenerationServer::CrashGenerationServer( + const int listen_fd, + OnClientDumpRequestCallback dump_callback, + void* dump_context, + OnClientExitingCallback exit_callback, + void* exit_context, + bool generate_dumps, + const string* dump_path) : + server_fd_(listen_fd), + dump_callback_(dump_callback), + dump_context_(dump_context), + exit_callback_(exit_callback), + exit_context_(exit_context), + generate_dumps_(generate_dumps), + started_(false) +{ + if (dump_path) + dump_dir_ = *dump_path; + else + dump_dir_ = "/tmp"; +} + +CrashGenerationServer::~CrashGenerationServer() +{ + if (started_) + Stop(); +} + +bool +CrashGenerationServer::Start() +{ + if (started_ || 0 > server_fd_) + return false; + + int control_pipe[2]; + if (pipe(control_pipe)) + return false; + + if (fcntl(control_pipe[0], F_SETFD, FD_CLOEXEC)) + return false; + if (fcntl(control_pipe[1], F_SETFD, FD_CLOEXEC)) + return false; + + if (fcntl(control_pipe[0], F_SETFL, O_NONBLOCK)) + return false; + + control_pipe_in_ = control_pipe[0]; + control_pipe_out_ = control_pipe[1]; + + if (pthread_create(&thread_, NULL, + ThreadMain, reinterpret_cast<void*>(this))) + return false; + + started_ = true; + return true; +} + +void +CrashGenerationServer::Stop() +{ + assert(pthread_self() != thread_); + + if (!started_) + return; + + HANDLE_EINTR(write(control_pipe_out_, &kCommandQuit, 1)); + + void* dummy; + pthread_join(thread_, &dummy); + + close(control_pipe_in_); + close(control_pipe_out_); + + started_ = false; +} + +//static +bool +CrashGenerationServer::CreateReportChannel(int* server_fd, int* client_fd) +{ + int fds[2]; + + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds)) + return false; + + static const int on = 1; + // Enable passcred on the server end of the socket + if (setsockopt(fds[1], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on))) + return false; + + if (fcntl(fds[1], F_SETFL, O_NONBLOCK)) + return false; + if (fcntl(fds[1], F_SETFD, FD_CLOEXEC)) + return false; + + *client_fd = fds[0]; + *server_fd = fds[1]; + return true; +} + +// The following methods/functions execute on the server thread + +void +CrashGenerationServer::Run() +{ + struct pollfd pollfds[2]; + memset(&pollfds, 0, sizeof(pollfds)); + + pollfds[0].fd = server_fd_; + pollfds[0].events = POLLIN; + + pollfds[1].fd = control_pipe_in_; + pollfds[1].events = POLLIN; + + while (true) { + // infinite timeout + int nevents = poll(pollfds, sizeof(pollfds)/sizeof(pollfds[0]), -1); + if (-1 == nevents) { + if (EINTR == errno) { + continue; + } else { + return; + } + } + + if (pollfds[0].revents && !ClientEvent(pollfds[0].revents)) + return; + + if (pollfds[1].revents && !ControlEvent(pollfds[1].revents)) + return; + } +} + +bool +CrashGenerationServer::ClientEvent(short revents) +{ + if (POLLHUP & revents) + return false; + assert(POLLIN & revents); + + // A process has crashed and has signaled us by writing a datagram + // to the death signal socket. The datagram contains the crash context needed + // for writing the minidump as well as a file descriptor and a credentials + // block so that they can't lie about their pid. + + // The length of the control message: + static const unsigned kControlMsgSize = + CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred)); + // The length of the regular payload: + static const unsigned kCrashContextSize = + sizeof(google_breakpad::ExceptionHandler::CrashContext); + + struct msghdr msg = {0}; + struct iovec iov[1]; + char crash_context[kCrashContextSize]; + char control[kControlMsgSize]; + const ssize_t expected_msg_size = sizeof(crash_context); + + iov[0].iov_base = crash_context; + iov[0].iov_len = sizeof(crash_context); + msg.msg_iov = iov; + msg.msg_iovlen = sizeof(iov)/sizeof(iov[0]); + msg.msg_control = control; + msg.msg_controllen = kControlMsgSize; + + const ssize_t msg_size = HANDLE_EINTR(recvmsg(server_fd_, &msg, 0)); + if (msg_size != expected_msg_size) + return true; + + if (msg.msg_controllen != kControlMsgSize || + msg.msg_flags & ~MSG_TRUNC) + return true; + + // Walk the control payload and extract the file descriptor and validated pid. + pid_t crashing_pid = -1; + int signal_fd = -1; + for (struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg); hdr; + hdr = CMSG_NXTHDR(&msg, hdr)) { + if (hdr->cmsg_level != SOL_SOCKET) + continue; + if (hdr->cmsg_type == SCM_RIGHTS) { + const unsigned len = hdr->cmsg_len - + (((uint8_t*)CMSG_DATA(hdr)) - (uint8_t*)hdr); + assert(len % sizeof(int) == 0u); + const unsigned num_fds = len / sizeof(int); + if (num_fds > 1 || num_fds == 0) { + // A nasty process could try and send us too many descriptors and + // force a leak. + for (unsigned i = 0; i < num_fds; ++i) + close(reinterpret_cast<int*>(CMSG_DATA(hdr))[i]); + return true; + } else { + signal_fd = reinterpret_cast<int*>(CMSG_DATA(hdr))[0]; + } + } else if (hdr->cmsg_type == SCM_CREDENTIALS) { + const struct ucred *cred = + reinterpret_cast<struct ucred*>(CMSG_DATA(hdr)); + crashing_pid = cred->pid; + } + } + + if (crashing_pid == -1 || signal_fd == -1) { + if (signal_fd) + close(signal_fd); + return true; + } + + string minidump_filename; + if (!MakeMinidumpFilename(minidump_filename)) + return true; + + if (!google_breakpad::WriteMinidump(minidump_filename.c_str(), + crashing_pid, crash_context, + kCrashContextSize)) { + close(signal_fd); + return true; + } + + if (dump_callback_) { + ClientInfo info(crashing_pid, this); + + dump_callback_(dump_context_, &info, &minidump_filename); + } + + // Send the done signal to the process: it can exit now. + // (Closing this will make the child's sys_read unblock and return 0.) + close(signal_fd); + + return true; +} + +bool +CrashGenerationServer::ControlEvent(short revents) +{ + if (POLLHUP & revents) + return false; + assert(POLLIN & revents); + + char command; + if (read(control_pipe_in_, &command, 1)) + return false; + + switch (command) { + case kCommandQuit: + return false; + default: + assert(0); + } + + return true; +} + +bool +CrashGenerationServer::MakeMinidumpFilename(string& outFilename) +{ + GUID guid; + char guidString[kGUIDStringLength+1]; + + if (!(CreateGUID(&guid) + && GUIDToString(&guid, guidString, sizeof(guidString)))) + return false; + + char path[PATH_MAX]; + snprintf(path, sizeof(path), "%s/%s.dmp", dump_dir_.c_str(), guidString); + + outFilename = path; + return true; +} + +// static +void* +CrashGenerationServer::ThreadMain(void *arg) +{ + reinterpret_cast<CrashGenerationServer*>(arg)->Run(); + return NULL; +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/crash_generation/crash_generation_server.h b/toolkit/crashreporter/google-breakpad/src/client/linux/crash_generation/crash_generation_server.h new file mode 100644 index 000000000..483fb709b --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/crash_generation/crash_generation_server.h @@ -0,0 +1,135 @@ +// Copyright (c) 2010 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_LINUX_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_ +#define CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_ + +#include <pthread.h> + +#include <string> + +#include "common/using_std_string.h" + +namespace google_breakpad { + +class ClientInfo; + +class CrashGenerationServer { +public: + // WARNING: callbacks may be invoked on a different thread + // than that which creates the CrashGenerationServer. They must + // be thread safe. + typedef void (*OnClientDumpRequestCallback)(void* context, + const ClientInfo* client_info, + const string* file_path); + + typedef void (*OnClientExitingCallback)(void* context, + const ClientInfo* client_info); + + // Create an instance with the given parameters. + // + // Parameter listen_fd: The server fd created by CreateReportChannel(). + // Parameter dump_callback: Callback for a client crash dump request. + // Parameter dump_context: Context for client crash dump request callback. + // Parameter exit_callback: Callback for client process exit. + // Parameter exit_context: Context for client exit callback. + // Parameter generate_dumps: Whether to automatically generate dumps. + // Client code of this class might want to generate dumps explicitly + // in the crash dump request callback. In that case, false can be + // passed for this parameter. + // Parameter dump_path: Path for generating dumps; required only if true is + // passed for generateDumps parameter; NULL can be passed otherwise. + CrashGenerationServer(const int listen_fd, + OnClientDumpRequestCallback dump_callback, + void* dump_context, + OnClientExitingCallback exit_callback, + void* exit_context, + bool generate_dumps, + const string* dump_path); + + ~CrashGenerationServer(); + + // Perform initialization steps needed to start listening to clients. + // + // Return true if initialization is successful; false otherwise. + bool Start(); + + // Stop the server. + void Stop(); + + // Create a "channel" that can be used by clients to report crashes + // to a CrashGenerationServer. |*server_fd| should be passed to + // this class's constructor, and |*client_fd| should be passed to + // the ExceptionHandler constructor in the client process. + static bool CreateReportChannel(int* server_fd, int* client_fd); + +private: + // Run the server's event loop + void Run(); + + // Invoked when an child process (client) event occurs + // Returning true => "keep running", false => "exit loop" + bool ClientEvent(short revents); + + // Invoked when the controlling thread (main) event occurs + // Returning true => "keep running", false => "exit loop" + bool ControlEvent(short revents); + + // Return a unique filename at which a minidump can be written + bool MakeMinidumpFilename(string& outFilename); + + // Trampoline to |Run()| + static void* ThreadMain(void* arg); + + int server_fd_; + + OnClientDumpRequestCallback dump_callback_; + void* dump_context_; + + OnClientExitingCallback exit_callback_; + void* exit_context_; + + bool generate_dumps_; + + string dump_dir_; + + bool started_; + + pthread_t thread_; + int control_pipe_in_; + int control_pipe_out_; + + // disable these + CrashGenerationServer(const CrashGenerationServer&); + CrashGenerationServer& operator=(const CrashGenerationServer&); +}; + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/data/linux-gate-amd.sym b/toolkit/crashreporter/google-breakpad/src/client/linux/data/linux-gate-amd.sym new file mode 100644 index 000000000..e042a5ec4 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/data/linux-gate-amd.sym @@ -0,0 +1,3 @@ +MODULE Linux x86 B8CFDE93002D54DA1900A40AA1BD67690 linux-gate.so +PUBLIC 400 0 __kernel_vsyscall +STACK WIN 4 400 100 1 1 0 0 0 0 0 1 diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/data/linux-gate-intel.sym b/toolkit/crashreporter/google-breakpad/src/client/linux/data/linux-gate-intel.sym new file mode 100644 index 000000000..c209c2375 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/data/linux-gate-intel.sym @@ -0,0 +1,3 @@ +MODULE Linux x86 4FBDA58B5A1DF5A379E3CF19A235EA090 linux-gate.so +PUBLIC 400 0 __kernel_vsyscall +STACK WIN 4 400 200 3 3 0 0 0 0 0 1
\ No newline at end of file diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/dump_writer_common/mapping_info.h b/toolkit/crashreporter/google-breakpad/src/client/linux/dump_writer_common/mapping_info.h new file mode 100644 index 000000000..5f247cfd4 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/dump_writer_common/mapping_info.h @@ -0,0 +1,61 @@ +// Copyright (c) 2014, 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_LINUX_DUMP_WRITER_COMMON_MAPPING_INFO_H_ +#define CLIENT_LINUX_DUMP_WRITER_COMMON_MAPPING_INFO_H_ + +#include <limits.h> +#include <list> +#include <stdint.h> + +#include "google_breakpad/common/minidump_format.h" + +namespace google_breakpad { + +// One of these is produced for each mapping in the process (i.e. line in +// /proc/$x/maps). +struct MappingInfo { + uintptr_t start_addr; + size_t size; + size_t offset; // offset into the backed file. + bool exec; // true if the mapping has the execute bit set. + char name[NAME_MAX]; +}; + +struct MappingEntry { + MappingInfo first; + uint8_t second[sizeof(MDGUID)]; +}; + +// A list of <MappingInfo, GUID> +typedef std::list<MappingEntry> MappingList; + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_DUMP_WRITER_COMMON_MAPPING_INFO_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/dump_writer_common/raw_context_cpu.h b/toolkit/crashreporter/google-breakpad/src/client/linux/dump_writer_common/raw_context_cpu.h new file mode 100644 index 000000000..e2ef45df5 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/dump_writer_common/raw_context_cpu.h @@ -0,0 +1,53 @@ +// Copyright (c) 2014, 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_LINUX_DUMP_WRITER_COMMON_RAW_CONTEXT_CPU_H +#define CLIENT_LINUX_DUMP_WRITER_COMMON_RAW_CONTEXT_CPU_H + +#include "google_breakpad/common/minidump_format.h" + +namespace google_breakpad { + +#if defined(__i386__) +typedef MDRawContextX86 RawContextCPU; +#elif defined(__x86_64) +typedef MDRawContextAMD64 RawContextCPU; +#elif defined(__ARM_EABI__) +typedef MDRawContextARM RawContextCPU; +#elif defined(__aarch64__) +typedef MDRawContextARM64 RawContextCPU; +#elif defined(__mips__) +typedef MDRawContextMIPS RawContextCPU; +#else +#error "This code has not been ported to your platform yet." +#endif + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_DUMP_WRITER_COMMON_RAW_CONTEXT_CPU_H diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/dump_writer_common/thread_info.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/dump_writer_common/thread_info.cc new file mode 100644 index 000000000..0a1041d62 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/dump_writer_common/thread_info.cc @@ -0,0 +1,305 @@ +// Copyright (c) 2014, 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/linux/dump_writer_common/thread_info.h" + +#include <string.h> +#include <assert.h> + +#include "common/linux/linux_libc_support.h" +#include "google_breakpad/common/minidump_format.h" + +namespace { + +#if defined(__i386__) +// Write a uint16_t to memory +// out: memory location to write to +// v: value to write. +void U16(void* out, uint16_t v) { + my_memcpy(out, &v, sizeof(v)); +} + +// Write a uint32_t to memory +// out: memory location to write to +// v: value to write. +void U32(void* out, uint32_t v) { + my_memcpy(out, &v, sizeof(v)); +} +#endif + +} + +namespace google_breakpad { + +#if defined(__i386__) + +uintptr_t ThreadInfo::GetInstructionPointer() const { + return regs.eip; +} + +void ThreadInfo::FillCPUContext(RawContextCPU* out) const { + out->context_flags = MD_CONTEXT_X86_ALL; + + out->dr0 = dregs[0]; + out->dr1 = dregs[1]; + out->dr2 = dregs[2]; + out->dr3 = dregs[3]; + // 4 and 5 deliberatly omitted because they aren't included in the minidump + // format. + out->dr6 = dregs[6]; + out->dr7 = dregs[7]; + + out->gs = regs.xgs; + out->fs = regs.xfs; + out->es = regs.xes; + out->ds = regs.xds; + + out->edi = regs.edi; + out->esi = regs.esi; + out->ebx = regs.ebx; + out->edx = regs.edx; + out->ecx = regs.ecx; + out->eax = regs.eax; + + out->ebp = regs.ebp; + out->eip = regs.eip; + out->cs = regs.xcs; + out->eflags = regs.eflags; + out->esp = regs.esp; + out->ss = regs.xss; + + out->float_save.control_word = fpregs.cwd; + out->float_save.status_word = fpregs.swd; + out->float_save.tag_word = fpregs.twd; + out->float_save.error_offset = fpregs.fip; + out->float_save.error_selector = fpregs.fcs; + out->float_save.data_offset = fpregs.foo; + out->float_save.data_selector = fpregs.fos; + + // 8 registers * 10 bytes per register. + my_memcpy(out->float_save.register_area, fpregs.st_space, 10 * 8); + + // This matches the Intel fpsave format. + U16(out->extended_registers + 0, fpregs.cwd); + U16(out->extended_registers + 2, fpregs.swd); + U16(out->extended_registers + 4, fpregs.twd); + U16(out->extended_registers + 6, fpxregs.fop); + U32(out->extended_registers + 8, fpxregs.fip); + U16(out->extended_registers + 12, fpxregs.fcs); + U32(out->extended_registers + 16, fpregs.foo); + U16(out->extended_registers + 20, fpregs.fos); + U32(out->extended_registers + 24, fpxregs.mxcsr); + + my_memcpy(out->extended_registers + 32, &fpxregs.st_space, 128); + my_memcpy(out->extended_registers + 160, &fpxregs.xmm_space, 128); +} + +#elif defined(__x86_64) + +uintptr_t ThreadInfo::GetInstructionPointer() const { + return regs.rip; +} + +void ThreadInfo::FillCPUContext(RawContextCPU* out) const { + out->context_flags = MD_CONTEXT_AMD64_FULL | + MD_CONTEXT_AMD64_SEGMENTS; + + out->cs = regs.cs; + + out->ds = regs.ds; + out->es = regs.es; + out->fs = regs.fs; + out->gs = regs.gs; + + out->ss = regs.ss; + out->eflags = regs.eflags; + + out->dr0 = dregs[0]; + out->dr1 = dregs[1]; + out->dr2 = dregs[2]; + out->dr3 = dregs[3]; + // 4 and 5 deliberatly omitted because they aren't included in the minidump + // format. + out->dr6 = dregs[6]; + out->dr7 = dregs[7]; + + out->rax = regs.rax; + out->rcx = regs.rcx; + out->rdx = regs.rdx; + out->rbx = regs.rbx; + + out->rsp = regs.rsp; + + out->rbp = regs.rbp; + out->rsi = regs.rsi; + out->rdi = regs.rdi; + out->r8 = regs.r8; + out->r9 = regs.r9; + out->r10 = regs.r10; + out->r11 = regs.r11; + out->r12 = regs.r12; + out->r13 = regs.r13; + out->r14 = regs.r14; + out->r15 = regs.r15; + + out->rip = regs.rip; + + out->flt_save.control_word = fpregs.cwd; + out->flt_save.status_word = fpregs.swd; + out->flt_save.tag_word = fpregs.ftw; + out->flt_save.error_opcode = fpregs.fop; + out->flt_save.error_offset = fpregs.rip; + out->flt_save.error_selector = 0; // We don't have this. + out->flt_save.data_offset = fpregs.rdp; + out->flt_save.data_selector = 0; // We don't have this. + out->flt_save.mx_csr = fpregs.mxcsr; + out->flt_save.mx_csr_mask = fpregs.mxcr_mask; + + my_memcpy(&out->flt_save.float_registers, &fpregs.st_space, 8 * 16); + my_memcpy(&out->flt_save.xmm_registers, &fpregs.xmm_space, 16 * 16); +} + +#elif defined(__ARM_EABI__) + +uintptr_t ThreadInfo::GetInstructionPointer() const { + return regs.uregs[15]; +} + +void ThreadInfo::FillCPUContext(RawContextCPU* out) const { + out->context_flags = MD_CONTEXT_ARM_FULL; + + for (int i = 0; i < MD_CONTEXT_ARM_GPR_COUNT; ++i) + out->iregs[i] = regs.uregs[i]; + // No CPSR register in ThreadInfo(it's not accessible via ptrace) + out->cpsr = 0; +#if !defined(__ANDROID__) + out->float_save.fpscr = fpregs.fpsr | + (static_cast<uint64_t>(fpregs.fpcr) << 32); + // TODO: sort this out, actually collect floating point registers + my_memset(&out->float_save.regs, 0, sizeof(out->float_save.regs)); + my_memset(&out->float_save.extra, 0, sizeof(out->float_save.extra)); +#endif +} + +#elif defined(__aarch64__) + +uintptr_t ThreadInfo::GetInstructionPointer() const { + return regs.pc; +} + +void ThreadInfo::FillCPUContext(RawContextCPU* out) const { + out->context_flags = MD_CONTEXT_ARM64_FULL; + + out->cpsr = static_cast<uint32_t>(regs.pstate); + for (int i = 0; i < MD_CONTEXT_ARM64_REG_SP; ++i) + out->iregs[i] = regs.regs[i]; + out->iregs[MD_CONTEXT_ARM64_REG_SP] = regs.sp; + out->iregs[MD_CONTEXT_ARM64_REG_PC] = regs.pc; + + out->float_save.fpsr = fpregs.fpsr; + out->float_save.fpcr = fpregs.fpcr; + my_memcpy(&out->float_save.regs, &fpregs.vregs, + MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT * 16); +} + +#elif defined(__mips__) + +uintptr_t ThreadInfo::GetInstructionPointer() const { + return mcontext.pc; +} + +void ThreadInfo::FillCPUContext(RawContextCPU* out) const { +#if _MIPS_SIM == _ABI64 + out->context_flags = MD_CONTEXT_MIPS64_FULL; +#elif _MIPS_SIM == _ABIO32 + out->context_flags = MD_CONTEXT_MIPS_FULL; +#else +# error "This mips ABI is currently not supported (n32)" +#endif + + for (int i = 0; i < MD_CONTEXT_MIPS_GPR_COUNT; ++i) + out->iregs[i] = mcontext.gregs[i]; + + out->mdhi = mcontext.mdhi; + out->mdlo = mcontext.mdlo; + out->dsp_control = mcontext.dsp; + + out->hi[0] = mcontext.hi1; + out->lo[0] = mcontext.lo1; + out->hi[1] = mcontext.hi2; + out->lo[1] = mcontext.lo2; + out->hi[2] = mcontext.hi3; + out->lo[2] = mcontext.lo3; + + out->epc = mcontext.pc; + out->badvaddr = 0; // Not stored in mcontext + out->status = 0; // Not stored in mcontext + out->cause = 0; // Not stored in mcontext + + for (int i = 0; i < MD_FLOATINGSAVEAREA_MIPS_FPR_COUNT; ++i) + out->float_save.regs[i] = mcontext.fpregs.fp_r.fp_fregs[i]._fp_fregs; + + out->float_save.fpcsr = mcontext.fpc_csr; +#if _MIPS_SIM == _ABIO32 + out->float_save.fir = mcontext.fpc_eir; +#endif +} +#endif // __mips__ + +void ThreadInfo::GetGeneralPurposeRegisters(void** gp_regs, size_t* size) { + assert(gp_regs || size); +#if defined(__mips__) + if (gp_regs) + *gp_regs = mcontext.gregs; + if (size) + *size = sizeof(mcontext.gregs); +#else + if (gp_regs) + *gp_regs = ®s; + if (size) + *size = sizeof(regs); +#endif +} + +void ThreadInfo::GetFloatingPointRegisters(void** fp_regs, size_t* size) { + assert(fp_regs || size); +#if defined(__mips__) + if (fp_regs) + *fp_regs = &mcontext.fpregs; + if (size) + *size = sizeof(mcontext.fpregs); +#else + if (fp_regs) + *fp_regs = &fpregs; + if (size) + *size = sizeof(fpregs); +#endif +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/dump_writer_common/thread_info.h b/toolkit/crashreporter/google-breakpad/src/client/linux/dump_writer_common/thread_info.h new file mode 100644 index 000000000..99093d2e0 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/dump_writer_common/thread_info.h @@ -0,0 +1,91 @@ +// Copyright (c) 2014, 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_LINUX_DUMP_WRITER_COMMON_THREAD_INFO_H_ +#define CLIENT_LINUX_DUMP_WRITER_COMMON_THREAD_INFO_H_ + +#include <sys/ucontext.h> +#include <sys/user.h> + +#include "client/linux/dump_writer_common/raw_context_cpu.h" +#include "common/memory.h" +#include "google_breakpad/common/minidump_format.h" + +namespace google_breakpad { + +#if defined(__i386) || defined(__x86_64) +typedef __typeof__(((struct user*) 0)->u_debugreg[0]) debugreg_t; +#endif + +// We produce one of these structures for each thread in the crashed process. +struct ThreadInfo { + pid_t tgid; // thread group id + pid_t ppid; // parent process + + uintptr_t stack_pointer; // thread stack pointer + + +#if defined(__i386) || defined(__x86_64) + user_regs_struct regs; + user_fpregs_struct fpregs; + static const unsigned kNumDebugRegisters = 8; + debugreg_t dregs[8]; +#if defined(__i386) + user_fpxregs_struct fpxregs; +#endif // defined(__i386) + +#elif defined(__ARM_EABI__) + // Mimicking how strace does this(see syscall.c, search for GETREGS) + struct user_regs regs; + struct user_fpregs fpregs; +#elif defined(__aarch64__) + // Use the structures defined in <sys/user.h> + struct user_regs_struct regs; + struct user_fpsimd_struct fpregs; +#elif defined(__mips__) + // Use the structure defined in <sys/ucontext.h>. + mcontext_t mcontext; +#endif + + // Returns the instruction pointer (platform-dependent impl.). + uintptr_t GetInstructionPointer() const; + + // Fills a RawContextCPU using the context in the ThreadInfo object. + void FillCPUContext(RawContextCPU* out) const; + + // Returns the pointer and size of general purpose register area. + void GetGeneralPurposeRegisters(void** gp_regs, size_t* size); + + // Returns the pointer and size of float point register area. + void GetFloatingPointRegisters(void** fp_regs, size_t* size); +}; + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_DUMP_WRITER_COMMON_THREAD_INFO_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/dump_writer_common/ucontext_reader.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/dump_writer_common/ucontext_reader.cc new file mode 100644 index 000000000..93b4d9f85 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/dump_writer_common/ucontext_reader.cc @@ -0,0 +1,259 @@ +// Copyright (c) 2014, 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/linux/dump_writer_common/ucontext_reader.h" + +#include "common/linux/linux_libc_support.h" +#include "google_breakpad/common/minidump_format.h" + +namespace google_breakpad { + +// Minidump defines register structures which are different from the raw +// structures which we get from the kernel. These are platform specific +// functions to juggle the ucontext and user structures into minidump format. + +#if defined(__i386__) + +uintptr_t UContextReader::GetStackPointer(const ucontext_t* uc) { + return uc->uc_mcontext.gregs[REG_ESP]; +} + +uintptr_t UContextReader::GetInstructionPointer(const ucontext_t* uc) { + return uc->uc_mcontext.gregs[REG_EIP]; +} + +void UContextReader::FillCPUContext(RawContextCPU *out, const ucontext_t *uc, + const struct _libc_fpstate* fp) { + const greg_t* regs = uc->uc_mcontext.gregs; + + out->context_flags = MD_CONTEXT_X86_FULL | + MD_CONTEXT_X86_FLOATING_POINT; + + out->gs = regs[REG_GS]; + out->fs = regs[REG_FS]; + out->es = regs[REG_ES]; + out->ds = regs[REG_DS]; + + out->edi = regs[REG_EDI]; + out->esi = regs[REG_ESI]; + out->ebx = regs[REG_EBX]; + out->edx = regs[REG_EDX]; + out->ecx = regs[REG_ECX]; + out->eax = regs[REG_EAX]; + + out->ebp = regs[REG_EBP]; + out->eip = regs[REG_EIP]; + out->cs = regs[REG_CS]; + out->eflags = regs[REG_EFL]; + out->esp = regs[REG_UESP]; + out->ss = regs[REG_SS]; + + out->float_save.control_word = fp->cw; + out->float_save.status_word = fp->sw; + out->float_save.tag_word = fp->tag; + out->float_save.error_offset = fp->ipoff; + out->float_save.error_selector = fp->cssel; + out->float_save.data_offset = fp->dataoff; + out->float_save.data_selector = fp->datasel; + + // 8 registers * 10 bytes per register. + my_memcpy(out->float_save.register_area, fp->_st, 10 * 8); +} + +#elif defined(__x86_64) + +uintptr_t UContextReader::GetStackPointer(const ucontext_t* uc) { + return uc->uc_mcontext.gregs[REG_RSP]; +} + +uintptr_t UContextReader::GetInstructionPointer(const ucontext_t* uc) { + return uc->uc_mcontext.gregs[REG_RIP]; +} + +void UContextReader::FillCPUContext(RawContextCPU *out, const ucontext_t *uc, + const struct _libc_fpstate* fpregs) { + const greg_t* regs = uc->uc_mcontext.gregs; + + out->context_flags = MD_CONTEXT_AMD64_FULL; + + out->cs = regs[REG_CSGSFS] & 0xffff; + + out->fs = (regs[REG_CSGSFS] >> 32) & 0xffff; + out->gs = (regs[REG_CSGSFS] >> 16) & 0xffff; + + out->eflags = regs[REG_EFL]; + + out->rax = regs[REG_RAX]; + out->rcx = regs[REG_RCX]; + out->rdx = regs[REG_RDX]; + out->rbx = regs[REG_RBX]; + + out->rsp = regs[REG_RSP]; + out->rbp = regs[REG_RBP]; + out->rsi = regs[REG_RSI]; + out->rdi = regs[REG_RDI]; + out->r8 = regs[REG_R8]; + out->r9 = regs[REG_R9]; + out->r10 = regs[REG_R10]; + out->r11 = regs[REG_R11]; + out->r12 = regs[REG_R12]; + out->r13 = regs[REG_R13]; + out->r14 = regs[REG_R14]; + out->r15 = regs[REG_R15]; + + out->rip = regs[REG_RIP]; + + out->flt_save.control_word = fpregs->cwd; + out->flt_save.status_word = fpregs->swd; + out->flt_save.tag_word = fpregs->ftw; + out->flt_save.error_opcode = fpregs->fop; + out->flt_save.error_offset = fpregs->rip; + out->flt_save.data_offset = fpregs->rdp; + out->flt_save.error_selector = 0; // We don't have this. + out->flt_save.data_selector = 0; // We don't have this. + out->flt_save.mx_csr = fpregs->mxcsr; + out->flt_save.mx_csr_mask = fpregs->mxcr_mask; + my_memcpy(&out->flt_save.float_registers, &fpregs->_st, 8 * 16); + my_memcpy(&out->flt_save.xmm_registers, &fpregs->_xmm, 16 * 16); +} + +#elif defined(__ARM_EABI__) + +uintptr_t UContextReader::GetStackPointer(const ucontext_t* uc) { + return uc->uc_mcontext.arm_sp; +} + +uintptr_t UContextReader::GetInstructionPointer(const ucontext_t* uc) { + return uc->uc_mcontext.arm_pc; +} + +void UContextReader::FillCPUContext(RawContextCPU *out, const ucontext_t *uc) { + out->context_flags = MD_CONTEXT_ARM_FULL; + + out->iregs[0] = uc->uc_mcontext.arm_r0; + out->iregs[1] = uc->uc_mcontext.arm_r1; + out->iregs[2] = uc->uc_mcontext.arm_r2; + out->iregs[3] = uc->uc_mcontext.arm_r3; + out->iregs[4] = uc->uc_mcontext.arm_r4; + out->iregs[5] = uc->uc_mcontext.arm_r5; + out->iregs[6] = uc->uc_mcontext.arm_r6; + out->iregs[7] = uc->uc_mcontext.arm_r7; + out->iregs[8] = uc->uc_mcontext.arm_r8; + out->iregs[9] = uc->uc_mcontext.arm_r9; + out->iregs[10] = uc->uc_mcontext.arm_r10; + + out->iregs[11] = uc->uc_mcontext.arm_fp; + out->iregs[12] = uc->uc_mcontext.arm_ip; + out->iregs[13] = uc->uc_mcontext.arm_sp; + out->iregs[14] = uc->uc_mcontext.arm_lr; + out->iregs[15] = uc->uc_mcontext.arm_pc; + + out->cpsr = uc->uc_mcontext.arm_cpsr; + + // TODO: fix this after fixing ExceptionHandler + out->float_save.fpscr = 0; + my_memset(&out->float_save.regs, 0, sizeof(out->float_save.regs)); + my_memset(&out->float_save.extra, 0, sizeof(out->float_save.extra)); +} + +#elif defined(__aarch64__) + +uintptr_t UContextReader::GetStackPointer(const ucontext_t* uc) { + return uc->uc_mcontext.sp; +} + +uintptr_t UContextReader::GetInstructionPointer(const ucontext_t* uc) { + return uc->uc_mcontext.pc; +} + +void UContextReader::FillCPUContext(RawContextCPU *out, const ucontext_t *uc, + const struct fpsimd_context* fpregs) { + out->context_flags = MD_CONTEXT_ARM64_FULL; + + out->cpsr = static_cast<uint32_t>(uc->uc_mcontext.pstate); + for (int i = 0; i < MD_CONTEXT_ARM64_REG_SP; ++i) + out->iregs[i] = uc->uc_mcontext.regs[i]; + out->iregs[MD_CONTEXT_ARM64_REG_SP] = uc->uc_mcontext.sp; + out->iregs[MD_CONTEXT_ARM64_REG_PC] = uc->uc_mcontext.pc; + + out->float_save.fpsr = fpregs->fpsr; + out->float_save.fpcr = fpregs->fpcr; + my_memcpy(&out->float_save.regs, &fpregs->vregs, + MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT * 16); +} + +#elif defined(__mips__) + +uintptr_t UContextReader::GetStackPointer(const ucontext_t* uc) { + return uc->uc_mcontext.gregs[MD_CONTEXT_MIPS_REG_SP]; +} + +uintptr_t UContextReader::GetInstructionPointer(const ucontext_t* uc) { + return uc->uc_mcontext.pc; +} + +void UContextReader::FillCPUContext(RawContextCPU *out, const ucontext_t *uc) { +#if _MIPS_SIM == _ABI64 + out->context_flags = MD_CONTEXT_MIPS64_FULL; +#elif _MIPS_SIM == _ABIO32 + out->context_flags = MD_CONTEXT_MIPS_FULL; +#else +#error "This mips ABI is currently not supported (n32)" +#endif + + for (int i = 0; i < MD_CONTEXT_MIPS_GPR_COUNT; ++i) + out->iregs[i] = uc->uc_mcontext.gregs[i]; + + out->mdhi = uc->uc_mcontext.mdhi; + out->mdlo = uc->uc_mcontext.mdlo; + + out->hi[0] = uc->uc_mcontext.hi1; + out->hi[1] = uc->uc_mcontext.hi2; + out->hi[2] = uc->uc_mcontext.hi3; + out->lo[0] = uc->uc_mcontext.lo1; + out->lo[1] = uc->uc_mcontext.lo2; + out->lo[2] = uc->uc_mcontext.lo3; + out->dsp_control = uc->uc_mcontext.dsp; + + out->epc = uc->uc_mcontext.pc; + out->badvaddr = 0; // Not reported in signal context. + out->status = 0; // Not reported in signal context. + out->cause = 0; // Not reported in signal context. + + for (int i = 0; i < MD_FLOATINGSAVEAREA_MIPS_FPR_COUNT; ++i) + out->float_save.regs[i] = uc->uc_mcontext.fpregs.fp_r.fp_dregs[i]; + + out->float_save.fpcsr = uc->uc_mcontext.fpc_csr; +#if _MIPS_SIM == _ABIO32 + out->float_save.fir = uc->uc_mcontext.fpc_eir; // Unused. +#endif +} +#endif + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/dump_writer_common/ucontext_reader.h b/toolkit/crashreporter/google-breakpad/src/client/linux/dump_writer_common/ucontext_reader.h new file mode 100644 index 000000000..2369a9ad3 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/dump_writer_common/ucontext_reader.h @@ -0,0 +1,64 @@ +// Copyright (c) 2014, 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_LINUX_DUMP_WRITER_COMMON_UCONTEXT_READER_H +#define CLIENT_LINUX_DUMP_WRITER_COMMON_UCONTEXT_READER_H + +#include <sys/ucontext.h> +#include <sys/user.h> + +#include "client/linux/dump_writer_common/raw_context_cpu.h" +#include "common/memory.h" +#include "google_breakpad/common/minidump_format.h" + +namespace google_breakpad { + +// Wraps platform-dependent implementations of accessors to ucontext structs. +struct UContextReader { + static uintptr_t GetStackPointer(const ucontext_t* uc); + + static uintptr_t GetInstructionPointer(const ucontext_t* uc); + + // Juggle a arch-specific ucontext into a minidump format + // out: the minidump structure + // info: the collection of register structures. +#if defined(__i386__) || defined(__x86_64) + static void FillCPUContext(RawContextCPU *out, const ucontext_t *uc, + const struct _libc_fpstate* fp); +#elif defined(__aarch64__) + static void FillCPUContext(RawContextCPU *out, const ucontext_t *uc, + const struct fpsimd_context* fpregs); +#else + static void FillCPUContext(RawContextCPU *out, const ucontext_t *uc); +#endif +}; + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_DUMP_WRITER_COMMON_UCONTEXT_READER_H diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/handler/exception_handler.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/handler/exception_handler.cc new file mode 100644 index 000000000..288e0bb69 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/handler/exception_handler.cc @@ -0,0 +1,789 @@ +// Copyright (c) 2010 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. + +// The ExceptionHandler object installs signal handlers for a number of +// signals. We rely on the signal handler running on the thread which crashed +// in order to identify it. This is true of the synchronous signals (SEGV etc), +// but not true of ABRT. Thus, if you send ABRT to yourself in a program which +// uses ExceptionHandler, you need to use tgkill to direct it to the current +// thread. +// +// The signal flow looks like this: +// +// SignalHandler (uses a global stack of ExceptionHandler objects to find +// | one to handle the signal. If the first rejects it, try +// | the second etc...) +// V +// HandleSignal ----------------------------| (clones a new process which +// | | shares an address space with +// (wait for cloned | the crashed process. This +// process) | allows us to ptrace the crashed +// | | process) +// V V +// (set signal handler to ThreadEntry (static function to bounce +// SIG_DFL and rethrow, | back into the object) +// killing the crashed | +// process) V +// DoDump (writes minidump) +// | +// V +// sys_exit +// + +// This code is a little fragmented. Different functions of the ExceptionHandler +// class run in a number of different contexts. Some of them run in a normal +// context and are easy to code, others run in a compromised context and the +// restrictions at the top of minidump_writer.cc apply: no libc and use the +// alternative malloc. Each function should have comment above it detailing the +// context which it runs in. + +#include "client/linux/handler/exception_handler.h" + +#include <errno.h> +#include <fcntl.h> +#include <linux/limits.h> +#include <pthread.h> +#include <sched.h> +#include <signal.h> +#include <stdio.h> +#include <sys/mman.h> +#include <sys/prctl.h> +#include <sys/syscall.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <sys/signal.h> +#include <sys/ucontext.h> +#include <sys/user.h> +#include <ucontext.h> + +#include <algorithm> +#include <utility> +#include <vector> + +#include "common/basictypes.h" +#include "common/linux/linux_libc_support.h" +#include "common/memory.h" +#include "client/linux/log/log.h" +#include "client/linux/microdump_writer/microdump_writer.h" +#include "client/linux/minidump_writer/linux_dumper.h" +#include "client/linux/minidump_writer/minidump_writer.h" +#include "common/linux/eintr_wrapper.h" +#include "third_party/lss/linux_syscall_support.h" + +#if defined(__ANDROID__) +#include "linux/sched.h" +#endif + +#ifndef PR_SET_PTRACER +#define PR_SET_PTRACER 0x59616d61 +#endif + +// A wrapper for the tgkill syscall: send a signal to a specific thread. +static int tgkill(pid_t tgid, pid_t tid, int sig) { + return syscall(__NR_tgkill, tgid, tid, sig); + return 0; +} + +namespace google_breakpad { + +namespace { +// The list of signals which we consider to be crashes. The default action for +// all these signals must be Core (see man 7 signal) because we rethrow the +// signal after handling it and expect that it'll be fatal. +const int kExceptionSignals[] = { + SIGSEGV, SIGABRT, SIGFPE, SIGILL, SIGBUS, SIGTRAP +}; +const int kNumHandledSignals = + sizeof(kExceptionSignals) / sizeof(kExceptionSignals[0]); +struct sigaction old_handlers[kNumHandledSignals]; +bool handlers_installed = false; + +// InstallAlternateStackLocked will store the newly installed stack in new_stack +// and (if it exists) the previously installed stack in old_stack. +stack_t old_stack; +stack_t new_stack; +bool stack_installed = false; + +// Create an alternative stack to run the signal handlers on. This is done since +// the signal might have been caused by a stack overflow. +// Runs before crashing: normal context. +void InstallAlternateStackLocked() { + if (stack_installed) + return; + + memset(&old_stack, 0, sizeof(old_stack)); + memset(&new_stack, 0, sizeof(new_stack)); + + // SIGSTKSZ may be too small to prevent the signal handlers from overrunning + // the alternative stack. Ensure that the size of the alternative stack is + // large enough. + static const unsigned kSigStackSize = std::max(16384, SIGSTKSZ); + + // Only set an alternative stack if there isn't already one, or if the current + // one is too small. + if (sys_sigaltstack(NULL, &old_stack) == -1 || !old_stack.ss_sp || + old_stack.ss_size < kSigStackSize) { + new_stack.ss_sp = calloc(1, kSigStackSize); + new_stack.ss_size = kSigStackSize; + + if (sys_sigaltstack(&new_stack, NULL) == -1) { + free(new_stack.ss_sp); + return; + } + stack_installed = true; + } +} + +// Runs before crashing: normal context. +void RestoreAlternateStackLocked() { + if (!stack_installed) + return; + + stack_t current_stack; + if (sys_sigaltstack(NULL, ¤t_stack) == -1) + return; + + // Only restore the old_stack if the current alternative stack is the one + // installed by the call to InstallAlternateStackLocked. + if (current_stack.ss_sp == new_stack.ss_sp) { + if (old_stack.ss_sp) { + if (sys_sigaltstack(&old_stack, NULL) == -1) + return; + } else { + stack_t disable_stack; + disable_stack.ss_flags = SS_DISABLE; + if (sys_sigaltstack(&disable_stack, NULL) == -1) + return; + } + } + + free(new_stack.ss_sp); + stack_installed = false; +} + +void InstallDefaultHandler(int sig) { +#if defined(__ANDROID__) + // Android L+ expose signal and sigaction symbols that override the system + // ones. There is a bug in these functions where a request to set the handler + // to SIG_DFL is ignored. In that case, an infinite loop is entered as the + // signal is repeatedly sent to breakpad's signal handler. + // To work around this, directly call the system's sigaction. + struct kernel_sigaction sa; + memset(&sa, 0, sizeof(sa)); + sys_sigemptyset(&sa.sa_mask); + sa.sa_handler_ = SIG_DFL; + sa.sa_flags = SA_RESTART; + sys_rt_sigaction(sig, &sa, NULL, sizeof(kernel_sigset_t)); +#else + signal(sig, SIG_DFL); +#endif +} + +// The global exception handler stack. This is needed because there may exist +// multiple ExceptionHandler instances in a process. Each will have itself +// registered in this stack. +std::vector<ExceptionHandler*>* g_handler_stack_ = NULL; +pthread_mutex_t g_handler_stack_mutex_ = PTHREAD_MUTEX_INITIALIZER; + +// sizeof(CrashContext) can be too big w.r.t the size of alternatate stack +// for SignalHandler(). Keep the crash context as a .bss field. Exception +// handlers are serialized by the |g_handler_stack_mutex_| and at most one at a +// time can use |g_crash_context_|. +ExceptionHandler::CrashContext g_crash_context_; + +} // namespace + +// Runs before crashing: normal context. +ExceptionHandler::ExceptionHandler(const MinidumpDescriptor& descriptor, + FilterCallback filter, + MinidumpCallback callback, + void* callback_context, + bool install_handler, + const int server_fd) + : filter_(filter), + callback_(callback), + callback_context_(callback_context), + minidump_descriptor_(descriptor), + crash_handler_(NULL) { + if (server_fd >= 0) + crash_generation_client_.reset(CrashGenerationClient::TryCreate(server_fd)); + + if (!IsOutOfProcess() && !minidump_descriptor_.IsFD() && + !minidump_descriptor_.IsMicrodumpOnConsole()) + minidump_descriptor_.UpdatePath(); + +#if defined(__ANDROID__) + if (minidump_descriptor_.IsMicrodumpOnConsole()) + logger::initializeCrashLogWriter(); +#endif + + pthread_mutex_lock(&g_handler_stack_mutex_); + + // Pre-fault the crash context struct. This is to avoid failing due to OOM + // if handling an exception when the process ran out of virtual memory. + memset(&g_crash_context_, 0, sizeof(g_crash_context_)); + + if (!g_handler_stack_) + g_handler_stack_ = new std::vector<ExceptionHandler*>; + if (install_handler) { + InstallAlternateStackLocked(); + InstallHandlersLocked(); + } + g_handler_stack_->push_back(this); + pthread_mutex_unlock(&g_handler_stack_mutex_); +} + +// Runs before crashing: normal context. +ExceptionHandler::~ExceptionHandler() { + pthread_mutex_lock(&g_handler_stack_mutex_); + std::vector<ExceptionHandler*>::iterator handler = + std::find(g_handler_stack_->begin(), g_handler_stack_->end(), this); + g_handler_stack_->erase(handler); + if (g_handler_stack_->empty()) { + delete g_handler_stack_; + g_handler_stack_ = NULL; + RestoreAlternateStackLocked(); + RestoreHandlersLocked(); + } + pthread_mutex_unlock(&g_handler_stack_mutex_); +} + +// Runs before crashing: normal context. +// static +bool ExceptionHandler::InstallHandlersLocked() { + if (handlers_installed) + return false; + + // Fail if unable to store all the old handlers. + for (int i = 0; i < kNumHandledSignals; ++i) { + if (sigaction(kExceptionSignals[i], NULL, &old_handlers[i]) == -1) + return false; + } + + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + + // Mask all exception signals when we're handling one of them. + for (int i = 0; i < kNumHandledSignals; ++i) + sigaddset(&sa.sa_mask, kExceptionSignals[i]); + + sa.sa_sigaction = SignalHandler; + sa.sa_flags = SA_ONSTACK | SA_SIGINFO; + + for (int i = 0; i < kNumHandledSignals; ++i) { + if (sigaction(kExceptionSignals[i], &sa, NULL) == -1) { + // At this point it is impractical to back out changes, and so failure to + // install a signal is intentionally ignored. + } + } + handlers_installed = true; + return true; +} + +// This function runs in a compromised context: see the top of the file. +// Runs on the crashing thread. +// static +void ExceptionHandler::RestoreHandlersLocked() { + if (!handlers_installed) + return; + + for (int i = 0; i < kNumHandledSignals; ++i) { + if (sigaction(kExceptionSignals[i], &old_handlers[i], NULL) == -1) { + InstallDefaultHandler(kExceptionSignals[i]); + } + } + handlers_installed = false; +} + +// void ExceptionHandler::set_crash_handler(HandlerCallback callback) { +// crash_handler_ = callback; +// } + +// This function runs in a compromised context: see the top of the file. +// Runs on the crashing thread. +// static +void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) { + // All the exception signals are blocked at this point. + pthread_mutex_lock(&g_handler_stack_mutex_); + + // Sometimes, Breakpad runs inside a process where some other buggy code + // saves and restores signal handlers temporarily with 'signal' + // instead of 'sigaction'. This loses the SA_SIGINFO flag associated + // with this function. As a consequence, the values of 'info' and 'uc' + // become totally bogus, generally inducing a crash. + // + // The following code tries to detect this case. When it does, it + // resets the signal handlers with sigaction + SA_SIGINFO and returns. + // This forces the signal to be thrown again, but this time the kernel + // will call the function with the right arguments. + struct sigaction cur_handler; + if (sigaction(sig, NULL, &cur_handler) == 0 && + (cur_handler.sa_flags & SA_SIGINFO) == 0) { + // Reset signal handler with the right flags. + sigemptyset(&cur_handler.sa_mask); + sigaddset(&cur_handler.sa_mask, sig); + + cur_handler.sa_sigaction = SignalHandler; + cur_handler.sa_flags = SA_ONSTACK | SA_SIGINFO; + + if (sigaction(sig, &cur_handler, NULL) == -1) { + // When resetting the handler fails, try to reset the + // default one to avoid an infinite loop here. + InstallDefaultHandler(sig); + } + pthread_mutex_unlock(&g_handler_stack_mutex_); + return; + } + + bool handled = false; + for (int i = g_handler_stack_->size() - 1; !handled && i >= 0; --i) { + handled = (*g_handler_stack_)[i]->HandleSignal(sig, info, uc); + } + + // Upon returning from this signal handler, sig will become unmasked and then + // it will be retriggered. If one of the ExceptionHandlers handled it + // successfully, restore the default handler. Otherwise, restore the + // previously installed handler. Then, when the signal is retriggered, it will + // be delivered to the appropriate handler. + if (handled) { + InstallDefaultHandler(sig); + } else { + RestoreHandlersLocked(); + } + + pthread_mutex_unlock(&g_handler_stack_mutex_); + + // info->si_code <= 0 iff SI_FROMUSER (SI_FROMKERNEL otherwise). + if (info->si_code <= 0 || sig == SIGABRT) { + // This signal was triggered by somebody sending us the signal with kill(). + // In order to retrigger it, we have to queue a new signal by calling + // kill() ourselves. The special case (si_pid == 0 && sig == SIGABRT) is + // due to the kernel sending a SIGABRT from a user request via SysRQ. + if (tgkill(getpid(), syscall(__NR_gettid), sig) < 0) { + // If we failed to kill ourselves (e.g. because a sandbox disallows us + // to do so), we instead resort to terminating our process. This will + // result in an incorrect exit code. + _exit(1); + } + } else { + // This was a synchronous signal triggered by a hard fault (e.g. SIGSEGV). + // No need to reissue the signal. It will automatically trigger again, + // when we return from the signal handler. + } +} + +struct ThreadArgument { + pid_t pid; // the crashing process + const MinidumpDescriptor* minidump_descriptor; + ExceptionHandler* handler; + const void* context; // a CrashContext structure + size_t context_size; +}; + +// This is the entry function for the cloned process. We are in a compromised +// context here: see the top of the file. +// static +int ExceptionHandler::ThreadEntry(void *arg) { + const ThreadArgument *thread_arg = reinterpret_cast<ThreadArgument*>(arg); + + // Block here until the crashing process unblocks us when + // we're allowed to use ptrace + thread_arg->handler->WaitForContinueSignal(); + + return thread_arg->handler->DoDump(thread_arg->pid, thread_arg->context, + thread_arg->context_size) == false; +} + +// This function runs in a compromised context: see the top of the file. +// Runs on the crashing thread. +bool ExceptionHandler::HandleSignal(int sig, siginfo_t* info, void* uc) { + if (filter_ && !filter_(callback_context_)) + return false; + + // Allow ourselves to be dumped if the signal is trusted. + bool signal_trusted = info->si_code > 0; + bool signal_pid_trusted = info->si_code == SI_USER || + info->si_code == SI_TKILL; + if (signal_trusted || (signal_pid_trusted && info->si_pid == getpid())) { + sys_prctl(PR_SET_DUMPABLE, 1, 0, 0, 0); + } + + // Fill in all the holes in the struct to make Valgrind happy. + memset(&g_crash_context_, 0, sizeof(g_crash_context_)); + memcpy(&g_crash_context_.siginfo, info, sizeof(siginfo_t)); + memcpy(&g_crash_context_.context, uc, sizeof(ucontext_t)); +#if defined(__aarch64__) + ucontext_t* uc_ptr = (ucontext_t*)uc; + struct fpsimd_context* fp_ptr = + (struct fpsimd_context*)&uc_ptr->uc_mcontext.__reserved; + if (fp_ptr->head.magic == FPSIMD_MAGIC) { + memcpy(&g_crash_context_.float_state, fp_ptr, + sizeof(g_crash_context_.float_state)); + } +#elif !defined(__ARM_EABI__) && !defined(__mips__) + // FP state is not part of user ABI on ARM Linux. + // In case of MIPS Linux FP state is already part of ucontext_t + // and 'float_state' is not a member of CrashContext. + ucontext_t* uc_ptr = (ucontext_t*)uc; + if (uc_ptr->uc_mcontext.fpregs) { + memcpy(&g_crash_context_.float_state, uc_ptr->uc_mcontext.fpregs, + sizeof(g_crash_context_.float_state)); + } +#endif + g_crash_context_.tid = syscall(__NR_gettid); + if (crash_handler_ != NULL) { + if (crash_handler_(&g_crash_context_, sizeof(g_crash_context_), + callback_context_)) { + return true; + } + } + return GenerateDump(&g_crash_context_); +} + +// This is a public interface to HandleSignal that allows the client to +// generate a crash dump. This function may run in a compromised context. +bool ExceptionHandler::SimulateSignalDelivery(int sig) { + siginfo_t siginfo = {}; + // Mimic a trusted signal to allow tracing the process (see + // ExceptionHandler::HandleSignal(). + siginfo.si_code = SI_USER; + siginfo.si_pid = getpid(); + ucontext_t context; + getcontext(&context); + return HandleSignal(sig, &siginfo, &context); +} + +// This function may run in a compromised context: see the top of the file. +bool ExceptionHandler::GenerateDump(CrashContext *context) { + if (IsOutOfProcess()) + return crash_generation_client_->RequestDump(context, sizeof(*context)); + + // Allocating too much stack isn't a problem, and better to err on the side + // of caution than smash it into random locations. + static const unsigned kChildStackSize = 16000; + PageAllocator allocator; + uint8_t* stack = reinterpret_cast<uint8_t*>(allocator.Alloc(kChildStackSize)); + if (!stack) + return false; + // clone() needs the top-most address. (scrub just to be safe) + stack += kChildStackSize; + my_memset(stack - 16, 0, 16); + + ThreadArgument thread_arg; + thread_arg.handler = this; + thread_arg.minidump_descriptor = &minidump_descriptor_; + thread_arg.pid = getpid(); + thread_arg.context = context; + thread_arg.context_size = sizeof(*context); + + // We need to explicitly enable ptrace of parent processes on some + // kernels, but we need to know the PID of the cloned process before we + // can do this. Create a pipe here which we can use to block the + // cloned process after creating it, until we have explicitly enabled ptrace + if (sys_pipe(fdes) == -1) { + // Creating the pipe failed. We'll log an error but carry on anyway, + // as we'll probably still get a useful crash report. All that will happen + // is the write() and read() calls will fail with EBADF + static const char no_pipe_msg[] = "ExceptionHandler::GenerateDump " + "sys_pipe failed:"; + logger::write(no_pipe_msg, sizeof(no_pipe_msg) - 1); + logger::write(strerror(errno), strlen(strerror(errno))); + logger::write("\n", 1); + + // Ensure fdes[0] and fdes[1] are invalid file descriptors. + fdes[0] = fdes[1] = -1; + } + + const pid_t child = sys_clone( + ThreadEntry, stack, CLONE_FILES | CLONE_FS | CLONE_UNTRACED, + &thread_arg, NULL, NULL, NULL); + if (child == -1) { + sys_close(fdes[0]); + sys_close(fdes[1]); + return false; + } + + if (child != 0) { + static const char clonedMsg[] = + "ExceptionHandler::GenerateDump cloned child "; + char pidMsg[32]; + + unsigned int pidLen = my_uint_len(child); + my_uitos(pidMsg, child, pidLen); + + logger::write(clonedMsg, my_strlen(clonedMsg)); + logger::write(pidMsg, pidLen); + logger::write("\n", 1); + } else { + static const char childMsg[] = + "ExceptionHandler::GenerateDump I'm the child\n"; + logger::write(childMsg, my_strlen(childMsg)); + } + + // Allow the child to ptrace us + sys_prctl(PR_SET_PTRACER, child, 0, 0, 0); + SendContinueSignalToChild(); + int status; + const int r = HANDLE_EINTR(sys_waitpid(child, &status, __WALL)); + + sys_close(fdes[0]); + sys_close(fdes[1]); + + if (r == -1) { + static const char msg[] = "ExceptionHandler::GenerateDump waitpid failed:"; + logger::write(msg, sizeof(msg) - 1); + logger::write(strerror(errno), strlen(strerror(errno))); + logger::write("\n", 1); + } + + bool success = r != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0; + if (callback_) + success = callback_(minidump_descriptor_, callback_context_, success); + return success; +} + +// This function runs in a compromised context: see the top of the file. +void ExceptionHandler::SendContinueSignalToChild() { + static const char okToContinueMessage = 'a'; + int r; + r = HANDLE_EINTR(sys_write(fdes[1], &okToContinueMessage, sizeof(char))); + if (r == -1) { + static const char msg[] = "ExceptionHandler::SendContinueSignalToChild " + "sys_write failed:"; + logger::write(msg, sizeof(msg) - 1); + logger::write(strerror(errno), strlen(strerror(errno))); + logger::write("\n", 1); + } + + const char* msg = "ExceptionHandler::SendContinueSignalToChild sent continue signal to child\n"; + logger::write(msg, my_strlen(msg)); +} + +// This function runs in a compromised context: see the top of the file. +// Runs on the cloned process. +void ExceptionHandler::WaitForContinueSignal() { + int r; + char receivedMessage; + + const char* waitMsg = "ExceptionHandler::WaitForContinueSignal waiting for continue signal...\n"; + logger::write(waitMsg, my_strlen(waitMsg)); + + r = HANDLE_EINTR(sys_read(fdes[0], &receivedMessage, sizeof(char))); + if (r == -1) { + static const char msg[] = "ExceptionHandler::WaitForContinueSignal " + "sys_read failed:"; + logger::write(msg, sizeof(msg) - 1); + logger::write(strerror(errno), strlen(strerror(errno))); + logger::write("\n", 1); + } +} + +// This function runs in a compromised context: see the top of the file. +// Runs on the cloned process. +bool ExceptionHandler::DoDump(pid_t crashing_process, const void* context, + size_t context_size) { + if (minidump_descriptor_.IsMicrodumpOnConsole()) { + return google_breakpad::WriteMicrodump( + crashing_process, + context, + context_size, + mapping_list_, + *minidump_descriptor_.microdump_extra_info()); + } + if (minidump_descriptor_.IsFD()) { + return google_breakpad::WriteMinidump(minidump_descriptor_.fd(), + minidump_descriptor_.size_limit(), + crashing_process, + context, + context_size, + mapping_list_, + app_memory_list_); + } + return google_breakpad::WriteMinidump(minidump_descriptor_.path(), + minidump_descriptor_.size_limit(), + crashing_process, + context, + context_size, + mapping_list_, + app_memory_list_); +} + +// static +bool ExceptionHandler::WriteMinidump(const string& dump_path, + MinidumpCallback callback, + void* callback_context) { + MinidumpDescriptor descriptor(dump_path); + ExceptionHandler eh(descriptor, NULL, callback, callback_context, false, -1); + return eh.WriteMinidump(); +} + +// In order to making using EBP to calculate the desired value for ESP +// a valid operation, ensure that this function is compiled with a +// frame pointer using the following attribute. This attribute +// is supported on GCC but not on clang. +#if defined(__i386__) && defined(__GNUC__) && !defined(__clang__) +__attribute__((optimize("no-omit-frame-pointer"))) +#endif +bool ExceptionHandler::WriteMinidump() { + if (!IsOutOfProcess() && !minidump_descriptor_.IsFD() && + !minidump_descriptor_.IsMicrodumpOnConsole()) { + // Update the path of the minidump so that this can be called multiple times + // and new files are created for each minidump. This is done before the + // generation happens, as clients may want to access the MinidumpDescriptor + // after this call to find the exact path to the minidump file. + minidump_descriptor_.UpdatePath(); + } else if (minidump_descriptor_.IsFD()) { + // Reposition the FD to its beginning and resize it to get rid of the + // previous minidump info. + lseek(minidump_descriptor_.fd(), 0, SEEK_SET); + ignore_result(ftruncate(minidump_descriptor_.fd(), 0)); + } + + // Allow this process to be dumped. + sys_prctl(PR_SET_DUMPABLE, 1, 0, 0, 0); + + CrashContext context; + int getcontext_result = getcontext(&context.context); + if (getcontext_result) + return false; + +#if defined(__i386__) + // In CPUFillFromUContext in minidumpwriter.cc the stack pointer is retrieved + // from REG_UESP instead of from REG_ESP. REG_UESP is the user stack pointer + // and it only makes sense when running in kernel mode with a different stack + // pointer. When WriteMiniDump is called during normal processing REG_UESP is + // zero which leads to bad minidump files. + if (!context.context.uc_mcontext.gregs[REG_UESP]) { + // If REG_UESP is set to REG_ESP then that includes the stack space for the + // CrashContext object in this function, which is about 128 KB. Since the + // Linux dumper only records 32 KB of stack this would mean that nothing + // useful would be recorded. A better option is to set REG_UESP to REG_EBP, + // perhaps with a small negative offset in case there is any code that + // objects to them being equal. + context.context.uc_mcontext.gregs[REG_UESP] = + context.context.uc_mcontext.gregs[REG_EBP] - 16; + // The stack saving is based off of REG_ESP so it must be set to match the + // new REG_UESP. + context.context.uc_mcontext.gregs[REG_ESP] = + context.context.uc_mcontext.gregs[REG_UESP]; + } +#endif + +#if !defined(__ARM_EABI__) && !defined(__aarch64__) && !defined(__mips__) + // FPU state is not part of ARM EABI ucontext_t. + memcpy(&context.float_state, context.context.uc_mcontext.fpregs, + sizeof(context.float_state)); +#endif + context.tid = sys_gettid(); + + // Add an exception stream to the minidump for better reporting. + memset(&context.siginfo, 0, sizeof(context.siginfo)); + context.siginfo.si_signo = MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED; +#if defined(__i386__) + context.siginfo.si_addr = + reinterpret_cast<void*>(context.context.uc_mcontext.gregs[REG_EIP]); +#elif defined(__x86_64__) + context.siginfo.si_addr = + reinterpret_cast<void*>(context.context.uc_mcontext.gregs[REG_RIP]); +#elif defined(__arm__) + context.siginfo.si_addr = + reinterpret_cast<void*>(context.context.uc_mcontext.arm_pc); +#elif defined(__aarch64__) + context.siginfo.si_addr = + reinterpret_cast<void*>(context.context.uc_mcontext.pc); +#elif defined(__mips__) + context.siginfo.si_addr = + reinterpret_cast<void*>(context.context.uc_mcontext.pc); +#else +#error "This code has not been ported to your platform yet." +#endif + + return GenerateDump(&context); +} + +void ExceptionHandler::AddMappingInfo(const string& name, + const uint8_t identifier[sizeof(MDGUID)], + uintptr_t start_address, + size_t mapping_size, + size_t file_offset) { + MappingInfo info; + info.start_addr = start_address; + info.size = mapping_size; + info.offset = file_offset; + strncpy(info.name, name.c_str(), sizeof(info.name) - 1); + info.name[sizeof(info.name) - 1] = '\0'; + + MappingEntry mapping; + mapping.first = info; + memcpy(mapping.second, identifier, sizeof(MDGUID)); + mapping_list_.push_back(mapping); +} + +void ExceptionHandler::RegisterAppMemory(void* ptr, size_t length) { + AppMemoryList::iterator iter = + std::find(app_memory_list_.begin(), app_memory_list_.end(), ptr); + if (iter != app_memory_list_.end()) { + // Don't allow registering the same pointer twice. + return; + } + + AppMemory app_memory; + app_memory.ptr = ptr; + app_memory.length = length; + app_memory_list_.push_back(app_memory); +} + +void ExceptionHandler::UnregisterAppMemory(void* ptr) { + AppMemoryList::iterator iter = + std::find(app_memory_list_.begin(), app_memory_list_.end(), ptr); + if (iter != app_memory_list_.end()) { + app_memory_list_.erase(iter); + } +} + +// static +bool ExceptionHandler::WriteMinidumpForChild(pid_t child, + pid_t child_blamed_thread, + const string& dump_path, + MinidumpCallback callback, + void* callback_context) { + // This function is not run in a compromised context. + MinidumpDescriptor descriptor(dump_path); + descriptor.UpdatePath(); + if (!google_breakpad::WriteMinidump(descriptor.path(), + child, + child_blamed_thread)) + return false; + + return callback ? callback(descriptor, callback_context, true) : true; +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/handler/exception_handler.h b/toolkit/crashreporter/google-breakpad/src/client/linux/handler/exception_handler.h new file mode 100644 index 000000000..846df772f --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/handler/exception_handler.h @@ -0,0 +1,278 @@ +// Copyright (c) 2010 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_LINUX_HANDLER_EXCEPTION_HANDLER_H_ +#define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_ + +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <sys/ucontext.h> + +#include <string> + +#include "client/linux/crash_generation/crash_generation_client.h" +#include "client/linux/handler/minidump_descriptor.h" +#include "client/linux/minidump_writer/minidump_writer.h" +#include "common/scoped_ptr.h" +#include "common/using_std_string.h" +#include "google_breakpad/common/minidump_format.h" + +namespace google_breakpad { + +// ExceptionHandler +// +// ExceptionHandler can write a minidump file when an exception occurs, +// or when WriteMinidump() is called explicitly by your program. +// +// To have the exception handler write minidumps when an uncaught exception +// (crash) occurs, you should create an instance early in the execution +// of your program, and keep it around for the entire time you want to +// have crash handling active (typically, until shutdown). +// (NOTE): There should be only be one this kind of exception handler +// object per process. +// +// If you want to write minidumps without installing the exception handler, +// you can create an ExceptionHandler with install_handler set to false, +// then call WriteMinidump. You can also use this technique if you want to +// use different minidump callbacks for different call sites. +// +// In either case, a callback function is called when a minidump is written, +// which receives the full path or file descriptor of the minidump. The +// caller can collect and write additional application state to that minidump, +// and launch an external crash-reporting application. +// +// Caller should try to make the callbacks as crash-friendly as possible, +// it should avoid use heap memory allocation as much as possible. + +class ExceptionHandler { + public: + // A callback function to run before Breakpad performs any substantial + // processing of an exception. A FilterCallback is called before writing + // a minidump. |context| is the parameter supplied by the user as + // callback_context when the handler was created. + // + // If a FilterCallback returns true, Breakpad will continue processing, + // attempting to write a minidump. If a FilterCallback returns false, + // Breakpad will immediately report the exception as unhandled without + // writing a minidump, allowing another handler the opportunity to handle it. + typedef bool (*FilterCallback)(void *context); + + // A callback function to run after the minidump has been written. + // |descriptor| contains the file descriptor or file path containing the + // minidump. |context| is the parameter supplied by the user as + // callback_context when the handler was created. |succeeded| indicates + // whether a minidump file was successfully written. + // + // If an exception occurred and the callback returns true, Breakpad will + // treat the exception as fully-handled, suppressing any other handlers from + // being notified of the exception. If the callback returns false, Breakpad + // will treat the exception as unhandled, and allow another handler to handle + // it. If there are no other handlers, Breakpad will report the exception to + // the system as unhandled, allowing a debugger or native crash dialog the + // opportunity to handle the exception. Most callback implementations + // should normally return the value of |succeeded|, or when they wish to + // not report an exception of handled, false. Callbacks will rarely want to + // return true directly (unless |succeeded| is true). + typedef bool (*MinidumpCallback)(const MinidumpDescriptor& descriptor, + void* context, + bool succeeded); + + // In certain cases, a user may wish to handle the generation of the minidump + // themselves. In this case, they can install a handler callback which is + // called when a crash has occurred. If this function returns true, no other + // processing of occurs and the process will shortly be crashed. If this + // returns false, the normal processing continues. + typedef bool (*HandlerCallback)(const void* crash_context, + size_t crash_context_size, + void* context); + + // Creates a new ExceptionHandler instance to handle writing minidumps. + // Before writing a minidump, the optional |filter| callback will be called. + // Its return value determines whether or not Breakpad should write a + // minidump. The minidump content will be written to the file path or file + // descriptor from |descriptor|, and the optional |callback| is called after + // writing the dump file, as described above. + // If install_handler is true, then a minidump will be written whenever + // an unhandled exception occurs. If it is false, minidumps will only + // be written when WriteMinidump is called. + // If |server_fd| is valid, the minidump is generated out-of-process. If it + // is -1, in-process generation will always be used. + ExceptionHandler(const MinidumpDescriptor& descriptor, + FilterCallback filter, + MinidumpCallback callback, + void* callback_context, + bool install_handler, + const int server_fd); + ~ExceptionHandler(); + + const MinidumpDescriptor& minidump_descriptor() const { + return minidump_descriptor_; + } + + void set_minidump_descriptor(const MinidumpDescriptor& descriptor) { + minidump_descriptor_ = descriptor; + } + + void set_crash_handler(HandlerCallback callback) { + crash_handler_ = callback; + } + + void set_crash_generation_client(CrashGenerationClient* client) { + crash_generation_client_.reset(client); + } + + // Writes a minidump immediately. This can be used to capture the execution + // state independently of a crash. + // Returns true on success. + // If the ExceptionHandler has been created with a path, a new file is + // generated for each minidump. The file path can be retrieved in the + // MinidumpDescriptor passed to the MinidumpCallback or by accessing the + // MinidumpDescriptor directly from the ExceptionHandler (with + // minidump_descriptor()). + // If the ExceptionHandler has been created with a file descriptor, the file + // descriptor is repositioned to its beginning and the previous generated + // minidump is overwritten. + // Note that this method is not supposed to be called from a compromised + // context as it uses the heap. + bool WriteMinidump(); + + // Convenience form of WriteMinidump which does not require an + // ExceptionHandler instance. + static bool WriteMinidump(const string& dump_path, + MinidumpCallback callback, + void* callback_context); + + // Write a minidump of |child| immediately. This can be used to + // capture the execution state of |child| independently of a crash. + // Pass a meaningful |child_blamed_thread| to make that thread in + // the child process the one from which a crash signature is + // extracted. + // + // WARNING: the return of this function *must* happen before + // the code that will eventually reap |child| executes. + // Otherwise there's a pernicious race condition in which |child| + // exits, is reaped, another process created with its pid, then that + // new process dumped. + static bool WriteMinidumpForChild(pid_t child, + pid_t child_blamed_thread, + const string& dump_path, + MinidumpCallback callback, + void* callback_context); + + // This structure is passed to minidump_writer.h:WriteMinidump via an opaque + // blob. It shouldn't be needed in any user code. + struct CrashContext { + siginfo_t siginfo; + pid_t tid; // the crashing thread. + ucontext_t context; +#if !defined(__ARM_EABI__) && !defined(__mips__) + // #ifdef this out because FP state is not part of user ABI for Linux ARM. + // In case of MIPS Linux FP state is already part of struct + // ucontext so 'float_state' is not required. + fpstate_t float_state; +#endif + }; + + // Returns whether out-of-process dump generation is used or not. + bool IsOutOfProcess() const { + return crash_generation_client_.get() != NULL; + } + + // Add information about a memory mapping. This can be used if + // a custom library loader is used that maps things in a way + // that the linux dumper can't handle by reading the maps file. + void AddMappingInfo(const string& name, + const uint8_t identifier[sizeof(MDGUID)], + uintptr_t start_address, + size_t mapping_size, + size_t file_offset); + + // Register a block of memory of length bytes starting at address ptr + // to be copied to the minidump when a crash happens. + void RegisterAppMemory(void* ptr, size_t length); + + // Unregister a block of memory that was registered with RegisterAppMemory. + void UnregisterAppMemory(void* ptr); + + // Force signal handling for the specified signal. + bool SimulateSignalDelivery(int sig); + + // Report a crash signal from an SA_SIGINFO signal handler. + bool HandleSignal(int sig, siginfo_t* info, void* uc); + + private: + // Save the old signal handlers and install new ones. + static bool InstallHandlersLocked(); + // Restore the old signal handlers. + static void RestoreHandlersLocked(); + + void PreresolveSymbols(); + bool GenerateDump(CrashContext *context); + void SendContinueSignalToChild(); + void WaitForContinueSignal(); + + static void SignalHandler(int sig, siginfo_t* info, void* uc); + static int ThreadEntry(void* arg); + bool DoDump(pid_t crashing_process, const void* context, + size_t context_size); + + const FilterCallback filter_; + const MinidumpCallback callback_; + void* const callback_context_; + + scoped_ptr<CrashGenerationClient> crash_generation_client_; + + MinidumpDescriptor minidump_descriptor_; + + // Must be volatile. The compiler is unaware of the code which runs in + // the signal handler which reads this variable. Without volatile the + // compiler is free to optimise away writes to this variable which it + // believes are never read. + volatile HandlerCallback crash_handler_; + + // We need to explicitly enable ptrace of parent processes on some + // kernels, but we need to know the PID of the cloned process before we + // can do this. We create a pipe which we can use to block the + // cloned process after creating it, until we have explicitly enabled + // ptrace. This is used to store the file descriptors for the pipe + int fdes[2]; + + // Callers can add extra info about mappings for cases where the + // dumper code cannot extract enough information from /proc/<pid>/maps. + MappingList mapping_list_; + + // Callers can request additional memory regions to be included in + // the dump. + AppMemoryList app_memory_list_; +}; + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/handler/exception_handler_unittest.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/handler/exception_handler_unittest.cc new file mode 100644 index 000000000..17d84cf7b --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/handler/exception_handler_unittest.cc @@ -0,0 +1,1179 @@ +// Copyright (c) 2010 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 <stdint.h> +#include <unistd.h> +#include <signal.h> +#include <sys/mman.h> +#include <sys/poll.h> +#include <sys/socket.h> +#include <sys/uio.h> +#include <sys/wait.h> +#if defined(__mips__) +#include <sys/cachectl.h> +#endif + +#include <string> + +#include "breakpad_googletest_includes.h" +#include "client/linux/handler/exception_handler.h" +#include "client/linux/minidump_writer/minidump_writer.h" +#include "common/linux/eintr_wrapper.h" +#include "common/linux/ignore_ret.h" +#include "common/linux/linux_libc_support.h" +#include "common/tests/auto_tempdir.h" +#include "common/using_std_string.h" +#include "third_party/lss/linux_syscall_support.h" +#include "google_breakpad/processor/minidump.h" + +using namespace google_breakpad; + +namespace { + +// Flush the instruction cache for a given memory range. +// Only required on ARM and mips. +void FlushInstructionCache(const char* memory, uint32_t memory_size) { +#if defined(__arm__) + long begin = reinterpret_cast<long>(memory); + long end = begin + static_cast<long>(memory_size); +# if defined(__ANDROID__) + // Provided by Android's <unistd.h> + cacheflush(begin, end, 0); +# elif defined(__linux__) + // GLibc/ARM doesn't provide a wrapper for it, do a direct syscall. +# ifndef __ARM_NR_cacheflush +# define __ARM_NR_cacheflush 0xf0002 +# endif + syscall(__ARM_NR_cacheflush, begin, end, 0); +# else +# error "Your operating system is not supported yet" +# endif +#elif defined(__mips__) +# if defined(__ANDROID__) + // Provided by Android's <unistd.h> + long begin = reinterpret_cast<long>(memory); + long end = begin + static_cast<long>(memory_size); +#if _MIPS_SIM == _ABIO32 + cacheflush(begin, end, 0); +#else + syscall(__NR_cacheflush, begin, end, ICACHE); +#endif +# elif defined(__linux__) + // See http://www.linux-mips.org/wiki/Cacheflush_Syscall. + cacheflush(const_cast<char*>(memory), memory_size, ICACHE); +# else +# error "Your operating system is not supported yet" +# endif +#endif +} + +void sigchld_handler(int signo) { } + +int CreateTMPFile(const string& dir, string* path) { + string file = dir + "/exception-handler-unittest.XXXXXX"; + const char* c_file = file.c_str(); + // Copy that string, mkstemp needs a C string it can modify. + char* c_path = strdup(c_file); + const int fd = mkstemp(c_path); + if (fd >= 0) + *path = c_path; + free(c_path); + return fd; +} + +class ExceptionHandlerTest : public ::testing::Test { + protected: + void SetUp() { + // We need to be able to wait for children, so SIGCHLD cannot be SIG_IGN. + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = sigchld_handler; + ASSERT_NE(sigaction(SIGCHLD, &sa, &old_action), -1); + } + + void TearDown() { + sigaction(SIGCHLD, &old_action, NULL); + } + + struct sigaction old_action; +}; + + +void WaitForProcessToTerminate(pid_t process_id, int expected_status) { + int status; + ASSERT_NE(HANDLE_EINTR(waitpid(process_id, &status, 0)), -1); + ASSERT_TRUE(WIFSIGNALED(status)); + ASSERT_EQ(expected_status, WTERMSIG(status)); +} + +// Reads the minidump path sent over the pipe |fd| and sets it in |path|. +void ReadMinidumpPathFromPipe(int fd, string* path) { + struct pollfd pfd; + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = fd; + pfd.events = POLLIN | POLLERR; + + const int r = HANDLE_EINTR(poll(&pfd, 1, 0)); + ASSERT_EQ(1, r); + ASSERT_TRUE(pfd.revents & POLLIN); + + int32_t len; + ASSERT_EQ(static_cast<ssize_t>(sizeof(len)), read(fd, &len, sizeof(len))); + ASSERT_LT(len, 2048); + char* filename = static_cast<char*>(malloc(len + 1)); + ASSERT_EQ(len, read(fd, filename, len)); + filename[len] = 0; + close(fd); + *path = filename; + free(filename); +} + +} // namespace + +TEST(ExceptionHandlerTest, SimpleWithPath) { + AutoTempDir temp_dir; + ExceptionHandler handler( + MinidumpDescriptor(temp_dir.path()), NULL, NULL, NULL, true, -1); + EXPECT_EQ(temp_dir.path(), handler.minidump_descriptor().directory()); + string temp_subdir = temp_dir.path() + "/subdir"; + handler.set_minidump_descriptor(MinidumpDescriptor(temp_subdir)); + EXPECT_EQ(temp_subdir, handler.minidump_descriptor().directory()); +} + +TEST(ExceptionHandlerTest, SimpleWithFD) { + AutoTempDir temp_dir; + string path; + const int fd = CreateTMPFile(temp_dir.path(), &path); + ExceptionHandler handler(MinidumpDescriptor(fd), NULL, NULL, NULL, true, -1); + close(fd); +} + +static bool DoneCallback(const MinidumpDescriptor& descriptor, + void* context, + bool succeeded) { + if (!succeeded) + return false; + + if (!descriptor.IsFD()) { + int fd = reinterpret_cast<intptr_t>(context); + uint32_t len = 0; + len = my_strlen(descriptor.path()); + IGNORE_RET(HANDLE_EINTR(sys_write(fd, &len, sizeof(len)))); + IGNORE_RET(HANDLE_EINTR(sys_write(fd, descriptor.path(), len))); + } + return true; +} + +#ifndef ADDRESS_SANITIZER + +// This is a replacement for "*reinterpret_cast<volatile int*>(NULL) = 0;" +// It is needed because GCC is allowed to assume that the program will +// not execute any undefined behavior (UB) operation. Further, when GCC +// observes that UB statement is reached, it can assume that all statements +// leading to the UB one are never executed either, and can completely +// optimize them out. In the case of ExceptionHandlerTest::ExternalDumper, +// GCC-4.9 optimized out the entire set up of ExceptionHandler, causing +// test failure. +volatile int *p_null; // external linkage, so GCC can't tell that it + // remains NULL. Volatile just for a good measure. +static void DoNullPointerDereference() { + *p_null = 1; +} + +void ChildCrash(bool use_fd) { + AutoTempDir temp_dir; + int fds[2] = {0}; + int minidump_fd = -1; + string minidump_path; + if (use_fd) { + minidump_fd = CreateTMPFile(temp_dir.path(), &minidump_path); + } else { + ASSERT_NE(pipe(fds), -1); + } + + const pid_t child = fork(); + if (child == 0) { + { + google_breakpad::scoped_ptr<ExceptionHandler> handler; + if (use_fd) { + handler.reset(new ExceptionHandler(MinidumpDescriptor(minidump_fd), + NULL, NULL, NULL, true, -1)); + } else { + close(fds[0]); // Close the reading end. + void* fd_param = reinterpret_cast<void*>(fds[1]); + handler.reset(new ExceptionHandler(MinidumpDescriptor(temp_dir.path()), + NULL, DoneCallback, fd_param, + true, -1)); + } + // Crash with the exception handler in scope. + DoNullPointerDereference(); + } + } + if (!use_fd) + close(fds[1]); // Close the writting end. + + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGSEGV)); + + if (!use_fd) + ASSERT_NO_FATAL_FAILURE(ReadMinidumpPathFromPipe(fds[0], &minidump_path)); + + struct stat st; + ASSERT_EQ(0, stat(minidump_path.c_str(), &st)); + ASSERT_GT(st.st_size, 0); + unlink(minidump_path.c_str()); +} + +TEST(ExceptionHandlerTest, ChildCrashWithPath) { + ASSERT_NO_FATAL_FAILURE(ChildCrash(false)); +} + +TEST(ExceptionHandlerTest, ChildCrashWithFD) { + ASSERT_NO_FATAL_FAILURE(ChildCrash(true)); +} + +static bool DoneCallbackReturnFalse(const MinidumpDescriptor& descriptor, + void* context, + bool succeeded) { + return false; +} + +static bool DoneCallbackReturnTrue(const MinidumpDescriptor& descriptor, + void* context, + bool succeeded) { + return true; +} + +static bool DoneCallbackRaiseSIGKILL(const MinidumpDescriptor& descriptor, + void* context, + bool succeeded) { + raise(SIGKILL); + return true; +} + +static bool FilterCallbackReturnFalse(void* context) { + return false; +} + +static bool FilterCallbackReturnTrue(void* context) { + return true; +} + +// SIGKILL cannot be blocked and a handler cannot be installed for it. In the +// following tests, if the child dies with signal SIGKILL, then the signal was +// redelivered to this handler. If the child dies with SIGSEGV then it wasn't. +static void RaiseSIGKILL(int sig) { + raise(SIGKILL); +} + +static bool InstallRaiseSIGKILL() { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = RaiseSIGKILL; + return sigaction(SIGSEGV, &sa, NULL) != -1; +} + +static void CrashWithCallbacks(ExceptionHandler::FilterCallback filter, + ExceptionHandler::MinidumpCallback done, + string path) { + ExceptionHandler handler( + MinidumpDescriptor(path), filter, done, NULL, true, -1); + // Crash with the exception handler in scope. + DoNullPointerDereference(); +} + +TEST(ExceptionHandlerTest, RedeliveryOnFilterCallbackFalse) { + AutoTempDir temp_dir; + + const pid_t child = fork(); + if (child == 0) { + ASSERT_TRUE(InstallRaiseSIGKILL()); + CrashWithCallbacks(FilterCallbackReturnFalse, NULL, temp_dir.path()); + } + + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGKILL)); +} + +TEST(ExceptionHandlerTest, RedeliveryOnDoneCallbackFalse) { + AutoTempDir temp_dir; + + const pid_t child = fork(); + if (child == 0) { + ASSERT_TRUE(InstallRaiseSIGKILL()); + CrashWithCallbacks(NULL, DoneCallbackReturnFalse, temp_dir.path()); + } + + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGKILL)); +} + +TEST(ExceptionHandlerTest, NoRedeliveryOnDoneCallbackTrue) { + AutoTempDir temp_dir; + + const pid_t child = fork(); + if (child == 0) { + ASSERT_TRUE(InstallRaiseSIGKILL()); + CrashWithCallbacks(NULL, DoneCallbackReturnTrue, temp_dir.path()); + } + + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGSEGV)); +} + +TEST(ExceptionHandlerTest, NoRedeliveryOnFilterCallbackTrue) { + AutoTempDir temp_dir; + + const pid_t child = fork(); + if (child == 0) { + ASSERT_TRUE(InstallRaiseSIGKILL()); + CrashWithCallbacks(FilterCallbackReturnTrue, NULL, temp_dir.path()); + } + + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGSEGV)); +} + +TEST(ExceptionHandlerTest, RedeliveryToDefaultHandler) { + AutoTempDir temp_dir; + + const pid_t child = fork(); + if (child == 0) { + CrashWithCallbacks(FilterCallbackReturnFalse, NULL, temp_dir.path()); + } + + // As RaiseSIGKILL wasn't installed, the redelivery should just kill the child + // with SIGSEGV. + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGSEGV)); +} + +// Check that saving and restoring the signal handler with 'signal' +// instead of 'sigaction' doesn't make the Breakpad signal handler +// crash. See comments in ExceptionHandler::SignalHandler for full +// details. +TEST(ExceptionHandlerTest, RedeliveryOnBadSignalHandlerFlag) { + AutoTempDir temp_dir; + const pid_t child = fork(); + if (child == 0) { + // Install the RaiseSIGKILL handler for SIGSEGV. + ASSERT_TRUE(InstallRaiseSIGKILL()); + + // Create a new exception handler, this installs a new SIGSEGV + // handler, after saving the old one. + ExceptionHandler handler( + MinidumpDescriptor(temp_dir.path()), NULL, + DoneCallbackReturnFalse, NULL, true, -1); + + // Install the default SIGSEGV handler, saving the current one. + // Then re-install the current one with 'signal', this loses the + // SA_SIGINFO flag associated with the Breakpad handler. + sighandler_t old_handler = signal(SIGSEGV, SIG_DFL); + ASSERT_NE(reinterpret_cast<void*>(old_handler), + reinterpret_cast<void*>(SIG_ERR)); + ASSERT_NE(reinterpret_cast<void*>(signal(SIGSEGV, old_handler)), + reinterpret_cast<void*>(SIG_ERR)); + + // Crash with the exception handler in scope. + DoNullPointerDereference(); + } + // SIGKILL means Breakpad's signal handler didn't crash. + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGKILL)); +} + +TEST(ExceptionHandlerTest, StackedHandlersDeliveredToTop) { + AutoTempDir temp_dir; + + const pid_t child = fork(); + if (child == 0) { + ExceptionHandler bottom(MinidumpDescriptor(temp_dir.path()), + NULL, + NULL, + NULL, + true, + -1); + CrashWithCallbacks(NULL, DoneCallbackRaiseSIGKILL, temp_dir.path()); + } + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGKILL)); +} + +TEST(ExceptionHandlerTest, StackedHandlersNotDeliveredToBottom) { + AutoTempDir temp_dir; + + const pid_t child = fork(); + if (child == 0) { + ExceptionHandler bottom(MinidumpDescriptor(temp_dir.path()), + NULL, + DoneCallbackRaiseSIGKILL, + NULL, + true, + -1); + CrashWithCallbacks(NULL, NULL, temp_dir.path()); + } + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGSEGV)); +} + +TEST(ExceptionHandlerTest, StackedHandlersFilteredToBottom) { + AutoTempDir temp_dir; + + const pid_t child = fork(); + if (child == 0) { + ExceptionHandler bottom(MinidumpDescriptor(temp_dir.path()), + NULL, + DoneCallbackRaiseSIGKILL, + NULL, + true, + -1); + CrashWithCallbacks(FilterCallbackReturnFalse, NULL, temp_dir.path()); + } + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGKILL)); +} + +TEST(ExceptionHandlerTest, StackedHandlersUnhandledToBottom) { + AutoTempDir temp_dir; + + const pid_t child = fork(); + if (child == 0) { + ExceptionHandler bottom(MinidumpDescriptor(temp_dir.path()), + NULL, + DoneCallbackRaiseSIGKILL, + NULL, + true, + -1); + CrashWithCallbacks(NULL, DoneCallbackReturnFalse, temp_dir.path()); + } + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGKILL)); +} + +#endif // !ADDRESS_SANITIZER + +const unsigned char kIllegalInstruction[] = { +#if defined(__mips__) + // mfc2 zero,Impl - usually illegal in userspace. + 0x48, 0x00, 0x00, 0x48 +#else + // This crashes with SIGILL on x86/x86-64/arm. + 0xff, 0xff, 0xff, 0xff +#endif +}; + +// Test that memory around the instruction pointer is written +// to the dump as a MinidumpMemoryRegion. +TEST(ExceptionHandlerTest, InstructionPointerMemory) { + AutoTempDir temp_dir; + int fds[2]; + ASSERT_NE(pipe(fds), -1); + + // These are defined here so the parent can use them to check the + // data from the minidump afterwards. + const uint32_t kMemorySize = 256; // bytes + const int kOffset = kMemorySize / 2; + + const pid_t child = fork(); + if (child == 0) { + close(fds[0]); + ExceptionHandler handler(MinidumpDescriptor(temp_dir.path()), NULL, + DoneCallback, reinterpret_cast<void*>(fds[1]), + true, -1); + // Get some executable memory. + char* memory = + reinterpret_cast<char*>(mmap(NULL, + kMemorySize, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANON, + -1, + 0)); + if (!memory) + exit(0); + + // Write some instructions that will crash. Put them in the middle + // of the block of memory, because the minidump should contain 128 + // bytes on either side of the instruction pointer. + memcpy(memory + kOffset, kIllegalInstruction, sizeof(kIllegalInstruction)); + FlushInstructionCache(memory, kMemorySize); + + // Now execute the instructions, which should crash. + typedef void (*void_function)(void); + void_function memory_function = + reinterpret_cast<void_function>(memory + kOffset); + memory_function(); + } + close(fds[1]); + + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGILL)); + + string minidump_path; + ASSERT_NO_FATAL_FAILURE(ReadMinidumpPathFromPipe(fds[0], &minidump_path)); + + struct stat st; + ASSERT_EQ(0, stat(minidump_path.c_str(), &st)); + ASSERT_GT(st.st_size, 0); + + // Read the minidump. Locate the exception record and the + // memory list, and then ensure that there is a memory region + // in the memory list that covers the instruction pointer from + // the exception record. + Minidump minidump(minidump_path); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(exception); + ASSERT_TRUE(memory_list); + ASSERT_LT(0U, memory_list->region_count()); + + MinidumpContext* context = exception->GetContext(); + ASSERT_TRUE(context); + + uint64_t instruction_pointer; + ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer)); + + MinidumpMemoryRegion* region = + memory_list->GetMemoryRegionForAddress(instruction_pointer); + ASSERT_TRUE(region); + + EXPECT_EQ(kMemorySize, region->GetSize()); + const uint8_t* bytes = region->GetMemory(); + ASSERT_TRUE(bytes); + + uint8_t prefix_bytes[kOffset]; + uint8_t suffix_bytes[kMemorySize - kOffset - sizeof(kIllegalInstruction)]; + memset(prefix_bytes, 0, sizeof(prefix_bytes)); + memset(suffix_bytes, 0, sizeof(suffix_bytes)); + EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0); + EXPECT_TRUE(memcmp(bytes + kOffset, kIllegalInstruction, + sizeof(kIllegalInstruction)) == 0); + EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(kIllegalInstruction), + suffix_bytes, sizeof(suffix_bytes)) == 0); + + unlink(minidump_path.c_str()); +} + +// Test that the memory region around the instruction pointer is +// bounded correctly on the low end. +TEST(ExceptionHandlerTest, InstructionPointerMemoryMinBound) { + AutoTempDir temp_dir; + int fds[2]; + ASSERT_NE(pipe(fds), -1); + + // These are defined here so the parent can use them to check the + // data from the minidump afterwards. + const uint32_t kMemorySize = 256; // bytes + const int kOffset = 0; + + const pid_t child = fork(); + if (child == 0) { + close(fds[0]); + ExceptionHandler handler(MinidumpDescriptor(temp_dir.path()), NULL, + DoneCallback, reinterpret_cast<void*>(fds[1]), + true, -1); + // Get some executable memory. + char* memory = + reinterpret_cast<char*>(mmap(NULL, + kMemorySize, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANON, + -1, + 0)); + if (!memory) + exit(0); + + // Write some instructions that will crash. Put them in the middle + // of the block of memory, because the minidump should contain 128 + // bytes on either side of the instruction pointer. + memcpy(memory + kOffset, kIllegalInstruction, sizeof(kIllegalInstruction)); + FlushInstructionCache(memory, kMemorySize); + + // Now execute the instructions, which should crash. + typedef void (*void_function)(void); + void_function memory_function = + reinterpret_cast<void_function>(memory + kOffset); + memory_function(); + } + close(fds[1]); + + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGILL)); + + string minidump_path; + ASSERT_NO_FATAL_FAILURE(ReadMinidumpPathFromPipe(fds[0], &minidump_path)); + + struct stat st; + ASSERT_EQ(0, stat(minidump_path.c_str(), &st)); + ASSERT_GT(st.st_size, 0); + + // Read the minidump. Locate the exception record and the + // memory list, and then ensure that there is a memory region + // in the memory list that covers the instruction pointer from + // the exception record. + Minidump minidump(minidump_path); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(exception); + ASSERT_TRUE(memory_list); + ASSERT_LT(0U, memory_list->region_count()); + + MinidumpContext* context = exception->GetContext(); + ASSERT_TRUE(context); + + uint64_t instruction_pointer; + ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer)); + + MinidumpMemoryRegion* region = + memory_list->GetMemoryRegionForAddress(instruction_pointer); + ASSERT_TRUE(region); + + EXPECT_EQ(kMemorySize / 2, region->GetSize()); + const uint8_t* bytes = region->GetMemory(); + ASSERT_TRUE(bytes); + + uint8_t suffix_bytes[kMemorySize / 2 - sizeof(kIllegalInstruction)]; + memset(suffix_bytes, 0, sizeof(suffix_bytes)); + EXPECT_TRUE(memcmp(bytes + kOffset, kIllegalInstruction, + sizeof(kIllegalInstruction)) == 0); + EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(kIllegalInstruction), + suffix_bytes, sizeof(suffix_bytes)) == 0); + unlink(minidump_path.c_str()); +} + +// Test that the memory region around the instruction pointer is +// bounded correctly on the high end. +TEST(ExceptionHandlerTest, InstructionPointerMemoryMaxBound) { + AutoTempDir temp_dir; + int fds[2]; + ASSERT_NE(pipe(fds), -1); + + // These are defined here so the parent can use them to check the + // data from the minidump afterwards. + // Use 4k here because the OS will hand out a single page even + // if a smaller size is requested, and this test wants to + // test the upper bound of the memory range. + const uint32_t kMemorySize = 4096; // bytes + const int kOffset = kMemorySize - sizeof(kIllegalInstruction); + + const pid_t child = fork(); + if (child == 0) { + close(fds[0]); + ExceptionHandler handler(MinidumpDescriptor(temp_dir.path()), NULL, + DoneCallback, reinterpret_cast<void*>(fds[1]), + true, -1); + // Get some executable memory. + char* memory = + reinterpret_cast<char*>(mmap(NULL, + kMemorySize, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANON, + -1, + 0)); + if (!memory) + exit(0); + + // Write some instructions that will crash. Put them in the middle + // of the block of memory, because the minidump should contain 128 + // bytes on either side of the instruction pointer. + memcpy(memory + kOffset, kIllegalInstruction, sizeof(kIllegalInstruction)); + FlushInstructionCache(memory, kMemorySize); + + // Now execute the instructions, which should crash. + typedef void (*void_function)(void); + void_function memory_function = + reinterpret_cast<void_function>(memory + kOffset); + memory_function(); + } + close(fds[1]); + + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGILL)); + + string minidump_path; + ASSERT_NO_FATAL_FAILURE(ReadMinidumpPathFromPipe(fds[0], &minidump_path)); + + struct stat st; + ASSERT_EQ(0, stat(minidump_path.c_str(), &st)); + ASSERT_GT(st.st_size, 0); + + // Read the minidump. Locate the exception record and the memory list, and + // then ensure that there is a memory region in the memory list that covers + // the instruction pointer from the exception record. + Minidump minidump(minidump_path); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(exception); + ASSERT_TRUE(memory_list); + ASSERT_LT(0U, memory_list->region_count()); + + MinidumpContext* context = exception->GetContext(); + ASSERT_TRUE(context); + + uint64_t instruction_pointer; + ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer)); + + MinidumpMemoryRegion* region = + memory_list->GetMemoryRegionForAddress(instruction_pointer); + ASSERT_TRUE(region); + + const size_t kPrefixSize = 128; // bytes + EXPECT_EQ(kPrefixSize + sizeof(kIllegalInstruction), region->GetSize()); + const uint8_t* bytes = region->GetMemory(); + ASSERT_TRUE(bytes); + + uint8_t prefix_bytes[kPrefixSize]; + memset(prefix_bytes, 0, sizeof(prefix_bytes)); + EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0); + EXPECT_TRUE(memcmp(bytes + kPrefixSize, + kIllegalInstruction, sizeof(kIllegalInstruction)) == 0); + + unlink(minidump_path.c_str()); +} + +#ifndef ADDRESS_SANITIZER + +// Ensure that an extra memory block doesn't get added when the instruction +// pointer is not in mapped memory. +TEST(ExceptionHandlerTest, InstructionPointerMemoryNullPointer) { + AutoTempDir temp_dir; + int fds[2]; + ASSERT_NE(pipe(fds), -1); + + const pid_t child = fork(); + if (child == 0) { + close(fds[0]); + ExceptionHandler handler(MinidumpDescriptor(temp_dir.path()), NULL, + DoneCallback, reinterpret_cast<void*>(fds[1]), + true, -1); + // Try calling a NULL pointer. + typedef void (*void_function)(void); + // Volatile markings are needed to keep Clang from generating invalid + // opcodes. See http://crbug.com/498354 for details. + volatile void_function memory_function = + reinterpret_cast<void_function>(NULL); + memory_function(); + // not reached + exit(1); + } + close(fds[1]); + + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGSEGV)); + + string minidump_path; + ASSERT_NO_FATAL_FAILURE(ReadMinidumpPathFromPipe(fds[0], &minidump_path)); + + struct stat st; + ASSERT_EQ(0, stat(minidump_path.c_str(), &st)); + ASSERT_GT(st.st_size, 0); + + // Read the minidump. Locate the exception record and the + // memory list, and then ensure that there is a memory region + // in the memory list that covers the instruction pointer from + // the exception record. + Minidump minidump(minidump_path); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(exception); + ASSERT_TRUE(memory_list); + ASSERT_EQ(static_cast<unsigned int>(1), memory_list->region_count()); + + unlink(minidump_path.c_str()); +} + +#endif // !ADDRESS_SANITIZER + +// Test that anonymous memory maps can be annotated with names and IDs. +TEST(ExceptionHandlerTest, ModuleInfo) { + // These are defined here so the parent can use them to check the + // data from the minidump afterwards. + const uint32_t kMemorySize = sysconf(_SC_PAGESIZE); + const char* kMemoryName = "a fake module"; + const uint8_t kModuleGUID[sizeof(MDGUID)] = { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF + }; + const string module_identifier = "33221100554477668899AABBCCDDEEFF0"; + + // Get some memory. + char* memory = + reinterpret_cast<char*>(mmap(NULL, + kMemorySize, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, + -1, + 0)); + const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory); + ASSERT_TRUE(memory); + + AutoTempDir temp_dir; + ExceptionHandler handler( + MinidumpDescriptor(temp_dir.path()), NULL, NULL, NULL, true, -1); + + // Add info about the anonymous memory mapping. + handler.AddMappingInfo(kMemoryName, + kModuleGUID, + kMemoryAddress, + kMemorySize, + 0); + ASSERT_TRUE(handler.WriteMinidump()); + + const MinidumpDescriptor& minidump_desc = handler.minidump_descriptor(); + // Read the minidump. Load the module list, and ensure that the mmap'ed + // |memory| is listed with the given module name and debug ID. + Minidump minidump(minidump_desc.path()); + ASSERT_TRUE(minidump.Read()); + + MinidumpModuleList* module_list = minidump.GetModuleList(); + ASSERT_TRUE(module_list); + const MinidumpModule* module = + module_list->GetModuleForAddress(kMemoryAddress); + ASSERT_TRUE(module); + + EXPECT_EQ(kMemoryAddress, module->base_address()); + EXPECT_EQ(kMemorySize, module->size()); + EXPECT_EQ(kMemoryName, module->code_file()); + EXPECT_EQ(module_identifier, module->debug_identifier()); + + unlink(minidump_desc.path()); +} + +#ifndef ADDRESS_SANITIZER + +static const unsigned kControlMsgSize = + CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred)); + +static bool +CrashHandler(const void* crash_context, size_t crash_context_size, + void* context) { + const int fd = (intptr_t) context; + int fds[2]; + if (pipe(fds) == -1) { + // There doesn't seem to be any way to reliably handle + // this failure without the parent process hanging + // At least make sure that this process doesn't access + // unexpected file descriptors + fds[0] = -1; + fds[1] = -1; + } + struct kernel_msghdr msg = {0}; + struct kernel_iovec iov; + iov.iov_base = const_cast<void*>(crash_context); + iov.iov_len = crash_context_size; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + char cmsg[kControlMsgSize]; + memset(cmsg, 0, kControlMsgSize); + msg.msg_control = cmsg; + msg.msg_controllen = sizeof(cmsg); + + struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg); + hdr->cmsg_level = SOL_SOCKET; + hdr->cmsg_type = SCM_RIGHTS; + hdr->cmsg_len = CMSG_LEN(sizeof(int)); + *((int*) CMSG_DATA(hdr)) = fds[1]; + hdr = CMSG_NXTHDR((struct msghdr*) &msg, hdr); + hdr->cmsg_level = SOL_SOCKET; + hdr->cmsg_type = SCM_CREDENTIALS; + hdr->cmsg_len = CMSG_LEN(sizeof(struct ucred)); + struct ucred *cred = reinterpret_cast<struct ucred*>(CMSG_DATA(hdr)); + cred->uid = getuid(); + cred->gid = getgid(); + cred->pid = getpid(); + + ssize_t ret = HANDLE_EINTR(sys_sendmsg(fd, &msg, 0)); + sys_close(fds[1]); + if (ret <= 0) + return false; + + char b; + IGNORE_RET(HANDLE_EINTR(sys_read(fds[0], &b, 1))); + + return true; +} + +TEST(ExceptionHandlerTest, ExternalDumper) { + int fds[2]; + ASSERT_NE(socketpair(AF_UNIX, SOCK_DGRAM, 0, fds), -1); + static const int on = 1; + setsockopt(fds[0], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); + setsockopt(fds[1], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); + + const pid_t child = fork(); + if (child == 0) { + close(fds[0]); + ExceptionHandler handler(MinidumpDescriptor("/tmp1"), NULL, NULL, + reinterpret_cast<void*>(fds[1]), true, -1); + handler.set_crash_handler(CrashHandler); + DoNullPointerDereference(); + } + close(fds[1]); + struct msghdr msg = {0}; + struct iovec iov; + static const unsigned kCrashContextSize = + sizeof(ExceptionHandler::CrashContext); + char context[kCrashContextSize]; + char control[kControlMsgSize]; + iov.iov_base = context; + iov.iov_len = kCrashContextSize; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control; + msg.msg_controllen = kControlMsgSize; + + const ssize_t n = HANDLE_EINTR(recvmsg(fds[0], &msg, 0)); + ASSERT_EQ(static_cast<ssize_t>(kCrashContextSize), n); + ASSERT_EQ(kControlMsgSize, msg.msg_controllen); + ASSERT_EQ(static_cast<__typeof__(msg.msg_flags)>(0), msg.msg_flags); + ASSERT_EQ(0, close(fds[0])); + + pid_t crashing_pid = -1; + int signal_fd = -1; + for (struct cmsghdr *hdr = CMSG_FIRSTHDR(&msg); hdr; + hdr = CMSG_NXTHDR(&msg, hdr)) { + if (hdr->cmsg_level != SOL_SOCKET) + continue; + if (hdr->cmsg_type == SCM_RIGHTS) { + const unsigned len = hdr->cmsg_len - + (((uint8_t*)CMSG_DATA(hdr)) - (uint8_t*)hdr); + ASSERT_EQ(sizeof(int), len); + signal_fd = *(reinterpret_cast<int*>(CMSG_DATA(hdr))); + } else if (hdr->cmsg_type == SCM_CREDENTIALS) { + const struct ucred *cred = + reinterpret_cast<struct ucred*>(CMSG_DATA(hdr)); + crashing_pid = cred->pid; + } + } + + ASSERT_NE(crashing_pid, -1); + ASSERT_NE(signal_fd, -1); + + AutoTempDir temp_dir; + string templ = temp_dir.path() + "/exception-handler-unittest"; + ASSERT_TRUE(WriteMinidump(templ.c_str(), crashing_pid, context, + kCrashContextSize)); + static const char b = 0; + ASSERT_EQ(1, (HANDLE_EINTR(write(signal_fd, &b, 1)))); + ASSERT_EQ(0, close(signal_fd)); + + ASSERT_NO_FATAL_FAILURE(WaitForProcessToTerminate(child, SIGSEGV)); + + struct stat st; + ASSERT_EQ(0, stat(templ.c_str(), &st)); + ASSERT_GT(st.st_size, 0); + unlink(templ.c_str()); +} + +#endif // !ADDRESS_SANITIZER + +TEST(ExceptionHandlerTest, WriteMinidumpExceptionStream) { + AutoTempDir temp_dir; + ExceptionHandler handler(MinidumpDescriptor(temp_dir.path()), NULL, NULL, + NULL, false, -1); + ASSERT_TRUE(handler.WriteMinidump()); + + string minidump_path = handler.minidump_descriptor().path(); + + // Read the minidump and check the exception stream. + Minidump minidump(minidump_path); + ASSERT_TRUE(minidump.Read()); + MinidumpException* exception = minidump.GetException(); + ASSERT_TRUE(exception); + const MDRawExceptionStream* raw = exception->exception(); + ASSERT_TRUE(raw); + EXPECT_EQ(MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED, + raw->exception_record.exception_code); +} + +TEST(ExceptionHandlerTest, GenerateMultipleDumpsWithFD) { + AutoTempDir temp_dir; + string path; + const int fd = CreateTMPFile(temp_dir.path(), &path); + ExceptionHandler handler(MinidumpDescriptor(fd), NULL, NULL, NULL, false, -1); + ASSERT_TRUE(handler.WriteMinidump()); + // Check by the size of the data written to the FD that a minidump was + // generated. + off_t size = lseek(fd, 0, SEEK_CUR); + ASSERT_GT(size, 0); + + // Generate another minidump. + ASSERT_TRUE(handler.WriteMinidump()); + size = lseek(fd, 0, SEEK_CUR); + ASSERT_GT(size, 0); +} + +TEST(ExceptionHandlerTest, GenerateMultipleDumpsWithPath) { + AutoTempDir temp_dir; + ExceptionHandler handler(MinidumpDescriptor(temp_dir.path()), NULL, NULL, + NULL, false, -1); + ASSERT_TRUE(handler.WriteMinidump()); + + const MinidumpDescriptor& minidump_1 = handler.minidump_descriptor(); + struct stat st; + ASSERT_EQ(0, stat(minidump_1.path(), &st)); + ASSERT_GT(st.st_size, 0); + string minidump_1_path(minidump_1.path()); + // Check it is a valid minidump. + Minidump minidump1(minidump_1_path); + ASSERT_TRUE(minidump1.Read()); + unlink(minidump_1.path()); + + // Generate another minidump, it should go to a different file. + ASSERT_TRUE(handler.WriteMinidump()); + const MinidumpDescriptor& minidump_2 = handler.minidump_descriptor(); + ASSERT_EQ(0, stat(minidump_2.path(), &st)); + ASSERT_GT(st.st_size, 0); + string minidump_2_path(minidump_2.path()); + // Check it is a valid minidump. + Minidump minidump2(minidump_2_path); + ASSERT_TRUE(minidump2.Read()); + unlink(minidump_2.path()); + + // 2 distinct files should be produced. + ASSERT_STRNE(minidump_1_path.c_str(), minidump_2_path.c_str()); +} + +// Test that an additional memory region can be added to the minidump. +TEST(ExceptionHandlerTest, AdditionalMemory) { + const uint32_t kMemorySize = sysconf(_SC_PAGESIZE); + + // Get some heap memory. + uint8_t* memory = new uint8_t[kMemorySize]; + const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory); + ASSERT_TRUE(memory); + + // Stick some data into the memory so the contents can be verified. + for (uint32_t i = 0; i < kMemorySize; ++i) { + memory[i] = i % 255; + } + + AutoTempDir temp_dir; + ExceptionHandler handler( + MinidumpDescriptor(temp_dir.path()), NULL, NULL, NULL, true, -1); + + // Add the memory region to the list of memory to be included. + handler.RegisterAppMemory(memory, kMemorySize); + handler.WriteMinidump(); + + const MinidumpDescriptor& minidump_desc = handler.minidump_descriptor(); + + // Read the minidump. Ensure that the memory region is present + Minidump minidump(minidump_desc.path()); + ASSERT_TRUE(minidump.Read()); + + MinidumpMemoryList* dump_memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(dump_memory_list); + const MinidumpMemoryRegion* region = + dump_memory_list->GetMemoryRegionForAddress(kMemoryAddress); + ASSERT_TRUE(region); + + EXPECT_EQ(kMemoryAddress, region->GetBase()); + EXPECT_EQ(kMemorySize, region->GetSize()); + + // Verify memory contents. + EXPECT_EQ(0, memcmp(region->GetMemory(), memory, kMemorySize)); + + delete[] memory; +} + +// Test that a memory region that was previously registered +// can be unregistered. +TEST(ExceptionHandlerTest, AdditionalMemoryRemove) { + const uint32_t kMemorySize = sysconf(_SC_PAGESIZE); + + // Get some heap memory. + uint8_t* memory = new uint8_t[kMemorySize]; + const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory); + ASSERT_TRUE(memory); + + AutoTempDir temp_dir; + ExceptionHandler handler( + MinidumpDescriptor(temp_dir.path()), NULL, NULL, NULL, true, -1); + + // Add the memory region to the list of memory to be included. + handler.RegisterAppMemory(memory, kMemorySize); + + // ...and then remove it + handler.UnregisterAppMemory(memory); + handler.WriteMinidump(); + + const MinidumpDescriptor& minidump_desc = handler.minidump_descriptor(); + + // Read the minidump. Ensure that the memory region is not present. + Minidump minidump(minidump_desc.path()); + ASSERT_TRUE(minidump.Read()); + + MinidumpMemoryList* dump_memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(dump_memory_list); + const MinidumpMemoryRegion* region = + dump_memory_list->GetMemoryRegionForAddress(kMemoryAddress); + EXPECT_FALSE(region); + + delete[] memory; +} + +static bool SimpleCallback(const MinidumpDescriptor& descriptor, + void* context, + bool succeeded) { + string* filename = reinterpret_cast<string*>(context); + *filename = descriptor.path(); + return true; +} + +TEST(ExceptionHandlerTest, WriteMinidumpForChild) { + int fds[2]; + ASSERT_NE(-1, pipe(fds)); + + const pid_t child = fork(); + if (child == 0) { + close(fds[1]); + char b; + HANDLE_EINTR(read(fds[0], &b, sizeof(b))); + close(fds[0]); + syscall(__NR_exit); + } + close(fds[0]); + + AutoTempDir temp_dir; + string minidump_filename; + ASSERT_TRUE( + ExceptionHandler::WriteMinidumpForChild(child, child, + temp_dir.path(), SimpleCallback, + (void*)&minidump_filename)); + + Minidump minidump(minidump_filename); + ASSERT_TRUE(minidump.Read()); + // Check that the crashing thread is the main thread of |child| + MinidumpException* exception = minidump.GetException(); + ASSERT_TRUE(exception); + uint32_t thread_id; + ASSERT_TRUE(exception->GetThreadID(&thread_id)); + EXPECT_EQ(child, static_cast<int32_t>(thread_id)); + + const MDRawExceptionStream* raw = exception->exception(); + ASSERT_TRUE(raw); + EXPECT_EQ(MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED, + raw->exception_record.exception_code); + + close(fds[1]); + unlink(minidump_filename.c_str()); +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/handler/microdump_extra_info.h b/toolkit/crashreporter/google-breakpad/src/client/linux/handler/microdump_extra_info.h new file mode 100644 index 000000000..bf01f0c7b --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/handler/microdump_extra_info.h @@ -0,0 +1,52 @@ +// Copyright 2015 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_LINUX_HANDLER_MICRODUMP_EXTRA_INFO_H_ +#define CLIENT_LINUX_HANDLER_MICRODUMP_EXTRA_INFO_H_ + +namespace google_breakpad { + +struct MicrodumpExtraInfo { + // Strings pointed to by this struct are not copied, and are + // expected to remain valid for the lifetime of the process. + const char* build_fingerprint; + const char* product_info; + const char* gpu_fingerprint; + const char* process_type; + + MicrodumpExtraInfo() + : build_fingerprint(NULL), + product_info(NULL), + gpu_fingerprint(NULL), + process_type(NULL) {} +}; + +} + +#endif // CLIENT_LINUX_HANDLER_MICRODUMP_EXTRA_INFO_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/handler/minidump_descriptor.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/handler/minidump_descriptor.cc new file mode 100644 index 000000000..ce09153dd --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/handler/minidump_descriptor.cc @@ -0,0 +1,87 @@ +// 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 <stdio.h> + +#include "client/linux/handler/minidump_descriptor.h" + +#include "common/linux/guid_creator.h" + +namespace google_breakpad { + +//static +const MinidumpDescriptor::MicrodumpOnConsole + MinidumpDescriptor::kMicrodumpOnConsole = {}; + +MinidumpDescriptor::MinidumpDescriptor(const MinidumpDescriptor& descriptor) + : mode_(descriptor.mode_), + fd_(descriptor.fd_), + directory_(descriptor.directory_), + c_path_(NULL), + size_limit_(descriptor.size_limit_), + microdump_extra_info_(descriptor.microdump_extra_info_) { + // The copy constructor is not allowed to be called on a MinidumpDescriptor + // with a valid path_, as getting its c_path_ would require the heap which + // can cause problems in compromised environments. + assert(descriptor.path_.empty()); +} + +MinidumpDescriptor& MinidumpDescriptor::operator=( + const MinidumpDescriptor& descriptor) { + assert(descriptor.path_.empty()); + + mode_ = descriptor.mode_; + fd_ = descriptor.fd_; + directory_ = descriptor.directory_; + path_.clear(); + if (c_path_) { + // This descriptor already had a path set, so generate a new one. + c_path_ = NULL; + UpdatePath(); + } + size_limit_ = descriptor.size_limit_; + microdump_extra_info_ = descriptor.microdump_extra_info_; + return *this; +} + +void MinidumpDescriptor::UpdatePath() { + assert(mode_ == kWriteMinidumpToFile && !directory_.empty()); + + GUID guid; + char guid_str[kGUIDStringLength + 1]; + if (!CreateGUID(&guid) || !GUIDToString(&guid, guid_str, sizeof(guid_str))) { + assert(false); + } + + path_.clear(); + path_ = directory_ + "/" + guid_str + ".dmp"; + c_path_ = path_.c_str(); +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/handler/minidump_descriptor.h b/toolkit/crashreporter/google-breakpad/src/client/linux/handler/minidump_descriptor.h new file mode 100644 index 000000000..782a60a4e --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/handler/minidump_descriptor.h @@ -0,0 +1,149 @@ +// 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_LINUX_HANDLER_MINIDUMP_DESCRIPTOR_H_ +#define CLIENT_LINUX_HANDLER_MINIDUMP_DESCRIPTOR_H_ + +#include <assert.h> +#include <sys/types.h> + +#include <string> + +#include "client/linux/handler/microdump_extra_info.h" +#include "common/using_std_string.h" + +// This class describes how a crash dump should be generated, either: +// - Writing a full minidump to a file in a given directory (the actual path, +// inside the directory, is determined by this class). +// - Writing a full minidump to a given fd. +// - Writing a reduced microdump to the console (logcat on Android). +namespace google_breakpad { + +class MinidumpDescriptor { + public: + struct MicrodumpOnConsole {}; + static const MicrodumpOnConsole kMicrodumpOnConsole; + + MinidumpDescriptor() + : mode_(kUninitialized), + fd_(-1), + size_limit_(-1) {} + + explicit MinidumpDescriptor(const string& directory) + : mode_(kWriteMinidumpToFile), + fd_(-1), + directory_(directory), + c_path_(NULL), + size_limit_(-1) { + assert(!directory.empty()); + } + + explicit MinidumpDescriptor(int fd) + : mode_(kWriteMinidumpToFd), + fd_(fd), + c_path_(NULL), + size_limit_(-1) { + assert(fd != -1); + } + + explicit MinidumpDescriptor(const MicrodumpOnConsole&) + : mode_(kWriteMicrodumpToConsole), + fd_(-1), + size_limit_(-1) {} + + explicit MinidumpDescriptor(const MinidumpDescriptor& descriptor); + MinidumpDescriptor& operator=(const MinidumpDescriptor& descriptor); + + static MinidumpDescriptor getMicrodumpDescriptor(); + + bool IsFD() const { return mode_ == kWriteMinidumpToFd; } + + int fd() const { return fd_; } + + string directory() const { return directory_; } + + const char* path() const { return c_path_; } + + bool IsMicrodumpOnConsole() const { + return mode_ == kWriteMicrodumpToConsole; + } + + // Updates the path so it is unique. + // Should be called from a normal context: this methods uses the heap. + void UpdatePath(); + + off_t size_limit() const { return size_limit_; } + void set_size_limit(off_t limit) { size_limit_ = limit; } + + MicrodumpExtraInfo* microdump_extra_info() { + assert(IsMicrodumpOnConsole()); + return µdump_extra_info_; + }; + + private: + enum DumpMode { + kUninitialized = 0, + kWriteMinidumpToFile, + kWriteMinidumpToFd, + kWriteMicrodumpToConsole + }; + + // Specifies the dump mode (see DumpMode). + DumpMode mode_; + + // The file descriptor where the minidump is generated. + int fd_; + + // The directory where the minidump should be generated. + string directory_; + + // The full path to the generated minidump. + string path_; + + // The C string of |path_|. Precomputed so it can be access from a compromised + // context. + const char* c_path_; + + off_t size_limit_; + + // The extra microdump data (e.g. product name/version, build + // fingerprint, gpu fingerprint) that should be appended to the dump + // (microdump only). Microdumps don't have the ability of appending + // extra metadata after the dump is generated (as opposite to + // minidumps MIME fields), therefore the extra data must be provided + // upfront. Any memory pointed to by members of the + // MicrodumpExtraInfo struct must be valid for the lifetime of the + // process (read: the caller has to guarantee that it is stored in + // global static storage.) + MicrodumpExtraInfo microdump_extra_info_; +}; + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_HANDLER_MINIDUMP_DESCRIPTOR_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/log/log.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/log/log.cc new file mode 100644 index 000000000..fc23aa6d5 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/log/log.cc @@ -0,0 +1,84 @@ +// 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/linux/log/log.h" + +#if defined(__ANDROID__) +#include <android/log.h> +#include <dlfcn.h> +#else +#include "third_party/lss/linux_syscall_support.h" +#endif + +namespace logger { + +#if defined(__ANDROID__) +namespace { + +// __android_log_buf_write() is not exported in the NDK and is being used by +// dynamic runtime linking. Its declaration is taken from Android's +// system/core/include/log/log.h. +using AndroidLogBufferWriteFunc = int (*)(int bufID, int prio, const char *tag, + const char *text); +const int kAndroidCrashLogId = 4; // From LOG_ID_CRASH in log.h. +const char kAndroidLogTag[] = "google-breakpad"; + +bool g_crash_log_initialized = false; +AndroidLogBufferWriteFunc g_android_log_buf_write = nullptr; + +} // namespace + +void initializeCrashLogWriter() { + if (g_crash_log_initialized) + return; + g_android_log_buf_write = reinterpret_cast<AndroidLogBufferWriteFunc>( + dlsym(RTLD_DEFAULT, "__android_log_buf_write")); + g_crash_log_initialized = true; +} + +int writeToCrashLog(const char* buf) { + // Try writing to the crash log ring buffer. If not available, fall back to + // the standard log buffer. + if (g_android_log_buf_write) { + return g_android_log_buf_write(kAndroidCrashLogId, ANDROID_LOG_FATAL, + kAndroidLogTag, buf); + } + return __android_log_write(ANDROID_LOG_FATAL, kAndroidLogTag, buf); +} +#endif + +int write(const char* buf, size_t nbytes) { +#if defined(__ANDROID__) + return __android_log_write(ANDROID_LOG_WARN, kAndroidLogTag, buf); +#else + return sys_write(2, buf, nbytes); +#endif +} + +} // namespace logger diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/log/log.h b/toolkit/crashreporter/google-breakpad/src/client/linux/log/log.h new file mode 100644 index 000000000..f94bbd5fb --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/log/log.h @@ -0,0 +1,55 @@ +// 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_LINUX_LOG_LOG_H_ +#define CLIENT_LINUX_LOG_LOG_H_ + +#include <stddef.h> + +namespace logger { + +int write(const char* buf, size_t nbytes); + +// In the case of Android the log can be written to the default system log +// (default behavior of write() above, or to the crash log (see +// writeToCrashLog() below). +#if defined(__ANDROID__) + +// The logger must be initialized in a non-compromised context. +void initializeCrashLogWriter(); + +// Once initialized, writeToCrashLog is safe to use in a compromised context, +// even if the initialization failed, in which case this will silently fall +// back on write(). +int writeToCrashLog(const char* buf); +#endif + +} // namespace logger + +#endif // CLIENT_LINUX_LOG_LOG_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/microdump_writer/microdump_writer.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/microdump_writer/microdump_writer.cc new file mode 100644 index 000000000..a508667a0 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/microdump_writer/microdump_writer.cc @@ -0,0 +1,609 @@ +// Copyright (c) 2014, 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. + +// This translation unit generates microdumps into the console (logcat on +// Android). See crbug.com/410294 for more info and design docs. + +#include "client/linux/microdump_writer/microdump_writer.h" + +#include <limits> + +#include <sys/utsname.h> + +#include "client/linux/dump_writer_common/thread_info.h" +#include "client/linux/dump_writer_common/ucontext_reader.h" +#include "client/linux/handler/exception_handler.h" +#include "client/linux/handler/microdump_extra_info.h" +#include "client/linux/log/log.h" +#include "client/linux/minidump_writer/linux_ptrace_dumper.h" +#include "common/linux/file_id.h" +#include "common/linux/linux_libc_support.h" +#include "common/memory.h" + +namespace { + +using google_breakpad::auto_wasteful_vector; +using google_breakpad::ExceptionHandler; +using google_breakpad::kDefaultBuildIdSize; +using google_breakpad::LinuxDumper; +using google_breakpad::LinuxPtraceDumper; +using google_breakpad::MappingInfo; +using google_breakpad::MappingList; +using google_breakpad::MicrodumpExtraInfo; +using google_breakpad::RawContextCPU; +using google_breakpad::ThreadInfo; +using google_breakpad::UContextReader; + +const size_t kLineBufferSize = 2048; + +#if !defined(__LP64__) +// The following are only used by DumpFreeSpace, so need to be compiled +// in conditionally in the same way. + +template <typename Dst, typename Src> +Dst saturated_cast(Src src) { + if (src >= std::numeric_limits<Dst>::max()) + return std::numeric_limits<Dst>::max(); + if (src <= std::numeric_limits<Dst>::min()) + return std::numeric_limits<Dst>::min(); + return static_cast<Dst>(src); +} + +int Log2Floor(uint64_t n) { + // Copied from chromium src/base/bits.h + if (n == 0) + return -1; + int log = 0; + uint64_t value = n; + for (int i = 5; i >= 0; --i) { + int shift = (1 << i); + uint64_t x = value >> shift; + if (x != 0) { + value = x; + log += shift; + } + } + assert(value == 1u); + return log; +} + +bool MappingsAreAdjacent(const MappingInfo& a, const MappingInfo& b) { + // Because of load biasing, we can end up with a situation where two + // mappings actually overlap. So we will define adjacency to also include a + // b start address that lies within a's address range (including starting + // immediately after a). + // Because load biasing only ever moves the start address backwards, the end + // address should still increase. + return a.start_addr <= b.start_addr && a.start_addr + a.size >= b.start_addr; +} + +bool MappingLessThan(const MappingInfo* a, const MappingInfo* b) { + // Return true if mapping a is before mapping b. + // For the same reason (load biasing) we compare end addresses, which - unlike + // start addresses - will not have been modified. + return a->start_addr + a->size < b->start_addr + b->size; +} + +size_t NextOrderedMapping( + const google_breakpad::wasteful_vector<MappingInfo*>& mappings, + size_t curr) { + // Find the mapping that directly follows mappings[curr]. + // If no such mapping exists, return |invalid| to indicate this. + const size_t invalid = std::numeric_limits<size_t>::max(); + size_t best = invalid; + for (size_t next = 0; next < mappings.size(); ++next) { + if (MappingLessThan(mappings[curr], mappings[next]) && + (best == invalid || MappingLessThan(mappings[next], mappings[best]))) { + best = next; + } + } + return best; +} + +#endif // !__LP64__ + +class MicrodumpWriter { + public: + MicrodumpWriter(const ExceptionHandler::CrashContext* context, + const MappingList& mappings, + const MicrodumpExtraInfo& microdump_extra_info, + LinuxDumper* dumper) + : ucontext_(context ? &context->context : NULL), +#if !defined(__ARM_EABI__) && !defined(__mips__) + float_state_(context ? &context->float_state : NULL), +#endif + dumper_(dumper), + mapping_list_(mappings), + microdump_extra_info_(microdump_extra_info), + log_line_(NULL) { + log_line_ = reinterpret_cast<char*>(Alloc(kLineBufferSize)); + if (log_line_) + log_line_[0] = '\0'; // Clear out the log line buffer. + } + + ~MicrodumpWriter() { dumper_->ThreadsResume(); } + + bool Init() { + // In the exceptional case where the system was out of memory and there + // wasn't even room to allocate the line buffer, bail out. There is nothing + // useful we can possibly achieve without the ability to Log. At least let's + // try to not crash. + if (!dumper_->Init() || !log_line_) + return false; + return dumper_->ThreadsSuspend() && dumper_->LateInit(); + } + + bool Dump() { + bool success; + LogLine("-----BEGIN BREAKPAD MICRODUMP-----"); + DumpProductInformation(); + DumpOSInformation(); + DumpProcessType(); + DumpGPUInformation(); +#if !defined(__LP64__) + DumpFreeSpace(); +#endif + success = DumpCrashingThread(); + if (success) + success = DumpMappings(); + LogLine("-----END BREAKPAD MICRODUMP-----"); + dumper_->ThreadsResume(); + return success; + } + + private: + // Writes one line to the system log. + void LogLine(const char* msg) { +#if defined(__ANDROID__) + logger::writeToCrashLog(msg); +#else + logger::write(msg, my_strlen(msg)); + logger::write("\n", 1); +#endif + } + + // Stages the given string in the current line buffer. + void LogAppend(const char* str) { + my_strlcat(log_line_, str, kLineBufferSize); + } + + // As above (required to take precedence over template specialization below). + void LogAppend(char* str) { + LogAppend(const_cast<const char*>(str)); + } + + // Stages the hex repr. of the given int type in the current line buffer. + template<typename T> + void LogAppend(T value) { + // Make enough room to hex encode the largest int type + NUL. + static const char HEX[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F'}; + char hexstr[sizeof(T) * 2 + 1]; + for (int i = sizeof(T) * 2 - 1; i >= 0; --i, value >>= 4) + hexstr[i] = HEX[static_cast<uint8_t>(value) & 0x0F]; + hexstr[sizeof(T) * 2] = '\0'; + LogAppend(hexstr); + } + + // Stages the buffer content hex-encoded in the current line buffer. + void LogAppend(const void* buf, size_t length) { + const uint8_t* ptr = reinterpret_cast<const uint8_t*>(buf); + for (size_t i = 0; i < length; ++i, ++ptr) + LogAppend(*ptr); + } + + // Writes out the current line buffer on the system log. + void LogCommitLine() { + LogLine(log_line_); + my_strlcpy(log_line_, "", kLineBufferSize); + } + + void DumpProductInformation() { + LogAppend("V "); + if (microdump_extra_info_.product_info) { + LogAppend(microdump_extra_info_.product_info); + } else { + LogAppend("UNKNOWN:0.0.0.0"); + } + LogCommitLine(); + } + + void DumpProcessType() { + LogAppend("P "); + if (microdump_extra_info_.process_type) { + LogAppend(microdump_extra_info_.process_type); + } else { + LogAppend("UNKNOWN"); + } + LogCommitLine(); + } + + void DumpOSInformation() { + const uint8_t n_cpus = static_cast<uint8_t>(sysconf(_SC_NPROCESSORS_CONF)); + +#if defined(__ANDROID__) + const char kOSId[] = "A"; +#else + const char kOSId[] = "L"; +#endif + +// Dump the runtime architecture. On multiarch devices it might not match the +// hw architecture (the one returned by uname()), for instance in the case of +// a 32-bit app running on a aarch64 device. +#if defined(__aarch64__) + const char kArch[] = "arm64"; +#elif defined(__ARMEL__) + const char kArch[] = "arm"; +#elif defined(__x86_64__) + const char kArch[] = "x86_64"; +#elif defined(__i386__) + const char kArch[] = "x86"; +#elif defined(__mips__) +# if _MIPS_SIM == _ABIO32 + const char kArch[] = "mips"; +# elif _MIPS_SIM == _ABI64 + const char kArch[] = "mips64"; +# else +# error "This mips ABI is currently not supported (n32)" +#endif +#else +#error "This code has not been ported to your platform yet" +#endif + + LogAppend("O "); + LogAppend(kOSId); + LogAppend(" "); + LogAppend(kArch); + LogAppend(" "); + LogAppend(n_cpus); + LogAppend(" "); + + // Dump the HW architecture (e.g., armv7l, aarch64). + struct utsname uts; + const bool has_uts_info = (uname(&uts) == 0); + const char* hwArch = has_uts_info ? uts.machine : "unknown_hw_arch"; + LogAppend(hwArch); + LogAppend(" "); + + // If the client has attached a build fingerprint to the MinidumpDescriptor + // use that one. Otherwise try to get some basic info from uname(). + if (microdump_extra_info_.build_fingerprint) { + LogAppend(microdump_extra_info_.build_fingerprint); + } else if (has_uts_info) { + LogAppend(uts.release); + LogAppend(" "); + LogAppend(uts.version); + } else { + LogAppend("no build fingerprint available"); + } + LogCommitLine(); + } + + void DumpGPUInformation() { + LogAppend("G "); + if (microdump_extra_info_.gpu_fingerprint) { + LogAppend(microdump_extra_info_.gpu_fingerprint); + } else { + LogAppend("UNKNOWN"); + } + LogCommitLine(); + } + + bool DumpThreadStack(uint32_t thread_id, + uintptr_t stack_pointer, + int max_stack_len, + uint8_t** stack_copy) { + *stack_copy = NULL; + const void* stack; + size_t stack_len; + + if (!dumper_->GetStackInfo(&stack, &stack_len, stack_pointer)) { + // The stack pointer might not be available. In this case we don't hard + // fail, just produce a (almost useless) microdump w/o a stack section. + return true; + } + + LogAppend("S 0 "); + LogAppend(stack_pointer); + LogAppend(" "); + LogAppend(reinterpret_cast<uintptr_t>(stack)); + LogAppend(" "); + LogAppend(stack_len); + LogCommitLine(); + + if (max_stack_len >= 0 && + stack_len > static_cast<unsigned int>(max_stack_len)) { + stack_len = max_stack_len; + } + + *stack_copy = reinterpret_cast<uint8_t*>(Alloc(stack_len)); + dumper_->CopyFromProcess(*stack_copy, thread_id, stack, stack_len); + + // Dump the content of the stack, splicing it into chunks which size is + // compatible with the max logcat line size (see LOGGER_ENTRY_MAX_PAYLOAD). + const size_t STACK_DUMP_CHUNK_SIZE = 384; + for (size_t stack_off = 0; stack_off < stack_len; + stack_off += STACK_DUMP_CHUNK_SIZE) { + LogAppend("S "); + LogAppend(reinterpret_cast<uintptr_t>(stack) + stack_off); + LogAppend(" "); + LogAppend(*stack_copy + stack_off, + std::min(STACK_DUMP_CHUNK_SIZE, stack_len - stack_off)); + LogCommitLine(); + } + return true; + } + + // Write information about the crashing thread. + bool DumpCrashingThread() { + const unsigned num_threads = dumper_->threads().size(); + + for (unsigned i = 0; i < num_threads; ++i) { + MDRawThread thread; + my_memset(&thread, 0, sizeof(thread)); + thread.thread_id = dumper_->threads()[i]; + + // Dump only the crashing thread. + if (static_cast<pid_t>(thread.thread_id) != dumper_->crash_thread()) + continue; + + assert(ucontext_); + assert(!dumper_->IsPostMortem()); + + uint8_t* stack_copy; + const uintptr_t stack_ptr = UContextReader::GetStackPointer(ucontext_); + if (!DumpThreadStack(thread.thread_id, stack_ptr, -1, &stack_copy)) + return false; + + RawContextCPU cpu; + my_memset(&cpu, 0, sizeof(RawContextCPU)); +#if !defined(__ARM_EABI__) && !defined(__mips__) + UContextReader::FillCPUContext(&cpu, ucontext_, float_state_); +#else + UContextReader::FillCPUContext(&cpu, ucontext_); +#endif + DumpCPUState(&cpu); + } + return true; + } + + void DumpCPUState(RawContextCPU* cpu) { + LogAppend("C "); + LogAppend(cpu, sizeof(*cpu)); + LogCommitLine(); + } + + // If there is caller-provided information about this mapping + // in the mapping_list_ list, return true. Otherwise, return false. + bool HaveMappingInfo(const MappingInfo& mapping) { + for (MappingList::const_iterator iter = mapping_list_.begin(); + iter != mapping_list_.end(); + ++iter) { + // Ignore any mappings that are wholly contained within + // mappings in the mapping_info_ list. + if (mapping.start_addr >= iter->first.start_addr && + (mapping.start_addr + mapping.size) <= + (iter->first.start_addr + iter->first.size)) { + return true; + } + } + return false; + } + + // Dump information about the provided |mapping|. If |identifier| is non-NULL, + // use it instead of calculating a file ID from the mapping. + void DumpModule(const MappingInfo& mapping, + bool member, + unsigned int mapping_id, + const uint8_t* identifier) { + + auto_wasteful_vector<uint8_t, kDefaultBuildIdSize> identifier_bytes( + dumper_->allocator()); + + if (identifier) { + // GUID was provided by caller. + identifier_bytes.insert(identifier_bytes.end(), + identifier, + identifier + sizeof(MDGUID)); + } else { + dumper_->ElfFileIdentifierForMapping( + mapping, + member, + mapping_id, + identifier_bytes); + } + + // Copy as many bytes of |identifier| as will fit into a MDGUID + MDGUID module_identifier = {0}; + memcpy(&module_identifier, &identifier_bytes[0], + std::min(sizeof(MDGUID), identifier_bytes.size())); + + char file_name[NAME_MAX]; + char file_path[NAME_MAX]; + dumper_->GetMappingEffectiveNameAndPath( + mapping, file_path, sizeof(file_path), file_name, sizeof(file_name)); + + LogAppend("M "); + LogAppend(static_cast<uintptr_t>(mapping.start_addr)); + LogAppend(" "); + LogAppend(mapping.offset); + LogAppend(" "); + LogAppend(mapping.size); + LogAppend(" "); + LogAppend(module_identifier.data1); + LogAppend(module_identifier.data2); + LogAppend(module_identifier.data3); + LogAppend(module_identifier.data4[0]); + LogAppend(module_identifier.data4[1]); + LogAppend(module_identifier.data4[2]); + LogAppend(module_identifier.data4[3]); + LogAppend(module_identifier.data4[4]); + LogAppend(module_identifier.data4[5]); + LogAppend(module_identifier.data4[6]); + LogAppend(module_identifier.data4[7]); + LogAppend("0 "); // Age is always 0 on Linux. + LogAppend(file_name); + LogCommitLine(); + } + +#if !defined(__LP64__) + void DumpFreeSpace() { + const google_breakpad::wasteful_vector<MappingInfo*>& mappings = + dumper_->mappings(); + if (mappings.size() == 0) return; + + // This is complicated by the fact that mappings is not in order. It should + // be mostly in order, however the mapping that contains the entry point for + // the process is always at the front of the vector. + + static const int HBITS = sizeof(size_t) * 8; + size_t hole_histogram[HBITS]; + my_memset(hole_histogram, 0, sizeof(hole_histogram)); + + // Find the lowest address mapping. + size_t curr = 0; + for (size_t i = 1; i < mappings.size(); ++i) { + if (mappings[i]->start_addr < mappings[curr]->start_addr) curr = i; + } + + uintptr_t lo_addr = mappings[curr]->start_addr; + + size_t hole_cnt = 0; + size_t hole_max = 0; + size_t hole_sum = 0; + + while (true) { + // Skip to the end of an adjacent run of mappings. This is an optimization + // for the fact that mappings is mostly sorted. + while (curr != mappings.size() - 1 && + MappingsAreAdjacent(*mappings[curr], *mappings[curr + 1])) { + ++curr; + } + + size_t next = NextOrderedMapping(mappings, curr); + if (next == std::numeric_limits<size_t>::max()) + break; + + uintptr_t hole_lo = mappings[curr]->start_addr + mappings[curr]->size; + uintptr_t hole_hi = mappings[next]->start_addr; + + if (hole_hi > hole_lo) { + size_t hole_sz = hole_hi - hole_lo; + hole_sum += hole_sz; + hole_max = std::max(hole_sz, hole_max); + ++hole_cnt; + ++hole_histogram[Log2Floor(hole_sz)]; + } + curr = next; + } + + uintptr_t hi_addr = mappings[curr]->start_addr + mappings[curr]->size; + + LogAppend("H "); + LogAppend(lo_addr); + LogAppend(" "); + LogAppend(hi_addr); + LogAppend(" "); + LogAppend(saturated_cast<uint16_t>(hole_cnt)); + LogAppend(" "); + LogAppend(hole_max); + LogAppend(" "); + LogAppend(hole_sum); + for (unsigned int i = 0; i < HBITS; ++i) { + if (!hole_histogram[i]) continue; + LogAppend(" "); + LogAppend(saturated_cast<uint8_t>(i)); + LogAppend(":"); + LogAppend(saturated_cast<uint8_t>(hole_histogram[i])); + } + LogCommitLine(); + } +#endif + + // Write information about the mappings in effect. + bool DumpMappings() { + // First write all the mappings from the dumper + for (unsigned i = 0; i < dumper_->mappings().size(); ++i) { + const MappingInfo& mapping = *dumper_->mappings()[i]; + if (mapping.name[0] == 0 || // only want modules with filenames. + !mapping.exec || // only want executable mappings. + mapping.size < 4096 || // too small to get a signature for. + HaveMappingInfo(mapping)) { + continue; + } + + DumpModule(mapping, true, i, NULL); + } + // Next write all the mappings provided by the caller + for (MappingList::const_iterator iter = mapping_list_.begin(); + iter != mapping_list_.end(); + ++iter) { + DumpModule(iter->first, false, 0, iter->second); + } + return true; + } + + void* Alloc(unsigned bytes) { return dumper_->allocator()->Alloc(bytes); } + + const ucontext_t* const ucontext_; +#if !defined(__ARM_EABI__) && !defined(__mips__) + const google_breakpad::fpstate_t* const float_state_; +#endif + LinuxDumper* dumper_; + const MappingList& mapping_list_; + const MicrodumpExtraInfo microdump_extra_info_; + char* log_line_; +}; +} // namespace + +namespace google_breakpad { + +bool WriteMicrodump(pid_t crashing_process, + const void* blob, + size_t blob_size, + const MappingList& mappings, + const MicrodumpExtraInfo& microdump_extra_info) { + LinuxPtraceDumper dumper(crashing_process); + const ExceptionHandler::CrashContext* context = NULL; + if (blob) { + if (blob_size != sizeof(ExceptionHandler::CrashContext)) + return false; + context = reinterpret_cast<const ExceptionHandler::CrashContext*>(blob); + dumper.set_crash_address( + reinterpret_cast<uintptr_t>(context->siginfo.si_addr)); + dumper.set_crash_signal(context->siginfo.si_signo); + dumper.set_crash_thread(context->tid); + } + MicrodumpWriter writer(context, mappings, microdump_extra_info, &dumper); + if (!writer.Init()) + return false; + return writer.Dump(); +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/microdump_writer/microdump_writer.h b/toolkit/crashreporter/google-breakpad/src/client/linux/microdump_writer/microdump_writer.h new file mode 100644 index 000000000..7c742761d --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/microdump_writer/microdump_writer.h @@ -0,0 +1,65 @@ +// Copyright (c) 2014, 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_LINUX_MINIDUMP_WRITER_MICRODUMP_WRITER_H_ +#define CLIENT_LINUX_MINIDUMP_WRITER_MICRODUMP_WRITER_H_ + +#include <stdint.h> +#include <sys/types.h> + +#include "client/linux/dump_writer_common/mapping_info.h" + +namespace google_breakpad { + +struct MicrodumpExtraInfo; + +// Writes a microdump (a reduced dump containing only the state of the crashing +// thread) on the console (logcat on Android). These functions do not malloc nor +// use libc functions which may. Thus, it can be used in contexts where the +// state of the heap may be corrupt. +// Args: +// crashing_process: the pid of the crashing process. This must be trusted. +// blob: a blob of data from the crashing process. See exception_handler.h +// blob_size: the length of |blob| in bytes. +// mappings: a list of additional mappings provided by the application. +// build_fingerprint: a (optional) C string which determines the OS +// build fingerprint (e.g., aosp/occam/mako:5.1.1/LMY47W/1234:eng/dev-keys). +// product_info: a (optional) C string which determines the product name and +// version (e.g., WebView:42.0.2311.136). +// +// Returns true iff successful. +bool WriteMicrodump(pid_t crashing_process, + const void* blob, + size_t blob_size, + const MappingList& mappings, + const MicrodumpExtraInfo& microdump_extra_info); + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_MINIDUMP_WRITER_MICRODUMP_WRITER_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/microdump_writer/microdump_writer_unittest.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/microdump_writer/microdump_writer_unittest.cc new file mode 100644 index 000000000..58a731188 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/microdump_writer/microdump_writer_unittest.cc @@ -0,0 +1,257 @@ +// Copyright (c) 2014 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 <ctype.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <unistd.h> + +#include <sstream> +#include <string> + +#include "breakpad_googletest_includes.h" +#include "client/linux/handler/exception_handler.h" +#include "client/linux/handler/microdump_extra_info.h" +#include "client/linux/microdump_writer/microdump_writer.h" +#include "common/linux/eintr_wrapper.h" +#include "common/linux/ignore_ret.h" +#include "common/scoped_ptr.h" +#include "common/tests/auto_tempdir.h" +#include "common/using_std_string.h" + +using namespace google_breakpad; + +namespace { + +typedef testing::Test MicrodumpWriterTest; + +MicrodumpExtraInfo MakeMicrodumpExtraInfo( + const char* build_fingerprint, + const char* product_info, + const char* gpu_fingerprint) { + MicrodumpExtraInfo info; + info.build_fingerprint = build_fingerprint; + info.product_info = product_info; + info.gpu_fingerprint = gpu_fingerprint; + return info; +} + +void CrashAndGetMicrodump( + const MappingList& mappings, + const MicrodumpExtraInfo& microdump_extra_info, + scoped_array<char>* buf) { + int fds[2]; + ASSERT_NE(-1, pipe(fds)); + + AutoTempDir temp_dir; + string stderr_file = temp_dir.path() + "/stderr.log"; + int err_fd = open(stderr_file.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); + ASSERT_NE(-1, err_fd); + + const pid_t child = fork(); + if (child == 0) { + close(fds[1]); + char b; + IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b)))); + close(fds[0]); + syscall(__NR_exit); + } + close(fds[0]); + + ExceptionHandler::CrashContext context; + memset(&context, 0, sizeof(context)); + + // Set a non-zero tid to avoid tripping asserts. + context.tid = child; + + // Redirect temporarily stderr to the stderr.log file. + int save_err = dup(STDERR_FILENO); + ASSERT_NE(-1, save_err); + ASSERT_NE(-1, dup2(err_fd, STDERR_FILENO)); + + ASSERT_TRUE(WriteMicrodump(child, &context, sizeof(context), mappings, + microdump_extra_info)); + + // Revert stderr back to the console. + dup2(save_err, STDERR_FILENO); + close(save_err); + + // Read back the stderr file and check for the microdump marker. + fsync(err_fd); + lseek(err_fd, 0, SEEK_SET); + const size_t kBufSize = 64 * 1024; + buf->reset(new char[kBufSize]); + ASSERT_GT(read(err_fd, buf->get(), kBufSize), 0); + + close(err_fd); + close(fds[1]); + + ASSERT_NE(static_cast<char*>(0), strstr( + buf->get(), "-----BEGIN BREAKPAD MICRODUMP-----")); + ASSERT_NE(static_cast<char*>(0), strstr( + buf->get(), "-----END BREAKPAD MICRODUMP-----")); +} + +void CheckMicrodumpContents(const string& microdump_content, + const MicrodumpExtraInfo& expected_info) { + std::istringstream iss(microdump_content); + bool did_find_os_info = false; + bool did_find_product_info = false; + bool did_find_gpu_info = false; + for (string line; std::getline(iss, line);) { + if (line.find("O ") == 0) { + std::istringstream os_info_tokens(line); + string token; + os_info_tokens.ignore(2); // Ignore the "O " preamble. + // Check the OS descriptor char (L=Linux, A=Android). + os_info_tokens >> token; + ASSERT_TRUE(token == "L" || token == "A"); + + os_info_tokens >> token; // HW architecture. + os_info_tokens >> token; // Number of cpus. + for (size_t i = 0; i < token.size(); ++i) + ASSERT_TRUE(isxdigit(token[i])); + os_info_tokens >> token; // SW architecture. + + // Check that the build fingerprint is in the right place. + os_info_tokens >> token; + if (expected_info.build_fingerprint) + ASSERT_EQ(expected_info.build_fingerprint, token); + did_find_os_info = true; + } else if (line.find("V ") == 0) { + if (expected_info.product_info) + ASSERT_EQ(string("V ") + expected_info.product_info, line); + did_find_product_info = true; + } else if (line.find("G ") == 0) { + if (expected_info.gpu_fingerprint) + ASSERT_EQ(string("G ") + expected_info.gpu_fingerprint, line); + did_find_gpu_info = true; + } + } + ASSERT_TRUE(did_find_os_info); + ASSERT_TRUE(did_find_product_info); + ASSERT_TRUE(did_find_gpu_info); +} + +void CheckMicrodumpContents(const string& microdump_content, + const string& expected_fingerprint, + const string& expected_product_info, + const string& expected_gpu_fingerprint) { + CheckMicrodumpContents( + microdump_content, + MakeMicrodumpExtraInfo(expected_fingerprint.c_str(), + expected_product_info.c_str(), + expected_gpu_fingerprint.c_str())); +} + +TEST(MicrodumpWriterTest, BasicWithMappings) { + // Push some extra mapping to check the MappingList logic. + const uint32_t memory_size = sysconf(_SC_PAGESIZE); + const char* kMemoryName = "libfoo.so"; + const uint8_t kModuleGUID[sizeof(MDGUID)] = { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF + }; + + MappingInfo info; + info.start_addr = memory_size; + info.size = memory_size; + info.offset = 42; + strcpy(info.name, kMemoryName); + + MappingList mappings; + MappingEntry mapping; + mapping.first = info; + memcpy(mapping.second, kModuleGUID, sizeof(MDGUID)); + mappings.push_back(mapping); + + scoped_array<char> buf; + CrashAndGetMicrodump(mappings, MicrodumpExtraInfo(), &buf); + +#ifdef __LP64__ + ASSERT_NE(static_cast<char*>(0), strstr( + buf.get(), "M 0000000000001000 000000000000002A 0000000000001000 " + "33221100554477668899AABBCCDDEEFF0 libfoo.so")); +#else + ASSERT_NE(static_cast<char*>(0), strstr( + buf.get(), "M 00001000 0000002A 00001000 " + "33221100554477668899AABBCCDDEEFF0 libfoo.so")); +#endif + + // In absence of a product info in the minidump, the writer should just write + // an unknown marker. + ASSERT_NE(static_cast<char*>(0), strstr( + buf.get(), "V UNKNOWN:0.0.0.0")); +} + +// Ensure that the product info and build fingerprint metadata show up in the +// final microdump if present. +TEST(MicrodumpWriterTest, BuildFingerprintAndProductInfo) { + const char kProductInfo[] = "MockProduct:42.0.2311.99"; + const char kBuildFingerprint[] = + "aosp/occam/mako:5.1.1/LMY47W/12345678:userdegbug/dev-keys"; + const char kGPUFingerprint[] = + "Qualcomm;Adreno (TM) 330;OpenGL ES 3.0 V@104.0 AU@ (GIT@Id3510ff6dc)"; + const MicrodumpExtraInfo kMicrodumpExtraInfo( + MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, kGPUFingerprint)); + scoped_array<char> buf; + MappingList no_mappings; + + CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfo, &buf); + CheckMicrodumpContents(string(buf.get()), kMicrodumpExtraInfo); +} + +TEST(MicrodumpWriterTest, NoProductInfo) { + const char kBuildFingerprint[] = "foobar"; + const char kGPUFingerprint[] = "bazqux"; + scoped_array<char> buf; + MappingList no_mappings; + + const MicrodumpExtraInfo kMicrodumpExtraInfoNoProductInfo( + MakeMicrodumpExtraInfo(kBuildFingerprint, NULL, kGPUFingerprint)); + + CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfoNoProductInfo, &buf); + CheckMicrodumpContents(string(buf.get()), kBuildFingerprint, + "UNKNOWN:0.0.0.0", kGPUFingerprint); +} + +TEST(MicrodumpWriterTest, NoGPUInfo) { + const char kProductInfo[] = "bazqux"; + const char kBuildFingerprint[] = "foobar"; + scoped_array<char> buf; + MappingList no_mappings; + + const MicrodumpExtraInfo kMicrodumpExtraInfoNoGPUInfo( + MakeMicrodumpExtraInfo(kBuildFingerprint, kProductInfo, NULL)); + + CrashAndGetMicrodump(no_mappings, kMicrodumpExtraInfoNoGPUInfo, &buf); + CheckMicrodumpContents(string(buf.get()), kBuildFingerprint, + kProductInfo, "UNKNOWN"); +} +} // namespace diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/cpu_set.h b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/cpu_set.h new file mode 100644 index 000000000..1cca9aa5a --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/cpu_set.h @@ -0,0 +1,144 @@ +// Copyright (c) 2013, 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_LINUX_MINIDUMP_WRITER_CPU_SET_H_ +#define CLIENT_LINUX_MINIDUMP_WRITER_CPU_SET_H_ + +#include <stdint.h> +#include <assert.h> +#include <string.h> + +#include "common/linux/linux_libc_support.h" +#include "third_party/lss/linux_syscall_support.h" + +namespace google_breakpad { + +// Helper class used to model a set of CPUs, as read from sysfs +// files like /sys/devices/system/cpu/present +// See See http://www.kernel.org/doc/Documentation/cputopology.txt +class CpuSet { +public: + // The maximum number of supported CPUs. + static const size_t kMaxCpus = 1024; + + CpuSet() { + my_memset(mask_, 0, sizeof(mask_)); + } + + // Parse a sysfs file to extract the corresponding CPU set. + bool ParseSysFile(int fd) { + char buffer[512]; + int ret = sys_read(fd, buffer, sizeof(buffer)-1); + if (ret < 0) + return false; + + buffer[ret] = '\0'; + + // Expected format: comma-separated list of items, where each + // item can be a decimal integer, or two decimal integers separated + // by a dash. + // E.g.: + // 0 + // 0,1,2,3 + // 0-3 + // 1,10-23 + const char* p = buffer; + const char* p_end = p + ret; + while (p < p_end) { + // Skip leading space, if any + while (p < p_end && my_isspace(*p)) + p++; + + // Find start and size of current item. + const char* item = p; + size_t item_len = static_cast<size_t>(p_end - p); + const char* item_next = + static_cast<const char*>(my_memchr(p, ',', item_len)); + if (item_next != NULL) { + p = item_next + 1; + item_len = static_cast<size_t>(item_next - item); + } else { + p = p_end; + item_next = p_end; + } + + // Ignore trailing spaces. + while (item_next > item && my_isspace(item_next[-1])) + item_next--; + + // skip empty items. + if (item_next == item) + continue; + + // read first decimal value. + uintptr_t start = 0; + const char* next = my_read_decimal_ptr(&start, item); + uintptr_t end = start; + if (*next == '-') + my_read_decimal_ptr(&end, next+1); + + while (start <= end) + SetBit(start++); + } + return true; + } + + // Intersect this CPU set with another one. + void IntersectWith(const CpuSet& other) { + for (size_t nn = 0; nn < kMaskWordCount; ++nn) + mask_[nn] &= other.mask_[nn]; + } + + // Return the number of CPUs in this set. + int GetCount() { + int result = 0; + for (size_t nn = 0; nn < kMaskWordCount; ++nn) { + result += __builtin_popcount(mask_[nn]); + } + return result; + } + +private: + void SetBit(uintptr_t index) { + size_t nn = static_cast<size_t>(index); + if (nn < kMaxCpus) + mask_[nn / kMaskWordBits] |= (1U << (nn % kMaskWordBits)); + } + + typedef uint32_t MaskWordType; + static const size_t kMaskWordBits = 8*sizeof(MaskWordType); + static const size_t kMaskWordCount = + (kMaxCpus + kMaskWordBits - 1) / kMaskWordBits; + + MaskWordType mask_[kMaskWordCount]; +}; + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_MINIDUMP_WRITER_CPU_SET_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/cpu_set_unittest.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/cpu_set_unittest.cc new file mode 100644 index 000000000..e2274bd17 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/cpu_set_unittest.cc @@ -0,0 +1,164 @@ +// Copyright (c) 2013, 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 <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> + +#include "breakpad_googletest_includes.h" +#include "client/linux/minidump_writer/cpu_set.h" +#include "common/linux/tests/auto_testfile.h" + +using namespace google_breakpad; + +namespace { + +typedef testing::Test CpuSetTest; + +// Helper class to write test text file to a temporary file and return +// its file descriptor. +class ScopedTestFile : public AutoTestFile { +public: + explicit ScopedTestFile(const char* text) + : AutoTestFile("cpu_set", text) { + } +}; + +} + +TEST(CpuSetTest, EmptyCount) { + CpuSet set; + ASSERT_EQ(0, set.GetCount()); +} + +TEST(CpuSetTest, OneCpu) { + ScopedTestFile file("10"); + ASSERT_TRUE(file.IsOk()); + + CpuSet set; + ASSERT_TRUE(set.ParseSysFile(file.GetFd())); + ASSERT_EQ(1, set.GetCount()); +} + +TEST(CpuSetTest, OneCpuTerminated) { + ScopedTestFile file("10\n"); + ASSERT_TRUE(file.IsOk()); + + CpuSet set; + ASSERT_TRUE(set.ParseSysFile(file.GetFd())); + ASSERT_EQ(1, set.GetCount()); +} + +TEST(CpuSetTest, TwoCpusWithComma) { + ScopedTestFile file("1,10"); + ASSERT_TRUE(file.IsOk()); + + CpuSet set; + ASSERT_TRUE(set.ParseSysFile(file.GetFd())); + ASSERT_EQ(2, set.GetCount()); +} + +TEST(CpuSetTest, TwoCpusWithRange) { + ScopedTestFile file("1-2"); + ASSERT_TRUE(file.IsOk()); + + CpuSet set; + ASSERT_TRUE(set.ParseSysFile(file.GetFd())); + ASSERT_EQ(2, set.GetCount()); +} + +TEST(CpuSetTest, TenCpusWithRange) { + ScopedTestFile file("9-18"); + ASSERT_TRUE(file.IsOk()); + + CpuSet set; + ASSERT_TRUE(set.ParseSysFile(file.GetFd())); + ASSERT_EQ(10, set.GetCount()); +} + +TEST(CpuSetTest, MultiItems) { + ScopedTestFile file("0, 2-4, 128"); + ASSERT_TRUE(file.IsOk()); + + CpuSet set; + ASSERT_TRUE(set.ParseSysFile(file.GetFd())); + ASSERT_EQ(5, set.GetCount()); +} + +TEST(CpuSetTest, IntersectWith) { + ScopedTestFile file1("9-19"); + ASSERT_TRUE(file1.IsOk()); + CpuSet set1; + ASSERT_TRUE(set1.ParseSysFile(file1.GetFd())); + ASSERT_EQ(11, set1.GetCount()); + + ScopedTestFile file2("16-24"); + ASSERT_TRUE(file2.IsOk()); + CpuSet set2; + ASSERT_TRUE(set2.ParseSysFile(file2.GetFd())); + ASSERT_EQ(9, set2.GetCount()); + + set1.IntersectWith(set2); + ASSERT_EQ(4, set1.GetCount()); + ASSERT_EQ(9, set2.GetCount()); +} + +TEST(CpuSetTest, SelfIntersection) { + ScopedTestFile file1("9-19"); + ASSERT_TRUE(file1.IsOk()); + CpuSet set1; + ASSERT_TRUE(set1.ParseSysFile(file1.GetFd())); + ASSERT_EQ(11, set1.GetCount()); + + set1.IntersectWith(set1); + ASSERT_EQ(11, set1.GetCount()); +} + +TEST(CpuSetTest, EmptyIntersection) { + ScopedTestFile file1("0-19"); + ASSERT_TRUE(file1.IsOk()); + CpuSet set1; + ASSERT_TRUE(set1.ParseSysFile(file1.GetFd())); + ASSERT_EQ(20, set1.GetCount()); + + ScopedTestFile file2("20-39"); + ASSERT_TRUE(file2.IsOk()); + CpuSet set2; + ASSERT_TRUE(set2.ParseSysFile(file2.GetFd())); + ASSERT_EQ(20, set2.GetCount()); + + set1.IntersectWith(set2); + ASSERT_EQ(0, set1.GetCount()); + + ASSERT_EQ(20, set2.GetCount()); +} + diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/directory_reader.h b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/directory_reader.h new file mode 100644 index 000000000..a4bde1803 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/directory_reader.h @@ -0,0 +1,106 @@ +// Copyright (c) 2009, 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_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_ +#define CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_ + +#include <stdint.h> +#include <unistd.h> +#include <limits.h> +#include <assert.h> +#include <errno.h> +#include <string.h> + +#include "common/linux/linux_libc_support.h" +#include "third_party/lss/linux_syscall_support.h" + +namespace google_breakpad { + +// A class for enumerating a directory without using diropen/readdir or other +// functions which may allocate memory. +class DirectoryReader { + public: + DirectoryReader(int fd) + : fd_(fd), + buf_used_(0) { + } + + // Return the next entry from the directory + // name: (output) the NUL terminated entry name + // + // Returns true iff successful (false on EOF). + // + // After calling this, one must call |PopEntry| otherwise you'll get the same + // entry over and over. + bool GetNextEntry(const char** name) { + struct kernel_dirent* const dent = + reinterpret_cast<kernel_dirent*>(buf_); + + if (buf_used_ == 0) { + // need to read more entries. + const int n = sys_getdents(fd_, dent, sizeof(buf_)); + if (n < 0) { + return false; + } else if (n == 0) { + hit_eof_ = true; + } else { + buf_used_ += n; + } + } + + if (buf_used_ == 0 && hit_eof_) + return false; + + assert(buf_used_ > 0); + + *name = dent->d_name; + return true; + } + + void PopEntry() { + if (!buf_used_) + return; + + const struct kernel_dirent* const dent = + reinterpret_cast<kernel_dirent*>(buf_); + + buf_used_ -= dent->d_reclen; + my_memmove(buf_, buf_ + dent->d_reclen, buf_used_); + } + + private: + const int fd_; + bool hit_eof_; + unsigned buf_used_; + uint8_t buf_[sizeof(struct kernel_dirent) + NAME_MAX + 1]; +}; + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_MINIDUMP_WRITER_DIRECTORY_READER_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/directory_reader_unittest.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/directory_reader_unittest.cc new file mode 100644 index 000000000..326f9e36b --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/directory_reader_unittest.cc @@ -0,0 +1,78 @@ +// Copyright (c) 2009, 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 <set> +#include <string> + +#include <dirent.h> +#include <fcntl.h> +#include <sys/types.h> + +#include "client/linux/minidump_writer/directory_reader.h" +#include "common/using_std_string.h" +#include "breakpad_googletest_includes.h" + +using namespace google_breakpad; + +namespace { +typedef testing::Test DirectoryReaderTest; +} + +TEST(DirectoryReaderTest, CompareResults) { + std::set<string> dent_set; + + DIR *const dir = opendir("/proc/self"); + ASSERT_TRUE(dir != NULL); + + struct dirent* dent; + while ((dent = readdir(dir))) + dent_set.insert(dent->d_name); + + closedir(dir); + + const int fd = open("/proc/self", O_DIRECTORY | O_RDONLY); + ASSERT_GE(fd, 0); + + DirectoryReader dir_reader(fd); + unsigned seen = 0; + + const char* name; + while (dir_reader.GetNextEntry(&name)) { + ASSERT_TRUE(dent_set.find(name) != dent_set.end()); + seen++; + dir_reader.PopEntry(); + } + + ASSERT_TRUE(dent_set.find("status") != dent_set.end()); + ASSERT_TRUE(dent_set.find("stat") != dent_set.end()); + ASSERT_TRUE(dent_set.find("cmdline") != dent_set.end()); + + ASSERT_EQ(dent_set.size(), seen); + close(fd); +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/line_reader.h b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/line_reader.h new file mode 100644 index 000000000..779cfeb60 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/line_reader.h @@ -0,0 +1,131 @@ +// Copyright (c) 2009, 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_LINUX_MINIDUMP_WRITER_LINE_READER_H_ +#define CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_ + +#include <stdint.h> +#include <assert.h> +#include <string.h> + +#include "common/linux/linux_libc_support.h" +#include "third_party/lss/linux_syscall_support.h" + +namespace google_breakpad { + +// A class for reading a file, line by line, without using fopen/fgets or other +// functions which may allocate memory. +class LineReader { + public: + LineReader(int fd) + : fd_(fd), + hit_eof_(false), + buf_used_(0) { + } + + // The maximum length of a line. + static const size_t kMaxLineLen = 512; + + // Return the next line from the file. + // line: (output) a pointer to the start of the line. The line is NUL + // terminated. + // len: (output) the length of the line (not inc the NUL byte) + // + // Returns true iff successful (false on EOF). + // + // One must call |PopLine| after this function, otherwise you'll continue to + // get the same line over and over. + bool GetNextLine(const char **line, unsigned *len) { + for (;;) { + if (buf_used_ == 0 && hit_eof_) + return false; + + for (unsigned i = 0; i < buf_used_; ++i) { + if (buf_[i] == '\n' || buf_[i] == 0) { + buf_[i] = 0; + *len = i; + *line = buf_; + return true; + } + } + + if (buf_used_ == sizeof(buf_)) { + // we scanned the whole buffer and didn't find an end-of-line marker. + // This line is too long to process. + return false; + } + + // We didn't find any end-of-line terminators in the buffer. However, if + // this is the last line in the file it might not have one: + if (hit_eof_) { + assert(buf_used_); + // There's room for the NUL because of the buf_used_ == sizeof(buf_) + // check above. + buf_[buf_used_] = 0; + *len = buf_used_; + buf_used_ += 1; // since we appended the NUL. + *line = buf_; + return true; + } + + // Otherwise, we should pull in more data from the file + const ssize_t n = sys_read(fd_, buf_ + buf_used_, + sizeof(buf_) - buf_used_); + if (n < 0) { + return false; + } else if (n == 0) { + hit_eof_ = true; + } else { + buf_used_ += n; + } + + // At this point, we have either set the hit_eof_ flag, or we have more + // data to process... + } + } + + void PopLine(unsigned len) { + // len doesn't include the NUL byte at the end. + + assert(buf_used_ >= len + 1); + buf_used_ -= len + 1; + my_memmove(buf_, buf_ + len + 1, buf_used_); + } + + private: + const int fd_; + + bool hit_eof_; + unsigned buf_used_; + char buf_[kMaxLineLen]; +}; + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_MINIDUMP_WRITER_LINE_READER_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/line_reader_unittest.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/line_reader_unittest.cc new file mode 100644 index 000000000..29686f04a --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/line_reader_unittest.cc @@ -0,0 +1,169 @@ +// Copyright (c) 2009, 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 <stdlib.h> +#include <unistd.h> +#include <sys/types.h> + +#include "client/linux/minidump_writer/line_reader.h" +#include "breakpad_googletest_includes.h" +#include "common/linux/tests/auto_testfile.h" + +using namespace google_breakpad; + +namespace { + +typedef testing::Test LineReaderTest; + +class ScopedTestFile : public AutoTestFile { +public: + explicit ScopedTestFile(const char* text) + : AutoTestFile("line_reader", text) { + } + + ScopedTestFile(const char* text, size_t text_len) + : AutoTestFile("line_reader", text, text_len) { + } +}; + +} + +TEST(LineReaderTest, EmptyFile) { + ScopedTestFile file(""); + ASSERT_TRUE(file.IsOk()); + LineReader reader(file.GetFd()); + + const char *line; + unsigned len; + ASSERT_FALSE(reader.GetNextLine(&line, &len)); +} + +TEST(LineReaderTest, OneLineTerminated) { + ScopedTestFile file("a\n"); + ASSERT_TRUE(file.IsOk()); + LineReader reader(file.GetFd()); + + const char *line; + unsigned int len; + ASSERT_TRUE(reader.GetNextLine(&line, &len)); + ASSERT_EQ((unsigned int)1, len); + ASSERT_EQ('a', line[0]); + ASSERT_EQ('\0', line[1]); + reader.PopLine(len); + + ASSERT_FALSE(reader.GetNextLine(&line, &len)); +} + +TEST(LineReaderTest, OneLine) { + ScopedTestFile file("a"); + ASSERT_TRUE(file.IsOk()); + LineReader reader(file.GetFd()); + + const char *line; + unsigned len; + ASSERT_TRUE(reader.GetNextLine(&line, &len)); + ASSERT_EQ((unsigned)1, len); + ASSERT_EQ('a', line[0]); + ASSERT_EQ('\0', line[1]); + reader.PopLine(len); + + ASSERT_FALSE(reader.GetNextLine(&line, &len)); +} + +TEST(LineReaderTest, TwoLinesTerminated) { + ScopedTestFile file("a\nb\n"); + ASSERT_TRUE(file.IsOk()); + LineReader reader(file.GetFd()); + + const char *line; + unsigned len; + ASSERT_TRUE(reader.GetNextLine(&line, &len)); + ASSERT_EQ((unsigned)1, len); + ASSERT_EQ('a', line[0]); + ASSERT_EQ('\0', line[1]); + reader.PopLine(len); + + ASSERT_TRUE(reader.GetNextLine(&line, &len)); + ASSERT_EQ((unsigned)1, len); + ASSERT_EQ('b', line[0]); + ASSERT_EQ('\0', line[1]); + reader.PopLine(len); + + ASSERT_FALSE(reader.GetNextLine(&line, &len)); +} + +TEST(LineReaderTest, TwoLines) { + ScopedTestFile file("a\nb"); + ASSERT_TRUE(file.IsOk()); + LineReader reader(file.GetFd()); + + const char *line; + unsigned len; + ASSERT_TRUE(reader.GetNextLine(&line, &len)); + ASSERT_EQ((unsigned)1, len); + ASSERT_EQ('a', line[0]); + ASSERT_EQ('\0', line[1]); + reader.PopLine(len); + + ASSERT_TRUE(reader.GetNextLine(&line, &len)); + ASSERT_EQ((unsigned)1, len); + ASSERT_EQ('b', line[0]); + ASSERT_EQ('\0', line[1]); + reader.PopLine(len); + + ASSERT_FALSE(reader.GetNextLine(&line, &len)); +} + +TEST(LineReaderTest, MaxLength) { + char l[LineReader::kMaxLineLen-1]; + memset(l, 'a', sizeof(l)); + ScopedTestFile file(l, sizeof(l)); + ASSERT_TRUE(file.IsOk()); + LineReader reader(file.GetFd()); + + const char *line; + unsigned len; + ASSERT_TRUE(reader.GetNextLine(&line, &len)); + ASSERT_EQ(sizeof(l), len); + ASSERT_TRUE(memcmp(l, line, sizeof(l)) == 0); + ASSERT_EQ('\0', line[len]); +} + +TEST(LineReaderTest, TooLong) { + // Note: this writes kMaxLineLen 'a' chars in the test file. + char l[LineReader::kMaxLineLen]; + memset(l, 'a', sizeof(l)); + ScopedTestFile file(l, sizeof(l)); + ASSERT_TRUE(file.IsOk()); + LineReader reader(file.GetFd()); + + const char *line; + unsigned len; + ASSERT_FALSE(reader.GetNextLine(&line, &len)); +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_core_dumper.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_core_dumper.cc new file mode 100644 index 000000000..622f05069 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_core_dumper.cc @@ -0,0 +1,258 @@ +// 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. + +// linux_core_dumper.cc: Implement google_breakpad::LinuxCoreDumper. +// See linux_core_dumper.h for details. + +#include "client/linux/minidump_writer/linux_core_dumper.h" + +#include <asm/ptrace.h> +#include <assert.h> +#include <elf.h> +#include <stdio.h> +#include <string.h> +#include <sys/procfs.h> +#if defined(__mips__) && defined(__ANDROID__) +// To get register definitions. +#include <asm/reg.h> +#endif + +#include "common/linux/linux_libc_support.h" + +namespace google_breakpad { + +LinuxCoreDumper::LinuxCoreDumper(pid_t pid, + const char* core_path, + const char* procfs_path, + const char* root_prefix) + : LinuxDumper(pid, root_prefix), + core_path_(core_path), + procfs_path_(procfs_path), + thread_infos_(&allocator_, 8) { + assert(core_path_); +} + +bool LinuxCoreDumper::BuildProcPath(char* path, pid_t pid, + const char* node) const { + if (!path || !node) + return false; + + size_t node_len = my_strlen(node); + if (node_len == 0) + return false; + + size_t procfs_path_len = my_strlen(procfs_path_); + size_t total_length = procfs_path_len + 1 + node_len; + if (total_length >= NAME_MAX) + return false; + + memcpy(path, procfs_path_, procfs_path_len); + path[procfs_path_len] = '/'; + memcpy(path + procfs_path_len + 1, node, node_len); + path[total_length] = '\0'; + return true; +} + +bool LinuxCoreDumper::CopyFromProcess(void* dest, pid_t child, + const void* src, size_t length) { + ElfCoreDump::Addr virtual_address = reinterpret_cast<ElfCoreDump::Addr>(src); + // TODO(benchan): Investigate whether the data to be copied could span + // across multiple segments in the core dump file. ElfCoreDump::CopyData + // and this method do not handle that case yet. + if (!core_.CopyData(dest, virtual_address, length)) { + // If the data segment is not found in the core dump, fill the result + // with marker characters. + memset(dest, 0xab, length); + return false; + } + return true; +} + +bool LinuxCoreDumper::GetThreadInfoByIndex(size_t index, ThreadInfo* info) { + if (index >= thread_infos_.size()) + return false; + + *info = thread_infos_[index]; + const uint8_t* stack_pointer; +#if defined(__i386) + memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp)); +#elif defined(__x86_64) + memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp)); +#elif defined(__ARM_EABI__) + memcpy(&stack_pointer, &info->regs.ARM_sp, sizeof(info->regs.ARM_sp)); +#elif defined(__aarch64__) + memcpy(&stack_pointer, &info->regs.sp, sizeof(info->regs.sp)); +#elif defined(__mips__) + stack_pointer = + reinterpret_cast<uint8_t*>(info->mcontext.gregs[MD_CONTEXT_MIPS_REG_SP]); +#else +#error "This code hasn't been ported to your platform yet." +#endif + info->stack_pointer = reinterpret_cast<uintptr_t>(stack_pointer); + return true; +} + +bool LinuxCoreDumper::IsPostMortem() const { + return true; +} + +bool LinuxCoreDumper::ThreadsSuspend() { + return true; +} + +bool LinuxCoreDumper::ThreadsResume() { + return true; +} + +bool LinuxCoreDumper::EnumerateThreads() { + if (!mapped_core_file_.Map(core_path_, 0)) { + fprintf(stderr, "Could not map core dump file into memory\n"); + return false; + } + + core_.SetContent(mapped_core_file_.content()); + if (!core_.IsValid()) { + fprintf(stderr, "Invalid core dump file\n"); + return false; + } + + ElfCoreDump::Note note = core_.GetFirstNote(); + if (!note.IsValid()) { + fprintf(stderr, "PT_NOTE section not found\n"); + return false; + } + + bool first_thread = true; + do { + ElfCoreDump::Word type = note.GetType(); + MemoryRange name = note.GetName(); + MemoryRange description = note.GetDescription(); + + if (type == 0 || name.IsEmpty() || description.IsEmpty()) { + fprintf(stderr, "Could not found a valid PT_NOTE.\n"); + return false; + } + + // Based on write_note_info() in linux/kernel/fs/binfmt_elf.c, notes are + // ordered as follows (NT_PRXFPREG and NT_386_TLS are i386 specific): + // Thread Name Type + // ------------------------------------------------------------------- + // 1st thread CORE NT_PRSTATUS + // process-wide CORE NT_PRPSINFO + // process-wide CORE NT_AUXV + // 1st thread CORE NT_FPREGSET + // 1st thread LINUX NT_PRXFPREG + // 1st thread LINUX NT_386_TLS + // + // 2nd thread CORE NT_PRSTATUS + // 2nd thread CORE NT_FPREGSET + // 2nd thread LINUX NT_PRXFPREG + // 2nd thread LINUX NT_386_TLS + // + // 3rd thread CORE NT_PRSTATUS + // 3rd thread CORE NT_FPREGSET + // 3rd thread LINUX NT_PRXFPREG + // 3rd thread LINUX NT_386_TLS + // + // The following code only works if notes are ordered as expected. + switch (type) { + case NT_PRSTATUS: { + if (description.length() != sizeof(elf_prstatus)) { + fprintf(stderr, "Found NT_PRSTATUS descriptor of unexpected size\n"); + return false; + } + + const elf_prstatus* status = + reinterpret_cast<const elf_prstatus*>(description.data()); + pid_t pid = status->pr_pid; + ThreadInfo info; + memset(&info, 0, sizeof(ThreadInfo)); + info.tgid = status->pr_pgrp; + info.ppid = status->pr_ppid; +#if defined(__mips__) +#if defined(__ANDROID__) + for (int i = EF_R0; i <= EF_R31; i++) + info.mcontext.gregs[i - EF_R0] = status->pr_reg[i]; +#else // __ANDROID__ + for (int i = EF_REG0; i <= EF_REG31; i++) + info.mcontext.gregs[i - EF_REG0] = status->pr_reg[i]; +#endif // __ANDROID__ + info.mcontext.mdlo = status->pr_reg[EF_LO]; + info.mcontext.mdhi = status->pr_reg[EF_HI]; + info.mcontext.pc = status->pr_reg[EF_CP0_EPC]; +#else // __mips__ + memcpy(&info.regs, status->pr_reg, sizeof(info.regs)); +#endif // __mips__ + if (first_thread) { + crash_thread_ = pid; + crash_signal_ = status->pr_info.si_signo; + } + first_thread = false; + threads_.push_back(pid); + thread_infos_.push_back(info); + break; + } +#if defined(__i386) || defined(__x86_64) + case NT_FPREGSET: { + if (thread_infos_.empty()) + return false; + + ThreadInfo* info = &thread_infos_.back(); + if (description.length() != sizeof(info->fpregs)) { + fprintf(stderr, "Found NT_FPREGSET descriptor of unexpected size\n"); + return false; + } + + memcpy(&info->fpregs, description.data(), sizeof(info->fpregs)); + break; + } +#endif +#if defined(__i386) + case NT_PRXFPREG: { + if (thread_infos_.empty()) + return false; + + ThreadInfo* info = &thread_infos_.back(); + if (description.length() != sizeof(info->fpxregs)) { + fprintf(stderr, "Found NT_PRXFPREG descriptor of unexpected size\n"); + return false; + } + + memcpy(&info->fpxregs, description.data(), sizeof(info->fpxregs)); + break; + } +#endif + } + note = note.GetNextNote(); + } while (note.IsValid()); + + return true; +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_core_dumper.h b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_core_dumper.h new file mode 100644 index 000000000..8a7c924b6 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_core_dumper.h @@ -0,0 +1,125 @@ +// 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. + +// linux_core_dumper.h: Define the google_breakpad::LinuxCoreDumper +// class, which is derived from google_breakpad::LinuxDumper to extract +// information from a crashed process via its core dump and proc files. + +#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINUX_CORE_DUMPER_H_ +#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_CORE_DUMPER_H_ + +#include "client/linux/minidump_writer/linux_dumper.h" +#include "common/linux/elf_core_dump.h" +#include "common/linux/memory_mapped_file.h" + +namespace google_breakpad { + +class LinuxCoreDumper : public LinuxDumper { + public: + // Constructs a dumper for extracting information of a given process + // with a process ID of |pid| via its core dump file at |core_path| and + // its proc files at |procfs_path|. If |procfs_path| is a copy of + // /proc/<pid>, it should contain the following files: + // auxv, cmdline, environ, exe, maps, status + // See LinuxDumper for the purpose of |root_prefix|. + LinuxCoreDumper(pid_t pid, const char* core_path, const char* procfs_path, + const char* root_prefix = ""); + + // Implements LinuxDumper::BuildProcPath(). + // Builds a proc path for a certain pid for a node (/proc/<pid>/<node>). + // |path| is a character array of at least NAME_MAX bytes to return the + // result.|node| is the final node without any slashes. Return true on + // success. + // + // As this dumper performs a post-mortem dump and makes use of a copy + // of the proc files of the crashed process, this derived method does + // not actually make use of |pid| and always returns a subpath of + // |procfs_path_| regardless of whether |pid| corresponds to the main + // process or a thread of the process, i.e. assuming both the main process + // and its threads have the following proc files with the same content: + // auxv, cmdline, environ, exe, maps, status + virtual bool BuildProcPath(char* path, pid_t pid, const char* node) const; + + // Implements LinuxDumper::CopyFromProcess(). + // Copies content of |length| bytes from a given process |child|, + // starting from |src|, into |dest|. This method extracts the content + // the core dump and fills |dest| with a sequence of marker bytes + // if the expected data is not found in the core dump. Returns true if + // the expected data is found in the core dump. + virtual bool CopyFromProcess(void* dest, pid_t child, const void* src, + size_t length); + + // Implements LinuxDumper::GetThreadInfoByIndex(). + // Reads information about the |index|-th thread of |threads_|. + // Returns true on success. One must have called |ThreadsSuspend| first. + virtual bool GetThreadInfoByIndex(size_t index, ThreadInfo* info); + + // Implements LinuxDumper::IsPostMortem(). + // Always returns true to indicate that this dumper performs a + // post-mortem dump of a crashed process via a core dump file. + virtual bool IsPostMortem() const; + + // Implements LinuxDumper::ThreadsSuspend(). + // As the dumper performs a post-mortem dump via a core dump file, + // there is no threads to suspend. This method does nothing and + // always returns true. + virtual bool ThreadsSuspend(); + + // Implements LinuxDumper::ThreadsResume(). + // As the dumper performs a post-mortem dump via a core dump file, + // there is no threads to resume. This method does nothing and + // always returns true. + virtual bool ThreadsResume(); + + protected: + // Implements LinuxDumper::EnumerateThreads(). + // Enumerates all threads of the given process into |threads_|. + virtual bool EnumerateThreads(); + + private: + // Path of the core dump file. + const char* core_path_; + + // Path of the directory containing the proc files of the given process, + // which is usually a copy of /proc/<pid>. + const char* procfs_path_; + + // Memory-mapped core dump file at |core_path_|. + MemoryMappedFile mapped_core_file_; + + // Content of the core dump file. + ElfCoreDump core_; + + // Thread info found in the core dump file. + wasteful_vector<ThreadInfo> thread_infos_; +}; + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_HANDLER_LINUX_CORE_DUMPER_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_core_dumper_unittest.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_core_dumper_unittest.cc new file mode 100644 index 000000000..ae0c965b3 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_core_dumper_unittest.cc @@ -0,0 +1,128 @@ +// 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. + +// linux_core_dumper_unittest.cc: +// Unit tests for google_breakpad::LinuxCoreDumoer. + +#include <string> + +#include "breakpad_googletest_includes.h" +#include "client/linux/minidump_writer/linux_core_dumper.h" +#include "common/linux/tests/crash_generator.h" +#include "common/using_std_string.h" + +using namespace google_breakpad; + +TEST(LinuxCoreDumperTest, GetMappingAbsolutePath) { + const LinuxCoreDumper dumper(getpid(), "core", "/tmp", "/mnt/root"); + const MappingInfo mapping = { 0, 0, 0, false, "/usr/lib/libc.so" }; + + char path[PATH_MAX]; + dumper.GetMappingAbsolutePath(mapping, path); + + EXPECT_STREQ("/mnt/root/usr/lib/libc.so", path); +} + +TEST(LinuxCoreDumperTest, BuildProcPath) { + const pid_t pid = getpid(); + const char procfs_path[] = "/procfs_copy"; + LinuxCoreDumper dumper(getpid(), "core_file", procfs_path); + + char maps_path[NAME_MAX] = ""; + char maps_path_expected[NAME_MAX]; + snprintf(maps_path_expected, sizeof(maps_path_expected), + "%s/maps", procfs_path); + EXPECT_TRUE(dumper.BuildProcPath(maps_path, pid, "maps")); + EXPECT_STREQ(maps_path_expected, maps_path); + + EXPECT_FALSE(dumper.BuildProcPath(NULL, pid, "maps")); + EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, "")); + EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, NULL)); + + char long_node[NAME_MAX]; + size_t long_node_len = NAME_MAX - strlen(procfs_path) - 1; + memset(long_node, 'a', long_node_len); + long_node[long_node_len] = '\0'; + EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, long_node)); +} + +TEST(LinuxCoreDumperTest, VerifyDumpWithMultipleThreads) { + CrashGenerator crash_generator; + if (!crash_generator.HasDefaultCorePattern()) { + fprintf(stderr, "LinuxCoreDumperTest.VerifyDumpWithMultipleThreads test " + "is skipped due to non-default core pattern\n"); + return; + } + + const unsigned kNumOfThreads = 3; + const unsigned kCrashThread = 1; + const int kCrashSignal = SIGABRT; + pid_t child_pid; + ASSERT_TRUE(crash_generator.CreateChildCrash(kNumOfThreads, kCrashThread, + kCrashSignal, &child_pid)); + + const string core_file = crash_generator.GetCoreFilePath(); + const string procfs_path = crash_generator.GetDirectoryOfProcFilesCopy(); + +#if defined(__ANDROID__) + struct stat st; + if (stat(core_file.c_str(), &st) != 0) { + fprintf(stderr, "LinuxCoreDumperTest.VerifyDumpWithMultipleThreads test is " + "skipped due to no core file being generated"); + return; + } +#endif + + LinuxCoreDumper dumper(child_pid, core_file.c_str(), procfs_path.c_str()); + + EXPECT_TRUE(dumper.Init()); + + EXPECT_TRUE(dumper.IsPostMortem()); + + // These are no-ops and should always return true. + EXPECT_TRUE(dumper.ThreadsSuspend()); + EXPECT_TRUE(dumper.ThreadsResume()); + + // LinuxCoreDumper cannot determine the crash address and thus it always + // sets the crash address to 0. + EXPECT_EQ(0U, dumper.crash_address()); + EXPECT_EQ(kCrashSignal, dumper.crash_signal()); + EXPECT_EQ(crash_generator.GetThreadId(kCrashThread), + dumper.crash_thread()); + + EXPECT_EQ(kNumOfThreads, dumper.threads().size()); + for (unsigned i = 0; i < kNumOfThreads; ++i) { + ThreadInfo info; + EXPECT_TRUE(dumper.GetThreadInfoByIndex(i, &info)); + const void* stack; + size_t stack_len; + EXPECT_TRUE(dumper.GetStackInfo(&stack, &stack_len, info.stack_pointer)); + EXPECT_EQ(getpid(), info.ppid); + } +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_dumper.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_dumper.cc new file mode 100644 index 000000000..bdbdc6507 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_dumper.cc @@ -0,0 +1,776 @@ +// Copyright (c) 2010, 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. + +// linux_dumper.cc: Implement google_breakpad::LinuxDumper. +// See linux_dumper.h for details. + +// This code deals with the mechanics of getting information about a crashed +// process. Since this code may run in a compromised address space, the same +// rules apply as detailed at the top of minidump_writer.h: no libc calls and +// use the alternative allocator. + +#include "client/linux/minidump_writer/linux_dumper.h" + +#include <assert.h> +#include <elf.h> +#include <fcntl.h> +#include <limits.h> +#include <stddef.h> +#include <string.h> + +#include "client/linux/minidump_writer/line_reader.h" +#include "common/linux/elfutils.h" +#include "common/linux/file_id.h" +#include "common/linux/linux_libc_support.h" +#include "common/linux/memory_mapped_file.h" +#include "common/linux/safe_readlink.h" +#include "third_party/lss/linux_syscall_support.h" + +#if defined(__ANDROID__) + +// Android packed relocations definitions are not yet available from the +// NDK header files, so we have to provide them manually here. +#ifndef DT_LOOS +#define DT_LOOS 0x6000000d +#endif +#ifndef DT_ANDROID_REL +static const int DT_ANDROID_REL = DT_LOOS + 2; +#endif +#ifndef DT_ANDROID_RELA +static const int DT_ANDROID_RELA = DT_LOOS + 4; +#endif + +#endif // __ANDROID __ + +static const char kMappedFileUnsafePrefix[] = "/dev/"; +static const char kDeletedSuffix[] = " (deleted)"; +static const char kReservedFlags[] = " ---p"; + +inline static bool IsMappedFileOpenUnsafe( + const google_breakpad::MappingInfo& mapping) { + // It is unsafe to attempt to open a mapped file that lives under /dev, + // because the semantics of the open may be driver-specific so we'd risk + // hanging the crash dumper. And a file in /dev/ almost certainly has no + // ELF file identifier anyways. + return my_strncmp(mapping.name, + kMappedFileUnsafePrefix, + sizeof(kMappedFileUnsafePrefix) - 1) == 0; +} + +namespace google_breakpad { + +#if defined(__CHROMEOS__) + +namespace { + +// Recover memory mappings before writing dump on ChromeOS +// +// On Linux, breakpad relies on /proc/[pid]/maps to associate symbols from +// addresses. ChromeOS' hugepage implementation replaces some segments with +// anonymous private pages, which is a restriction of current implementation +// in Linux kernel at the time of writing. Thus, breakpad can no longer +// symbolize addresses from those text segments replaced with hugepages. +// +// This postprocess tries to recover the mappings. Because hugepages are always +// inserted in between some .text sections, it tries to infer the names and +// offsets of the segments, by looking at segments immediately precede and +// succeed them. +// +// For example, a text segment before hugepage optimization +// 02001000-03002000 r-xp /opt/google/chrome/chrome +// +// can be broken into +// 02001000-02200000 r-xp /opt/google/chrome/chrome +// 02200000-03000000 r-xp +// 03000000-03002000 r-xp /opt/google/chrome/chrome +// +// For more details, see: +// crbug.com/628040 ChromeOS' use of hugepages confuses crash symbolization + +// Copied from CrOS' hugepage implementation, which is unlikely to change. +// The hugepage size is 2M. +const unsigned int kHpageShift = 21; +const size_t kHpageSize = (1 << kHpageShift); +const size_t kHpageMask = (~(kHpageSize - 1)); + +// Find and merge anonymous r-xp segments with surrounding named segments. +// There are two cases: + +// Case 1: curr, next +// curr is anonymous +// curr is r-xp +// curr.size >= 2M +// curr.size is a multiple of 2M. +// next is backed by some file. +// curr and next are contiguous. +// offset(next) == sizeof(curr) +void TryRecoverMappings(MappingInfo *curr, MappingInfo *next) { + // Merged segments are marked with size = 0. + if (curr->size == 0 || next->size == 0) + return; + + if (curr->size >= kHpageSize && + curr->exec && + (curr->size & kHpageMask) == curr->size && + (curr->start_addr & kHpageMask) == curr->start_addr && + curr->name[0] == '\0' && + next->name[0] != '\0' && + curr->start_addr + curr->size == next->start_addr && + curr->size == next->offset) { + + // matched + my_strlcpy(curr->name, next->name, NAME_MAX); + if (next->exec) { + // (curr, next) + curr->size += next->size; + next->size = 0; + } + } +} + +// Case 2: prev, curr, next +// curr is anonymous +// curr is r-xp +// curr.size >= 2M +// curr.size is a multiple of 2M. +// next and prev are backed by the same file. +// prev, curr and next are contiguous. +// offset(next) == offset(prev) + sizeof(prev) + sizeof(curr) +void TryRecoverMappings(MappingInfo *prev, MappingInfo *curr, + MappingInfo *next) { + // Merged segments are marked with size = 0. + if (prev->size == 0 || curr->size == 0 || next->size == 0) + return; + + if (curr->size >= kHpageSize && + curr->exec && + (curr->size & kHpageMask) == curr->size && + (curr->start_addr & kHpageMask) == curr->start_addr && + curr->name[0] == '\0' && + next->name[0] != '\0' && + curr->start_addr + curr->size == next->start_addr && + prev->start_addr + prev->size == curr->start_addr && + my_strncmp(prev->name, next->name, NAME_MAX) == 0 && + next->offset == prev->offset + prev->size + curr->size) { + + // matched + my_strlcpy(curr->name, prev->name, NAME_MAX); + if (prev->exec) { + curr->offset = prev->offset; + curr->start_addr = prev->start_addr; + if (next->exec) { + // (prev, curr, next) + curr->size += prev->size + next->size; + prev->size = 0; + next->size = 0; + } else { + // (prev, curr), next + curr->size += prev->size; + prev->size = 0; + } + } else { + curr->offset = prev->offset + prev->size; + if (next->exec) { + // prev, (curr, next) + curr->size += next->size; + next->size = 0; + } else { + // prev, curr, next + } + } + } +} + +// mappings_ is sorted excepted for the first entry. +// This function tries to merge segemnts into the first entry, +// then check for other sorted entries. +// See LinuxDumper::EnumerateMappings(). +void CrOSPostProcessMappings(wasteful_vector<MappingInfo*>& mappings) { + // Find the candidate "next" to first segment, which is the only one that + // could be out-of-order. + size_t l = 1; + size_t r = mappings.size(); + size_t next = mappings.size(); + while (l < r) { + int m = (l + r) / 2; + if (mappings[m]->start_addr > mappings[0]->start_addr) + r = next = m; + else + l = m + 1; + } + + // Try to merge segments into the first. + if (next < mappings.size()) { + TryRecoverMappings(mappings[0], mappings[next]); + if (next - 1 > 0) + TryRecoverMappings(mappings[next - 1], mappings[0], mappings[next]); + } + + // Iterate through normal, sorted cases. + // Normal case 1. + for (size_t i = 1; i < mappings.size() - 1; i++) + TryRecoverMappings(mappings[i], mappings[i + 1]); + + // Normal case 2. + for (size_t i = 1; i < mappings.size() - 2; i++) + TryRecoverMappings(mappings[i], mappings[i + 1], mappings[i + 2]); + + // Collect merged (size == 0) segments. + size_t f, e; + for (f = e = 0; e < mappings.size(); e++) + if (mappings[e]->size > 0) + mappings[f++] = mappings[e]; + mappings.resize(f); +} + +} // namespace +#endif // __CHROMEOS__ + +// All interesting auvx entry types are below AT_SYSINFO_EHDR +#define AT_MAX AT_SYSINFO_EHDR + +LinuxDumper::LinuxDumper(pid_t pid, const char* root_prefix) + : pid_(pid), + root_prefix_(root_prefix), + crash_address_(0), + crash_signal_(0), + crash_thread_(pid), + threads_(&allocator_, 8), + mappings_(&allocator_), + auxv_(&allocator_, AT_MAX + 1) { + assert(root_prefix_ && my_strlen(root_prefix_) < PATH_MAX); + // The passed-in size to the constructor (above) is only a hint. + // Must call .resize() to do actual initialization of the elements. + auxv_.resize(AT_MAX + 1); +} + +LinuxDumper::~LinuxDumper() { +} + +bool LinuxDumper::Init() { + return ReadAuxv() && EnumerateThreads() && EnumerateMappings(); +} + +bool LinuxDumper::LateInit() { +#if defined(__ANDROID__) + LatePostprocessMappings(); +#endif + +#if defined(__CHROMEOS__) + CrOSPostProcessMappings(mappings_); +#endif + + return true; +} + +bool +LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping, + bool member, + unsigned int mapping_id, + wasteful_vector<uint8_t>& identifier) { + assert(!member || mapping_id < mappings_.size()); + if (IsMappedFileOpenUnsafe(mapping)) + return false; + + // Special-case linux-gate because it's not a real file. + if (my_strcmp(mapping.name, kLinuxGateLibraryName) == 0) { + void* linux_gate = NULL; + if (pid_ == sys_getpid()) { + linux_gate = reinterpret_cast<void*>(mapping.start_addr); + } else { + linux_gate = allocator_.Alloc(mapping.size); + CopyFromProcess(linux_gate, pid_, + reinterpret_cast<const void*>(mapping.start_addr), + mapping.size); + } + return FileID::ElfFileIdentifierFromMappedFile(linux_gate, identifier); + } + + char filename[PATH_MAX]; + if (!GetMappingAbsolutePath(mapping, filename)) + return false; + bool filename_modified = HandleDeletedFileInMapping(filename); + + MemoryMappedFile mapped_file(filename, mapping.offset); + if (!mapped_file.data() || mapped_file.size() < SELFMAG) + return false; + + bool success = + FileID::ElfFileIdentifierFromMappedFile(mapped_file.data(), identifier); + if (success && member && filename_modified) { + mappings_[mapping_id]->name[my_strlen(mapping.name) - + sizeof(kDeletedSuffix) + 1] = '\0'; + } + + return success; +} + +bool LinuxDumper::GetMappingAbsolutePath(const MappingInfo& mapping, + char path[PATH_MAX]) const { + return my_strlcpy(path, root_prefix_, PATH_MAX) < PATH_MAX && + my_strlcat(path, mapping.name, PATH_MAX) < PATH_MAX; +} + +namespace { +bool ElfFileSoNameFromMappedFile( + const void* elf_base, char* soname, size_t soname_size) { + if (!IsValidElf(elf_base)) { + // Not ELF + return false; + } + + const void* segment_start; + size_t segment_size; + int elf_class; + if (!FindElfSection(elf_base, ".dynamic", SHT_DYNAMIC, + &segment_start, &segment_size, &elf_class)) { + // No dynamic section + return false; + } + + const void* dynstr_start; + size_t dynstr_size; + if (!FindElfSection(elf_base, ".dynstr", SHT_STRTAB, + &dynstr_start, &dynstr_size, &elf_class)) { + // No dynstr section + return false; + } + + const ElfW(Dyn)* dynamic = static_cast<const ElfW(Dyn)*>(segment_start); + size_t dcount = segment_size / sizeof(ElfW(Dyn)); + for (const ElfW(Dyn)* dyn = dynamic; dyn < dynamic + dcount; ++dyn) { + if (dyn->d_tag == DT_SONAME) { + const char* dynstr = static_cast<const char*>(dynstr_start); + if (dyn->d_un.d_val >= dynstr_size) { + // Beyond the end of the dynstr section + return false; + } + const char* str = dynstr + dyn->d_un.d_val; + const size_t maxsize = dynstr_size - dyn->d_un.d_val; + my_strlcpy(soname, str, maxsize < soname_size ? maxsize : soname_size); + return true; + } + } + + // Did not find SONAME + return false; +} + +// Find the shared object name (SONAME) by examining the ELF information +// for |mapping|. If the SONAME is found copy it into the passed buffer +// |soname| and return true. The size of the buffer is |soname_size|. +// The SONAME will be truncated if it is too long to fit in the buffer. +bool ElfFileSoName(const LinuxDumper& dumper, + const MappingInfo& mapping, char* soname, size_t soname_size) { + if (IsMappedFileOpenUnsafe(mapping)) { + // Not safe + return false; + } + + char filename[PATH_MAX]; + if (!dumper.GetMappingAbsolutePath(mapping, filename)) + return false; + + MemoryMappedFile mapped_file(filename, mapping.offset); + if (!mapped_file.data() || mapped_file.size() < SELFMAG) { + // mmap failed + return false; + } + + return ElfFileSoNameFromMappedFile(mapped_file.data(), soname, soname_size); +} + +} // namespace + + +void LinuxDumper::GetMappingEffectiveNameAndPath(const MappingInfo& mapping, + char* file_path, + size_t file_path_size, + char* file_name, + size_t file_name_size) { + my_strlcpy(file_path, mapping.name, file_path_size); + + // If an executable is mapped from a non-zero offset, this is likely because + // the executable was loaded directly from inside an archive file (e.g., an + // apk on Android). We try to find the name of the shared object (SONAME) by + // looking in the file for ELF sections. + bool mapped_from_archive = false; + if (mapping.exec && mapping.offset != 0) { + mapped_from_archive = + ElfFileSoName(*this, mapping, file_name, file_name_size); + } + + if (mapped_from_archive) { + // Some tools (e.g., stackwalk) extract the basename from the pathname. In + // this case, we append the file_name to the mapped archive path as follows: + // file_name := libname.so + // file_path := /path/to/ARCHIVE.APK/libname.so + if (my_strlen(file_path) + 1 + my_strlen(file_name) < file_path_size) { + my_strlcat(file_path, "/", file_path_size); + my_strlcat(file_path, file_name, file_path_size); + } + } else { + // Common case: + // file_path := /path/to/libname.so + // file_name := libname.so + const char* basename = my_strrchr(file_path, '/'); + basename = basename == NULL ? file_path : (basename + 1); + my_strlcpy(file_name, basename, file_name_size); + } +} + +bool LinuxDumper::ReadAuxv() { + char auxv_path[NAME_MAX]; + if (!BuildProcPath(auxv_path, pid_, "auxv")) { + return false; + } + + int fd = sys_open(auxv_path, O_RDONLY, 0); + if (fd < 0) { + return false; + } + + elf_aux_entry one_aux_entry; + bool res = false; + while (sys_read(fd, + &one_aux_entry, + sizeof(elf_aux_entry)) == sizeof(elf_aux_entry) && + one_aux_entry.a_type != AT_NULL) { + if (one_aux_entry.a_type <= AT_MAX) { + auxv_[one_aux_entry.a_type] = one_aux_entry.a_un.a_val; + res = true; + } + } + sys_close(fd); + return res; +} + +bool LinuxDumper::EnumerateMappings() { + char maps_path[NAME_MAX]; + if (!BuildProcPath(maps_path, pid_, "maps")) + return false; + + // linux_gate_loc is the beginning of the kernel's mapping of + // linux-gate.so in the process. It doesn't actually show up in the + // maps list as a filename, but it can be found using the AT_SYSINFO_EHDR + // aux vector entry, which gives the information necessary to special + // case its entry when creating the list of mappings. + // See http://www.trilithium.com/johan/2005/08/linux-gate/ for more + // information. + const void* linux_gate_loc = + reinterpret_cast<void *>(auxv_[AT_SYSINFO_EHDR]); + // Although the initial executable is usually the first mapping, it's not + // guaranteed (see http://crosbug.com/25355); therefore, try to use the + // actual entry point to find the mapping. + const void* entry_point_loc = reinterpret_cast<void *>(auxv_[AT_ENTRY]); + + const int fd = sys_open(maps_path, O_RDONLY, 0); + if (fd < 0) + return false; + LineReader* const line_reader = new(allocator_) LineReader(fd); + + const char* line; + unsigned line_len; + while (line_reader->GetNextLine(&line, &line_len)) { + uintptr_t start_addr, end_addr, offset; + + const char* i1 = my_read_hex_ptr(&start_addr, line); + if (*i1 == '-') { + const char* i2 = my_read_hex_ptr(&end_addr, i1 + 1); + if (*i2 == ' ') { + bool exec = (*(i2 + 3) == 'x'); + const char* i3 = my_read_hex_ptr(&offset, i2 + 6 /* skip ' rwxp ' */); + if (*i3 == ' ') { + const char* name = NULL; + // Only copy name if the name is a valid path name, or if + // it's the VDSO image. + if (((name = my_strchr(line, '/')) == NULL) && + linux_gate_loc && + reinterpret_cast<void*>(start_addr) == linux_gate_loc) { + name = kLinuxGateLibraryName; + offset = 0; + } + // Merge adjacent mappings with the same name into one module, + // assuming they're a single library mapped by the dynamic linker + if (name && !mappings_.empty()) { + MappingInfo* module = mappings_.back(); + if ((start_addr == module->start_addr + module->size) && + (my_strlen(name) == my_strlen(module->name)) && + (my_strncmp(name, module->name, my_strlen(name)) == 0)) { + module->size = end_addr - module->start_addr; + line_reader->PopLine(line_len); + continue; + } + } + // Also merge mappings that result from address ranges that the + // linker reserved but which a loaded library did not use. These + // appear as an anonymous private mapping with no access flags set + // and which directly follow an executable mapping. + if (!name && !mappings_.empty()) { + MappingInfo* module = mappings_.back(); + if ((start_addr == module->start_addr + module->size) && + module->exec && + module->name[0] == '/' && + offset == 0 && my_strncmp(i2, + kReservedFlags, + sizeof(kReservedFlags) - 1) == 0) { + module->size = end_addr - module->start_addr; + line_reader->PopLine(line_len); + continue; + } + } + MappingInfo* const module = new(allocator_) MappingInfo; + my_memset(module, 0, sizeof(MappingInfo)); + module->start_addr = start_addr; + module->size = end_addr - start_addr; + module->offset = offset; + module->exec = exec; + if (name != NULL) { + const unsigned l = my_strlen(name); + if (l < sizeof(module->name)) + my_memcpy(module->name, name, l); + } + // If this is the entry-point mapping, and it's not already the + // first one, then we need to make it be first. This is because + // the minidump format assumes the first module is the one that + // corresponds to the main executable (as codified in + // processor/minidump.cc:MinidumpModuleList::GetMainModule()). + if (entry_point_loc && + (entry_point_loc >= + reinterpret_cast<void*>(module->start_addr)) && + (entry_point_loc < + reinterpret_cast<void*>(module->start_addr+module->size)) && + !mappings_.empty()) { + // push the module onto the front of the list. + mappings_.resize(mappings_.size() + 1); + for (size_t idx = mappings_.size() - 1; idx > 0; idx--) + mappings_[idx] = mappings_[idx - 1]; + mappings_[0] = module; + } else { + mappings_.push_back(module); + } + } + } + } + line_reader->PopLine(line_len); + } + + sys_close(fd); + + return !mappings_.empty(); +} + +#if defined(__ANDROID__) + +bool LinuxDumper::GetLoadedElfHeader(uintptr_t start_addr, ElfW(Ehdr)* ehdr) { + CopyFromProcess(ehdr, pid_, + reinterpret_cast<const void*>(start_addr), + sizeof(*ehdr)); + return my_memcmp(&ehdr->e_ident, ELFMAG, SELFMAG) == 0; +} + +void LinuxDumper::ParseLoadedElfProgramHeaders(ElfW(Ehdr)* ehdr, + uintptr_t start_addr, + uintptr_t* min_vaddr_ptr, + uintptr_t* dyn_vaddr_ptr, + size_t* dyn_count_ptr) { + uintptr_t phdr_addr = start_addr + ehdr->e_phoff; + + const uintptr_t max_addr = UINTPTR_MAX; + uintptr_t min_vaddr = max_addr; + uintptr_t dyn_vaddr = 0; + size_t dyn_count = 0; + + for (size_t i = 0; i < ehdr->e_phnum; ++i) { + ElfW(Phdr) phdr; + CopyFromProcess(&phdr, pid_, + reinterpret_cast<const void*>(phdr_addr), + sizeof(phdr)); + if (phdr.p_type == PT_LOAD && phdr.p_vaddr < min_vaddr) { + min_vaddr = phdr.p_vaddr; + } + if (phdr.p_type == PT_DYNAMIC) { + dyn_vaddr = phdr.p_vaddr; + dyn_count = phdr.p_memsz / sizeof(ElfW(Dyn)); + } + phdr_addr += sizeof(phdr); + } + + *min_vaddr_ptr = min_vaddr; + *dyn_vaddr_ptr = dyn_vaddr; + *dyn_count_ptr = dyn_count; +} + +bool LinuxDumper::HasAndroidPackedRelocations(uintptr_t load_bias, + uintptr_t dyn_vaddr, + size_t dyn_count) { + uintptr_t dyn_addr = load_bias + dyn_vaddr; + for (size_t i = 0; i < dyn_count; ++i) { + ElfW(Dyn) dyn; + CopyFromProcess(&dyn, pid_, + reinterpret_cast<const void*>(dyn_addr), + sizeof(dyn)); + if (dyn.d_tag == DT_ANDROID_REL || dyn.d_tag == DT_ANDROID_RELA) { + return true; + } + dyn_addr += sizeof(dyn); + } + return false; +} + +uintptr_t LinuxDumper::GetEffectiveLoadBias(ElfW(Ehdr)* ehdr, + uintptr_t start_addr) { + uintptr_t min_vaddr = 0; + uintptr_t dyn_vaddr = 0; + size_t dyn_count = 0; + ParseLoadedElfProgramHeaders(ehdr, start_addr, + &min_vaddr, &dyn_vaddr, &dyn_count); + // If |min_vaddr| is non-zero and we find Android packed relocation tags, + // return the effective load bias. + if (min_vaddr != 0) { + const uintptr_t load_bias = start_addr - min_vaddr; + if (HasAndroidPackedRelocations(load_bias, dyn_vaddr, dyn_count)) { + return load_bias; + } + } + // Either |min_vaddr| is zero, or it is non-zero but we did not find the + // expected Android packed relocations tags. + return start_addr; +} + +void LinuxDumper::LatePostprocessMappings() { + for (size_t i = 0; i < mappings_.size(); ++i) { + // Only consider exec mappings that indicate a file path was mapped, and + // where the ELF header indicates a mapped shared library. + MappingInfo* mapping = mappings_[i]; + if (!(mapping->exec && mapping->name[0] == '/')) { + continue; + } + ElfW(Ehdr) ehdr; + if (!GetLoadedElfHeader(mapping->start_addr, &ehdr)) { + continue; + } + if (ehdr.e_type == ET_DYN) { + // Compute the effective load bias for this mapped library, and update + // the mapping to hold that rather than |start_addr|, at the same time + // adjusting |size| to account for the change in |start_addr|. Where + // the library does not contain Android packed relocations, + // GetEffectiveLoadBias() returns |start_addr| and the mapping entry + // is not changed. + const uintptr_t load_bias = GetEffectiveLoadBias(&ehdr, + mapping->start_addr); + mapping->size += mapping->start_addr - load_bias; + mapping->start_addr = load_bias; + } + } +} + +#endif // __ANDROID__ + +// Get information about the stack, given the stack pointer. We don't try to +// walk the stack since we might not have all the information needed to do +// unwind. So we just grab, up to, 32k of stack. +bool LinuxDumper::GetStackInfo(const void** stack, size_t* stack_len, + uintptr_t int_stack_pointer) { + // Move the stack pointer to the bottom of the page that it's in. + const uintptr_t page_size = getpagesize(); + + uint8_t* const stack_pointer = + reinterpret_cast<uint8_t*>(int_stack_pointer & ~(page_size - 1)); + + // The number of bytes of stack which we try to capture. + static const ptrdiff_t kStackToCapture = 32 * 1024; + + const MappingInfo* mapping = FindMapping(stack_pointer); + if (!mapping) + return false; + const ptrdiff_t offset = stack_pointer - + reinterpret_cast<uint8_t*>(mapping->start_addr); + const ptrdiff_t distance_to_end = + static_cast<ptrdiff_t>(mapping->size) - offset; + *stack_len = distance_to_end > kStackToCapture ? + kStackToCapture : distance_to_end; + *stack = stack_pointer; + return true; +} + +// Find the mapping which the given memory address falls in. +const MappingInfo* LinuxDumper::FindMapping(const void* address) const { + const uintptr_t addr = (uintptr_t) address; + + for (size_t i = 0; i < mappings_.size(); ++i) { + const uintptr_t start = static_cast<uintptr_t>(mappings_[i]->start_addr); + if (addr >= start && addr - start < mappings_[i]->size) + return mappings_[i]; + } + + return NULL; +} + +bool LinuxDumper::HandleDeletedFileInMapping(char* path) const { + static const size_t kDeletedSuffixLen = sizeof(kDeletedSuffix) - 1; + + // Check for ' (deleted)' in |path|. + // |path| has to be at least as long as "/x (deleted)". + const size_t path_len = my_strlen(path); + if (path_len < kDeletedSuffixLen + 2) + return false; + if (my_strncmp(path + path_len - kDeletedSuffixLen, kDeletedSuffix, + kDeletedSuffixLen) != 0) { + return false; + } + + // Check |path| against the /proc/pid/exe 'symlink'. + char exe_link[NAME_MAX]; + if (!BuildProcPath(exe_link, pid_, "exe")) + return false; + MappingInfo new_mapping = {0}; + if (!SafeReadLink(exe_link, new_mapping.name)) + return false; + char new_path[PATH_MAX]; + if (!GetMappingAbsolutePath(new_mapping, new_path)) + return false; + if (my_strcmp(path, new_path) != 0) + return false; + + // Check to see if someone actually named their executable 'foo (deleted)'. + struct kernel_stat exe_stat; + struct kernel_stat new_path_stat; + if (sys_stat(exe_link, &exe_stat) == 0 && + sys_stat(new_path, &new_path_stat) == 0 && + exe_stat.st_dev == new_path_stat.st_dev && + exe_stat.st_ino == new_path_stat.st_ino) { + return false; + } + + my_memcpy(path, exe_link, NAME_MAX); + return true; +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_dumper.h b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_dumper.h new file mode 100644 index 000000000..c3c799267 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_dumper.h @@ -0,0 +1,265 @@ +// Copyright (c) 2010, 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. + +// linux_dumper.h: Define the google_breakpad::LinuxDumper class, which +// is a base class for extracting information of a crashed process. It +// was originally a complete implementation using the ptrace API, but +// has been refactored to allow derived implementations supporting both +// ptrace and core dump. A portion of the original implementation is now +// in google_breakpad::LinuxPtraceDumper (see linux_ptrace_dumper.h for +// details). + +#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_ +#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_DUMPER_H_ + +#include <elf.h> +#if defined(__ANDROID__) +#include <link.h> +#endif +#include <linux/limits.h> +#include <stdint.h> +#include <sys/types.h> +#include <sys/user.h> + +#include "client/linux/dump_writer_common/mapping_info.h" +#include "client/linux/dump_writer_common/thread_info.h" +#include "common/linux/file_id.h" +#include "common/memory.h" +#include "google_breakpad/common/minidump_format.h" + +namespace google_breakpad { + +// Typedef for our parsing of the auxv variables in /proc/pid/auxv. +#if defined(__i386) || defined(__ARM_EABI__) || \ + (defined(__mips__) && _MIPS_SIM == _ABIO32) +typedef Elf32_auxv_t elf_aux_entry; +#elif defined(__x86_64) || defined(__aarch64__) || \ + (defined(__mips__) && _MIPS_SIM != _ABIO32) +typedef Elf64_auxv_t elf_aux_entry; +#endif + +typedef __typeof__(((elf_aux_entry*) 0)->a_un.a_val) elf_aux_val_t; + +// When we find the VDSO mapping in the process's address space, this +// is the name we use for it when writing it to the minidump. +// This should always be less than NAME_MAX! +const char kLinuxGateLibraryName[] = "linux-gate.so"; + +class LinuxDumper { + public: + // The |root_prefix| is prepended to mapping paths before opening them, which + // is useful if the crash originates from a chroot. + explicit LinuxDumper(pid_t pid, const char* root_prefix = ""); + + virtual ~LinuxDumper(); + + // Parse the data for |threads| and |mappings|. + virtual bool Init(); + + // Take any actions that could not be taken in Init(). LateInit() is + // called after all other caller's initialization is complete, and in + // particular after it has called ThreadsSuspend(), so that ptrace is + // available. + virtual bool LateInit(); + + // Return true if the dumper performs a post-mortem dump. + virtual bool IsPostMortem() const = 0; + + // Suspend/resume all threads in the given process. + virtual bool ThreadsSuspend() = 0; + virtual bool ThreadsResume() = 0; + + // Read information about the |index|-th thread of |threads_|. + // Returns true on success. One must have called |ThreadsSuspend| first. + virtual bool GetThreadInfoByIndex(size_t index, ThreadInfo* info) = 0; + + // These are only valid after a call to |Init|. + const wasteful_vector<pid_t> &threads() { return threads_; } + const wasteful_vector<MappingInfo*> &mappings() { return mappings_; } + const MappingInfo* FindMapping(const void* address) const; + const wasteful_vector<elf_aux_val_t>& auxv() { return auxv_; } + + // Find a block of memory to take as the stack given the top of stack pointer. + // stack: (output) the lowest address in the memory area + // stack_len: (output) the length of the memory area + // stack_top: the current top of the stack + bool GetStackInfo(const void** stack, size_t* stack_len, uintptr_t stack_top); + + PageAllocator* allocator() { return &allocator_; } + + // Copy content of |length| bytes from a given process |child|, + // starting from |src|, into |dest|. Returns true on success. + virtual bool CopyFromProcess(void* dest, pid_t child, const void* src, + size_t length) = 0; + + // Builds a proc path for a certain pid for a node (/proc/<pid>/<node>). + // |path| is a character array of at least NAME_MAX bytes to return the + // result.|node| is the final node without any slashes. Returns true on + // success. + virtual bool BuildProcPath(char* path, pid_t pid, const char* node) const = 0; + + // Generate a File ID from the .text section of a mapped entry. + // If not a member, mapping_id is ignored. This method can also manipulate the + // |mapping|.name to truncate "(deleted)" from the file name if necessary. + bool ElfFileIdentifierForMapping(const MappingInfo& mapping, + bool member, + unsigned int mapping_id, + wasteful_vector<uint8_t>& identifier); + + uintptr_t crash_address() const { return crash_address_; } + void set_crash_address(uintptr_t crash_address) { + crash_address_ = crash_address; + } + + int crash_signal() const { return crash_signal_; } + void set_crash_signal(int crash_signal) { crash_signal_ = crash_signal; } + + pid_t crash_thread() const { return crash_thread_; } + void set_crash_thread(pid_t crash_thread) { crash_thread_ = crash_thread; } + + // Concatenates the |root_prefix_| and |mapping| path. Writes into |path| and + // returns true unless the string is too long. + bool GetMappingAbsolutePath(const MappingInfo& mapping, + char path[PATH_MAX]) const; + + // Extracts the effective path and file name of from |mapping|. In most cases + // the effective name/path are just the mapping's path and basename. In some + // other cases, however, a library can be mapped from an archive (e.g., when + // loading .so libs from an apk on Android) and this method is able to + // reconstruct the original file name. + void GetMappingEffectiveNameAndPath(const MappingInfo& mapping, + char* file_path, + size_t file_path_size, + char* file_name, + size_t file_name_size); + + protected: + bool ReadAuxv(); + + virtual bool EnumerateMappings(); + + virtual bool EnumerateThreads() = 0; + + // For the case where a running program has been deleted, it'll show up in + // /proc/pid/maps as "/path/to/program (deleted)". If this is the case, then + // see if '/path/to/program (deleted)' matches /proc/pid/exe and return + // /proc/pid/exe in |path| so ELF identifier generation works correctly. This + // also checks to see if '/path/to/program (deleted)' exists, so it does not + // get fooled by a poorly named binary. + // For programs that don't end with ' (deleted)', this is a no-op. + // This assumes |path| is a buffer with length NAME_MAX. + // Returns true if |path| is modified. + bool HandleDeletedFileInMapping(char* path) const; + + // ID of the crashed process. + const pid_t pid_; + + // Path of the root directory to which mapping paths are relative. + const char* const root_prefix_; + + // Virtual address at which the process crashed. + uintptr_t crash_address_; + + // Signal that terminated the crashed process. + int crash_signal_; + + // ID of the crashed thread. + pid_t crash_thread_; + + mutable PageAllocator allocator_; + + // IDs of all the threads. + wasteful_vector<pid_t> threads_; + + // Info from /proc/<pid>/maps. + wasteful_vector<MappingInfo*> mappings_; + + // Info from /proc/<pid>/auxv + wasteful_vector<elf_aux_val_t> auxv_; + +#if defined(__ANDROID__) + private: + // Android M and later support packed ELF relocations in shared libraries. + // Packing relocations changes the vaddr of the LOAD segments, such that + // the effective load bias is no longer the same as the start address of + // the memory mapping containing the executable parts of the library. The + // packing is applied to the stripped library run on the target, but not to + // any other library, and in particular not to the library used to generate + // breakpad symbols. As a result, we need to adjust the |start_addr| for + // any mapping that results from a shared library that contains Android + // packed relocations, so that it properly represents the effective library + // load bias. The following functions support this adjustment. + + // Check that a given mapping at |start_addr| is for an ELF shared library. + // If it is, place the ELF header in |ehdr| and return true. + // The first LOAD segment in an ELF shared library has offset zero, so the + // ELF file header is at the start of this map entry, and in already mapped + // memory. + bool GetLoadedElfHeader(uintptr_t start_addr, ElfW(Ehdr)* ehdr); + + // For the ELF file mapped at |start_addr|, iterate ELF program headers to + // find the min vaddr of all program header LOAD segments, the vaddr for + // the DYNAMIC segment, and a count of DYNAMIC entries. Return values in + // |min_vaddr_ptr|, |dyn_vaddr_ptr|, and |dyn_count_ptr|. + // The program header table is also in already mapped memory. + void ParseLoadedElfProgramHeaders(ElfW(Ehdr)* ehdr, + uintptr_t start_addr, + uintptr_t* min_vaddr_ptr, + uintptr_t* dyn_vaddr_ptr, + size_t* dyn_count_ptr); + + // Search the DYNAMIC tags for the ELF file with the given |load_bias|, and + // return true if the tags indicate that the file contains Android packed + // relocations. Dynamic tags are found at |dyn_vaddr| past the |load_bias|. + bool HasAndroidPackedRelocations(uintptr_t load_bias, + uintptr_t dyn_vaddr, + size_t dyn_count); + + // If the ELF file mapped at |start_addr| contained Android packed + // relocations, return the load bias that the system linker (or Chromium + // crazy linker) will have used. If the file did not contain Android + // packed relocations, returns |start_addr|, indicating that no adjustment + // is necessary. + // The effective load bias is |start_addr| adjusted downwards by the + // min vaddr in the library LOAD segments. + uintptr_t GetEffectiveLoadBias(ElfW(Ehdr)* ehdr, uintptr_t start_addr); + + // Called from LateInit(). Iterates |mappings_| and rewrites the |start_addr| + // field of any that represent ELF shared libraries with Android packed + // relocations, so that |start_addr| is the load bias that the system linker + // (or Chromium crazy linker) used. This value matches the addresses produced + // when the non-relocation-packed library is used for breakpad symbol + // generation. + void LatePostprocessMappings(); +#endif // __ANDROID__ +}; + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_HANDLER_LINUX_DUMPER_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_dumper_unittest_helper.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_dumper_unittest_helper.cc new file mode 100644 index 000000000..4ccb7201f --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_dumper_unittest_helper.cc @@ -0,0 +1,94 @@ +// Copyright (c) 2010, 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. +// +// Helper program for the linux_dumper class, which creates a bunch of +// threads. The first word of each thread's stack is set to the thread +// id. + +#include <pthread.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/syscall.h> +#include <unistd.h> + +#include "common/scoped_ptr.h" +#include "third_party/lss/linux_syscall_support.h" + +#if defined(__ARM_EABI__) +#define TID_PTR_REGISTER "r3" +#elif defined(__aarch64__) +#define TID_PTR_REGISTER "x3" +#elif defined(__i386) +#define TID_PTR_REGISTER "ecx" +#elif defined(__x86_64) +#define TID_PTR_REGISTER "rcx" +#elif defined(__mips__) +#define TID_PTR_REGISTER "$1" +#else +#error This test has not been ported to this platform. +#endif + +void *thread_function(void *data) { + int pipefd = *static_cast<int *>(data); + volatile pid_t thread_id = syscall(__NR_gettid); + // Signal parent that a thread has started. + uint8_t byte = 1; + if (write(pipefd, &byte, sizeof(byte)) != sizeof(byte)) { + perror("ERROR: parent notification failed"); + return NULL; + } + register volatile pid_t *thread_id_ptr asm(TID_PTR_REGISTER) = &thread_id; + while (true) + asm volatile ("" : : "r" (thread_id_ptr)); + return NULL; +} + +int main(int argc, char *argv[]) { + if (argc < 3) { + fprintf(stderr, + "usage: linux_dumper_unittest_helper <pipe fd> <# of threads>\n"); + return 1; + } + int pipefd = atoi(argv[1]); + int num_threads = atoi(argv[2]); + if (num_threads < 1) { + fprintf(stderr, "ERROR: number of threads is 0"); + return 1; + } + google_breakpad::scoped_array<pthread_t> threads(new pthread_t[num_threads]); + pthread_attr_t thread_attributes; + pthread_attr_init(&thread_attributes); + pthread_attr_setdetachstate(&thread_attributes, PTHREAD_CREATE_DETACHED); + for (int i = 1; i < num_threads; i++) { + pthread_create(&threads[i], &thread_attributes, &thread_function, &pipefd); + } + thread_function(&pipefd); + return 0; +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_ptrace_dumper.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_ptrace_dumper.cc new file mode 100644 index 000000000..c35e0e958 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_ptrace_dumper.cc @@ -0,0 +1,355 @@ +// 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. + +// linux_ptrace_dumper.cc: Implement google_breakpad::LinuxPtraceDumper. +// See linux_ptrace_dumper.h for detals. +// This class was originally splitted from google_breakpad::LinuxDumper. + +// This code deals with the mechanics of getting information about a crashed +// process. Since this code may run in a compromised address space, the same +// rules apply as detailed at the top of minidump_writer.h: no libc calls and +// use the alternative allocator. + +#include "client/linux/minidump_writer/linux_ptrace_dumper.h" + +#include <asm/ptrace.h> +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ptrace.h> +#include <sys/uio.h> +#include <sys/wait.h> + +#if defined(__i386) +#include <cpuid.h> +#endif + +#include "client/linux/minidump_writer/directory_reader.h" +#include "client/linux/minidump_writer/line_reader.h" +#include "common/linux/linux_libc_support.h" +#include "third_party/lss/linux_syscall_support.h" + +// Suspends a thread by attaching to it. +static bool SuspendThread(pid_t pid) { + // This may fail if the thread has just died or debugged. + errno = 0; + if (sys_ptrace(PTRACE_ATTACH, pid, NULL, NULL) != 0 && + errno != 0) { + return false; + } + while (sys_waitpid(pid, NULL, __WALL) < 0) { + if (errno != EINTR) { + sys_ptrace(PTRACE_DETACH, pid, NULL, NULL); + return false; + } + } +#if defined(__i386) || defined(__x86_64) + // On x86, the stack pointer is NULL or -1, when executing trusted code in + // the seccomp sandbox. Not only does this cause difficulties down the line + // when trying to dump the thread's stack, it also results in the minidumps + // containing information about the trusted threads. This information is + // generally completely meaningless and just pollutes the minidumps. + // We thus test the stack pointer and exclude any threads that are part of + // the seccomp sandbox's trusted code. + user_regs_struct regs; + if (sys_ptrace(PTRACE_GETREGS, pid, NULL, ®s) == -1 || +#if defined(__i386) + !regs.esp +#elif defined(__x86_64) + !regs.rsp +#endif + ) { + sys_ptrace(PTRACE_DETACH, pid, NULL, NULL); + return false; + } +#endif + return true; +} + +// Resumes a thread by detaching from it. +static bool ResumeThread(pid_t pid) { + return sys_ptrace(PTRACE_DETACH, pid, NULL, NULL) >= 0; +} + +namespace google_breakpad { + +LinuxPtraceDumper::LinuxPtraceDumper(pid_t pid) + : LinuxDumper(pid), + threads_suspended_(false) { +} + +bool LinuxPtraceDumper::BuildProcPath(char* path, pid_t pid, + const char* node) const { + if (!path || !node || pid <= 0) + return false; + + size_t node_len = my_strlen(node); + if (node_len == 0) + return false; + + const unsigned pid_len = my_uint_len(pid); + const size_t total_length = 6 + pid_len + 1 + node_len; + if (total_length >= NAME_MAX) + return false; + + my_memcpy(path, "/proc/", 6); + my_uitos(path + 6, pid, pid_len); + path[6 + pid_len] = '/'; + my_memcpy(path + 6 + pid_len + 1, node, node_len); + path[total_length] = '\0'; + return true; +} + +bool LinuxPtraceDumper::CopyFromProcess(void* dest, pid_t child, + const void* src, size_t length) { + unsigned long tmp = 55; + size_t done = 0; + static const size_t word_size = sizeof(tmp); + uint8_t* const local = (uint8_t*) dest; + uint8_t* const remote = (uint8_t*) src; + + while (done < length) { + const size_t l = (length - done > word_size) ? word_size : (length - done); + if (sys_ptrace(PTRACE_PEEKDATA, child, remote + done, &tmp) == -1) { + tmp = 0; + } + my_memcpy(local + done, &tmp, l); + done += l; + } + return true; +} + +// Read thread info from /proc/$pid/status. +// Fill out the |tgid|, |ppid| and |pid| members of |info|. If unavailable, +// these members are set to -1. Returns true iff all three members are +// available. +bool LinuxPtraceDumper::GetThreadInfoByIndex(size_t index, ThreadInfo* info) { + if (index >= threads_.size()) + return false; + + pid_t tid = threads_[index]; + + assert(info != NULL); + char status_path[NAME_MAX]; + if (!BuildProcPath(status_path, tid, "status")) + return false; + + const int fd = sys_open(status_path, O_RDONLY, 0); + if (fd < 0) + return false; + + LineReader* const line_reader = new(allocator_) LineReader(fd); + const char* line; + unsigned line_len; + + info->ppid = info->tgid = -1; + + while (line_reader->GetNextLine(&line, &line_len)) { + if (my_strncmp("Tgid:\t", line, 6) == 0) { + my_strtoui(&info->tgid, line + 6); + } else if (my_strncmp("PPid:\t", line, 6) == 0) { + my_strtoui(&info->ppid, line + 6); + } + + line_reader->PopLine(line_len); + } + sys_close(fd); + + if (info->ppid == -1 || info->tgid == -1) + return false; + +#ifdef PTRACE_GETREGSET + struct iovec io; + info->GetGeneralPurposeRegisters(&io.iov_base, &io.iov_len); + if (sys_ptrace(PTRACE_GETREGSET, tid, (void*)NT_PRSTATUS, (void*)&io) == -1) { + return false; + } + + info->GetFloatingPointRegisters(&io.iov_base, &io.iov_len); + if (sys_ptrace(PTRACE_GETREGSET, tid, (void*)NT_FPREGSET, (void*)&io) == -1) { + return false; + } +#else // PTRACE_GETREGSET + void* gp_addr; + info->GetGeneralPurposeRegisters(&gp_addr, NULL); + if (sys_ptrace(PTRACE_GETREGS, tid, NULL, gp_addr) == -1) { + return false; + } + +#if !(defined(__ANDROID__) && defined(__ARM_EABI__)) + // When running an arm build on an arm64 device, attempting to get the + // floating point registers fails. On Android, the floating point registers + // aren't written to the cpu context anyway, so just don't get them here. + // See http://crbug.com/508324 + void* fp_addr; + info->GetFloatingPointRegisters(&fp_addr, NULL); + if (sys_ptrace(PTRACE_GETFPREGS, tid, NULL, fp_addr) == -1) { + return false; + } +#endif +#endif // PTRACE_GETREGSET + +#if defined(__i386) +#if !defined(bit_FXSAVE) // e.g. Clang +#define bit_FXSAVE bit_FXSR +#endif + // Detect if the CPU supports the FXSAVE/FXRSTOR instructions + int eax, ebx, ecx, edx; + __cpuid(1, eax, ebx, ecx, edx); + if (edx & bit_FXSAVE) { + if (sys_ptrace(PTRACE_GETFPXREGS, tid, NULL, &info->fpxregs) == -1) { + return false; + } + } else { + memset(&info->fpxregs, 0, sizeof(info->fpxregs)); + } +#endif // defined(__i386) + +#if defined(__i386) || defined(__x86_64) + for (unsigned i = 0; i < ThreadInfo::kNumDebugRegisters; ++i) { + if (sys_ptrace( + PTRACE_PEEKUSER, tid, + reinterpret_cast<void*> (offsetof(struct user, + u_debugreg[0]) + i * + sizeof(debugreg_t)), + &info->dregs[i]) == -1) { + return false; + } + } +#endif + +#if defined(__mips__) + sys_ptrace(PTRACE_PEEKUSER, tid, + reinterpret_cast<void*>(DSP_BASE), &info->mcontext.hi1); + sys_ptrace(PTRACE_PEEKUSER, tid, + reinterpret_cast<void*>(DSP_BASE + 1), &info->mcontext.lo1); + sys_ptrace(PTRACE_PEEKUSER, tid, + reinterpret_cast<void*>(DSP_BASE + 2), &info->mcontext.hi2); + sys_ptrace(PTRACE_PEEKUSER, tid, + reinterpret_cast<void*>(DSP_BASE + 3), &info->mcontext.lo2); + sys_ptrace(PTRACE_PEEKUSER, tid, + reinterpret_cast<void*>(DSP_BASE + 4), &info->mcontext.hi3); + sys_ptrace(PTRACE_PEEKUSER, tid, + reinterpret_cast<void*>(DSP_BASE + 5), &info->mcontext.lo3); + sys_ptrace(PTRACE_PEEKUSER, tid, + reinterpret_cast<void*>(DSP_CONTROL), &info->mcontext.dsp); +#endif + + const uint8_t* stack_pointer; +#if defined(__i386) + my_memcpy(&stack_pointer, &info->regs.esp, sizeof(info->regs.esp)); +#elif defined(__x86_64) + my_memcpy(&stack_pointer, &info->regs.rsp, sizeof(info->regs.rsp)); +#elif defined(__ARM_EABI__) + my_memcpy(&stack_pointer, &info->regs.ARM_sp, sizeof(info->regs.ARM_sp)); +#elif defined(__aarch64__) + my_memcpy(&stack_pointer, &info->regs.sp, sizeof(info->regs.sp)); +#elif defined(__mips__) + stack_pointer = + reinterpret_cast<uint8_t*>(info->mcontext.gregs[MD_CONTEXT_MIPS_REG_SP]); +#else +#error "This code hasn't been ported to your platform yet." +#endif + info->stack_pointer = reinterpret_cast<uintptr_t>(stack_pointer); + + return true; +} + +bool LinuxPtraceDumper::IsPostMortem() const { + return false; +} + +bool LinuxPtraceDumper::ThreadsSuspend() { + if (threads_suspended_) + return true; + for (size_t i = 0; i < threads_.size(); ++i) { + if (!SuspendThread(threads_[i])) { + // If the thread either disappeared before we could attach to it, or if + // it was part of the seccomp sandbox's trusted code, it is OK to + // silently drop it from the minidump. + if (i < threads_.size() - 1) { + my_memmove(&threads_[i], &threads_[i + 1], + (threads_.size() - i - 1) * sizeof(threads_[i])); + } + threads_.resize(threads_.size() - 1); + --i; + } + } + threads_suspended_ = true; + return threads_.size() > 0; +} + +bool LinuxPtraceDumper::ThreadsResume() { + if (!threads_suspended_) + return false; + bool good = true; + for (size_t i = 0; i < threads_.size(); ++i) + good &= ResumeThread(threads_[i]); + threads_suspended_ = false; + return good; +} + +// Parse /proc/$pid/task to list all the threads of the process identified by +// pid. +bool LinuxPtraceDumper::EnumerateThreads() { + char task_path[NAME_MAX]; + if (!BuildProcPath(task_path, pid_, "task")) + return false; + + const int fd = sys_open(task_path, O_RDONLY | O_DIRECTORY, 0); + if (fd < 0) + return false; + DirectoryReader* dir_reader = new(allocator_) DirectoryReader(fd); + + // The directory may contain duplicate entries which we filter by assuming + // that they are consecutive. + int last_tid = -1; + const char* dent_name; + while (dir_reader->GetNextEntry(&dent_name)) { + if (my_strcmp(dent_name, ".") && + my_strcmp(dent_name, "..")) { + int tid = 0; + if (my_strtoui(&tid, dent_name) && + last_tid != tid) { + last_tid = tid; + threads_.push_back(tid); + } + } + dir_reader->PopEntry(); + } + + sys_close(fd); + return true; +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_ptrace_dumper.h b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_ptrace_dumper.h new file mode 100644 index 000000000..2ce834b0f --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_ptrace_dumper.h @@ -0,0 +1,92 @@ +// 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. + +// linux_ptrace_dumper.h: Define the google_breakpad::LinuxPtraceDumper +// class, which is derived from google_breakpad::LinuxDumper to extract +// information from a crashed process via ptrace. +// This class was originally splitted from google_breakpad::LinuxDumper. + +#ifndef CLIENT_LINUX_MINIDUMP_WRITER_LINUX_PTRACE_DUMPER_H_ +#define CLIENT_LINUX_MINIDUMP_WRITER_LINUX_PTRACE_DUMPER_H_ + +#include "client/linux/minidump_writer/linux_dumper.h" + +namespace google_breakpad { + +class LinuxPtraceDumper : public LinuxDumper { + public: + // Constructs a dumper for extracting information of a given process + // with a process ID of |pid|. + explicit LinuxPtraceDumper(pid_t pid); + + // Implements LinuxDumper::BuildProcPath(). + // Builds a proc path for a certain pid for a node (/proc/<pid>/<node>). + // |path| is a character array of at least NAME_MAX bytes to return the + // result. |node| is the final node without any slashes. Returns true on + // success. + virtual bool BuildProcPath(char* path, pid_t pid, const char* node) const; + + // Implements LinuxDumper::CopyFromProcess(). + // Copies content of |length| bytes from a given process |child|, + // starting from |src|, into |dest|. This method uses ptrace to extract + // the content from the target process. Always returns true. + virtual bool CopyFromProcess(void* dest, pid_t child, const void* src, + size_t length); + + // Implements LinuxDumper::GetThreadInfoByIndex(). + // Reads information about the |index|-th thread of |threads_|. + // Returns true on success. One must have called |ThreadsSuspend| first. + virtual bool GetThreadInfoByIndex(size_t index, ThreadInfo* info); + + // Implements LinuxDumper::IsPostMortem(). + // Always returns false to indicate this dumper performs a dump of + // a crashed process via ptrace. + virtual bool IsPostMortem() const; + + // Implements LinuxDumper::ThreadsSuspend(). + // Suspends all threads in the given process. Returns true on success. + virtual bool ThreadsSuspend(); + + // Implements LinuxDumper::ThreadsResume(). + // Resumes all threads in the given process. Returns true on success. + virtual bool ThreadsResume(); + + protected: + // Implements LinuxDumper::EnumerateThreads(). + // Enumerates all threads of the given process into |threads_|. + virtual bool EnumerateThreads(); + + private: + // Set to true if all threads of the crashed process are suspended. + bool threads_suspended_; +}; + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_HANDLER_LINUX_PTRACE_DUMPER_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc new file mode 100644 index 000000000..be533e157 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc @@ -0,0 +1,470 @@ +// Copyright (c) 2009, 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. + +// linux_ptrace_dumper_unittest.cc: +// Unit tests for google_breakpad::LinuxPtraceDumper. +// +// This file was renamed from linux_dumper_unittest.cc and modified due +// to LinuxDumper being splitted into two classes. + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <unistd.h> +#include <signal.h> +#include <stdint.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/prctl.h> +#include <sys/poll.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <string> + +#include "breakpad_googletest_includes.h" +#include "client/linux/minidump_writer/linux_ptrace_dumper.h" +#include "client/linux/minidump_writer/minidump_writer_unittest_utils.h" +#include "common/linux/eintr_wrapper.h" +#include "common/linux/file_id.h" +#include "common/linux/ignore_ret.h" +#include "common/linux/safe_readlink.h" +#include "common/memory.h" +#include "common/using_std_string.h" + +#ifndef PR_SET_PTRACER +#define PR_SET_PTRACER 0x59616d61 +#endif + +using namespace google_breakpad; + +namespace { + +typedef wasteful_vector<uint8_t> id_vector; +typedef testing::Test LinuxPtraceDumperTest; + +/* Fixture for running tests in a child process. */ +class LinuxPtraceDumperChildTest : public testing::Test { + protected: + virtual void SetUp() { + child_pid_ = fork(); +#ifndef __ANDROID__ + prctl(PR_SET_PTRACER, child_pid_); +#endif + } + + /* Gtest is calling TestBody from this class, which sets up a child + * process in which the RealTestBody virtual member is called. + * As such, TestBody is not supposed to be overridden in derived classes. + */ + virtual void TestBody() /* final */ { + if (child_pid_ == 0) { + // child process + RealTestBody(); + exit(HasFatalFailure() ? kFatalFailure : + (HasNonfatalFailure() ? kNonFatalFailure : 0)); + } + + ASSERT_TRUE(child_pid_ > 0); + int status; + waitpid(child_pid_, &status, 0); + if (WEXITSTATUS(status) == kFatalFailure) { + GTEST_FATAL_FAILURE_("Test failed in child process"); + } else if (WEXITSTATUS(status) == kNonFatalFailure) { + GTEST_NONFATAL_FAILURE_("Test failed in child process"); + } + } + + /* Gtest defines TestBody functions through its macros, but classes + * derived from this one need to define RealTestBody instead. + * This is achieved by defining a TestBody macro further below. + */ + virtual void RealTestBody() = 0; + + id_vector make_vector() { + return id_vector(&allocator, kDefaultBuildIdSize); + } + + private: + static const int kFatalFailure = 1; + static const int kNonFatalFailure = 2; + + pid_t child_pid_; + PageAllocator allocator; +}; + +} // namespace + +/* Replace TestBody declarations within TEST*() with RealTestBody + * declarations */ +#define TestBody RealTestBody + +TEST_F(LinuxPtraceDumperChildTest, Setup) { + LinuxPtraceDumper dumper(getppid()); +} + +TEST_F(LinuxPtraceDumperChildTest, FindMappings) { + LinuxPtraceDumper dumper(getppid()); + ASSERT_TRUE(dumper.Init()); + + ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(getpid))); + ASSERT_TRUE(dumper.FindMapping(reinterpret_cast<void*>(printf))); + ASSERT_FALSE(dumper.FindMapping(NULL)); +} + +TEST_F(LinuxPtraceDumperChildTest, ThreadList) { + LinuxPtraceDumper dumper(getppid()); + ASSERT_TRUE(dumper.Init()); + + ASSERT_GE(dumper.threads().size(), (size_t)1); + bool found = false; + for (size_t i = 0; i < dumper.threads().size(); ++i) { + if (dumper.threads()[i] == getppid()) { + ASSERT_FALSE(found); + found = true; + } + } + ASSERT_TRUE(found); +} + +// Helper stack class to close a file descriptor and unmap +// a mmap'ed mapping. +class StackHelper { + public: + StackHelper() + : fd_(-1), mapping_(NULL), size_(0) {} + ~StackHelper() { + if (size_) + munmap(mapping_, size_); + if (fd_ >= 0) + close(fd_); + } + void Init(int fd, char* mapping, size_t size) { + fd_ = fd; + mapping_ = mapping; + size_ = size; + } + + char* mapping() const { return mapping_; } + size_t size() const { return size_; } + + private: + int fd_; + char* mapping_; + size_t size_; +}; + +class LinuxPtraceDumperMappingsTest : public LinuxPtraceDumperChildTest { + protected: + virtual void SetUp(); + + string helper_path_; + size_t page_size_; + StackHelper helper_; +}; + +void LinuxPtraceDumperMappingsTest::SetUp() { + helper_path_ = GetHelperBinary(); + if (helper_path_.empty()) { + FAIL() << "Couldn't find helper binary"; + exit(1); + } + + // mmap two segments out of the helper binary, one + // enclosed in the other, but with different protections. + page_size_ = sysconf(_SC_PAGESIZE); + const size_t kMappingSize = 3 * page_size_; + int fd = open(helper_path_.c_str(), O_RDONLY); + ASSERT_NE(-1, fd) << "Failed to open file: " << helper_path_ + << ", Error: " << strerror(errno); + char* mapping = + reinterpret_cast<char*>(mmap(NULL, + kMappingSize, + PROT_READ, + MAP_SHARED, + fd, + 0)); + ASSERT_TRUE(mapping); + + // Ensure that things get cleaned up. + helper_.Init(fd, mapping, kMappingSize); + + // Carve a page out of the first mapping with different permissions. + char* inside_mapping = reinterpret_cast<char*>( + mmap(mapping + 2 * page_size_, + page_size_, + PROT_NONE, + MAP_SHARED | MAP_FIXED, + fd, + // Map a different offset just to + // better test real-world conditions. + page_size_)); + ASSERT_TRUE(inside_mapping); + + LinuxPtraceDumperChildTest::SetUp(); +} + +TEST_F(LinuxPtraceDumperMappingsTest, MergedMappings) { + // Now check that LinuxPtraceDumper interpreted the mappings properly. + LinuxPtraceDumper dumper(getppid()); + ASSERT_TRUE(dumper.Init()); + int mapping_count = 0; + for (unsigned i = 0; i < dumper.mappings().size(); ++i) { + const MappingInfo& mapping = *dumper.mappings()[i]; + if (strcmp(mapping.name, this->helper_path_.c_str()) == 0) { + // This mapping should encompass the entire original mapped + // range. + EXPECT_EQ(reinterpret_cast<uintptr_t>(this->helper_.mapping()), + mapping.start_addr); + EXPECT_EQ(this->helper_.size(), mapping.size); + EXPECT_EQ(0U, mapping.offset); + mapping_count++; + } + } + EXPECT_EQ(1, mapping_count); +} + +TEST_F(LinuxPtraceDumperChildTest, BuildProcPath) { + const pid_t pid = getppid(); + LinuxPtraceDumper dumper(pid); + + char maps_path[NAME_MAX] = ""; + char maps_path_expected[NAME_MAX]; + snprintf(maps_path_expected, sizeof(maps_path_expected), + "/proc/%d/maps", pid); + EXPECT_TRUE(dumper.BuildProcPath(maps_path, pid, "maps")); + EXPECT_STREQ(maps_path_expected, maps_path); + + EXPECT_FALSE(dumper.BuildProcPath(NULL, pid, "maps")); + EXPECT_FALSE(dumper.BuildProcPath(maps_path, 0, "maps")); + EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, "")); + EXPECT_FALSE(dumper.BuildProcPath(maps_path, pid, NULL)); + + char long_node[NAME_MAX]; + size_t long_node_len = NAME_MAX - strlen("/proc/123") - 1; + memset(long_node, 'a', long_node_len); + long_node[long_node_len] = '\0'; + EXPECT_FALSE(dumper.BuildProcPath(maps_path, 123, long_node)); +} + +#if !defined(__ARM_EABI__) && !defined(__mips__) +// Ensure that the linux-gate VDSO is included in the mapping list. +TEST_F(LinuxPtraceDumperChildTest, MappingsIncludeLinuxGate) { + LinuxPtraceDumper dumper(getppid()); + ASSERT_TRUE(dumper.Init()); + + void* linux_gate_loc = + reinterpret_cast<void *>(dumper.auxv()[AT_SYSINFO_EHDR]); + ASSERT_TRUE(linux_gate_loc); + bool found_linux_gate = false; + + const wasteful_vector<MappingInfo*> mappings = dumper.mappings(); + const MappingInfo* mapping; + for (unsigned i = 0; i < mappings.size(); ++i) { + mapping = mappings[i]; + if (!strcmp(mapping->name, kLinuxGateLibraryName)) { + found_linux_gate = true; + break; + } + } + EXPECT_TRUE(found_linux_gate); + EXPECT_EQ(linux_gate_loc, reinterpret_cast<void*>(mapping->start_addr)); + EXPECT_EQ(0, memcmp(linux_gate_loc, ELFMAG, SELFMAG)); +} + +// Ensure that the linux-gate VDSO can generate a non-zeroed File ID. +TEST_F(LinuxPtraceDumperChildTest, LinuxGateMappingID) { + LinuxPtraceDumper dumper(getppid()); + ASSERT_TRUE(dumper.Init()); + + bool found_linux_gate = false; + const wasteful_vector<MappingInfo*> mappings = dumper.mappings(); + unsigned index = 0; + for (unsigned i = 0; i < mappings.size(); ++i) { + if (!strcmp(mappings[i]->name, kLinuxGateLibraryName)) { + found_linux_gate = true; + index = i; + break; + } + } + ASSERT_TRUE(found_linux_gate); + + // Need to suspend the child so ptrace actually works. + ASSERT_TRUE(dumper.ThreadsSuspend()); + id_vector identifier(make_vector()); + ASSERT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[index], + true, + index, + identifier)); + + id_vector empty_identifier(make_vector()); + empty_identifier.resize(kDefaultBuildIdSize, 0); + EXPECT_NE(empty_identifier, identifier); + EXPECT_TRUE(dumper.ThreadsResume()); +} +#endif + +TEST_F(LinuxPtraceDumperChildTest, FileIDsMatch) { + // Calculate the File ID of our binary using both + // FileID::ElfFileIdentifier and LinuxDumper::ElfFileIdentifierForMapping + // and ensure that we get the same result from both. + char exe_name[PATH_MAX]; + ASSERT_TRUE(SafeReadLink("/proc/self/exe", exe_name)); + + LinuxPtraceDumper dumper(getppid()); + ASSERT_TRUE(dumper.Init()); + const wasteful_vector<MappingInfo*> mappings = dumper.mappings(); + bool found_exe = false; + unsigned i; + for (i = 0; i < mappings.size(); ++i) { + const MappingInfo* mapping = mappings[i]; + if (!strcmp(mapping->name, exe_name)) { + found_exe = true; + break; + } + } + ASSERT_TRUE(found_exe); + + id_vector identifier1(make_vector()); + id_vector identifier2(make_vector()); + EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[i], true, i, + identifier1)); + FileID fileid(exe_name); + EXPECT_TRUE(fileid.ElfFileIdentifier(identifier2)); + + string identifier_string1 = + FileID::ConvertIdentifierToUUIDString(identifier1); + string identifier_string2 = + FileID::ConvertIdentifierToUUIDString(identifier2); + EXPECT_EQ(identifier_string1, identifier_string2); +} + +/* Get back to normal behavior of TEST*() macros wrt TestBody. */ +#undef TestBody + +TEST(LinuxPtraceDumperTest, VerifyStackReadWithMultipleThreads) { + static const int kNumberOfThreadsInHelperProgram = 5; + char kNumberOfThreadsArgument[2]; + sprintf(kNumberOfThreadsArgument, "%d", kNumberOfThreadsInHelperProgram); + + int fds[2]; + ASSERT_NE(-1, pipe(fds)); + + pid_t child_pid = fork(); + if (child_pid == 0) { + // In child process. + close(fds[0]); + + string helper_path(GetHelperBinary()); + if (helper_path.empty()) { + FAIL() << "Couldn't find helper binary"; + exit(1); + } + + // Pass the pipe fd and the number of threads as arguments. + char pipe_fd_string[8]; + sprintf(pipe_fd_string, "%d", fds[1]); + execl(helper_path.c_str(), + "linux_dumper_unittest_helper", + pipe_fd_string, + kNumberOfThreadsArgument, + NULL); + // Kill if we get here. + printf("Errno from exec: %d", errno); + FAIL() << "Exec of " << helper_path << " failed: " << strerror(errno); + exit(0); + } + close(fds[1]); + + // Wait for all child threads to indicate that they have started + for (int threads = 0; threads < kNumberOfThreadsInHelperProgram; threads++) { + struct pollfd pfd; + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = fds[0]; + pfd.events = POLLIN | POLLERR; + + const int r = HANDLE_EINTR(poll(&pfd, 1, 1000)); + ASSERT_EQ(1, r); + ASSERT_TRUE(pfd.revents & POLLIN); + uint8_t junk; + ASSERT_EQ(read(fds[0], &junk, sizeof(junk)), + static_cast<ssize_t>(sizeof(junk))); + } + close(fds[0]); + + // There is a race here because we may stop a child thread before + // it is actually running the busy loop. Empirically this sleep + // is sufficient to avoid the race. + usleep(100000); + + // Children are ready now. + LinuxPtraceDumper dumper(child_pid); + ASSERT_TRUE(dumper.Init()); + EXPECT_EQ((size_t)kNumberOfThreadsInHelperProgram, dumper.threads().size()); + EXPECT_TRUE(dumper.ThreadsSuspend()); + + ThreadInfo one_thread; + for (size_t i = 0; i < dumper.threads().size(); ++i) { + EXPECT_TRUE(dumper.GetThreadInfoByIndex(i, &one_thread)); + const void* stack; + size_t stack_len; + EXPECT_TRUE(dumper.GetStackInfo(&stack, &stack_len, + one_thread.stack_pointer)); + // In the helper program, we stored a pointer to the thread id in a + // specific register. Check that we can recover its value. +#if defined(__ARM_EABI__) + pid_t* process_tid_location = (pid_t*)(one_thread.regs.uregs[3]); +#elif defined(__aarch64__) + pid_t* process_tid_location = (pid_t*)(one_thread.regs.regs[3]); +#elif defined(__i386) + pid_t* process_tid_location = (pid_t*)(one_thread.regs.ecx); +#elif defined(__x86_64) + pid_t* process_tid_location = (pid_t*)(one_thread.regs.rcx); +#elif defined(__mips__) + pid_t* process_tid_location = + reinterpret_cast<pid_t*>(one_thread.mcontext.gregs[1]); +#else +#error This test has not been ported to this platform. +#endif + pid_t one_thread_id; + dumper.CopyFromProcess(&one_thread_id, + dumper.threads()[i], + process_tid_location, + 4); + EXPECT_EQ(dumper.threads()[i], one_thread_id); + } + EXPECT_TRUE(dumper.ThreadsResume()); + kill(child_pid, SIGKILL); + + // Reap child + int status; + ASSERT_NE(-1, HANDLE_EINTR(waitpid(child_pid, &status, 0))); + ASSERT_TRUE(WIFSIGNALED(status)); + ASSERT_EQ(SIGKILL, WTERMSIG(status)); +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer.cc new file mode 100644 index 000000000..4b1ae5ad3 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer.cc @@ -0,0 +1,1376 @@ +// Copyright (c) 2010, 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. + +// This code writes out minidump files: +// http://msdn.microsoft.com/en-us/library/ms680378(VS.85,loband).aspx +// +// Minidumps are a Microsoft format which Breakpad uses for recording crash +// dumps. This code has to run in a compromised environment (the address space +// may have received SIGSEGV), thus the following rules apply: +// * You may not enter the dynamic linker. This means that we cannot call +// any symbols in a shared library (inc libc). Because of this we replace +// libc functions in linux_libc_support.h. +// * You may not call syscalls via the libc wrappers. This rule is a subset +// of the first rule but it bears repeating. We have direct wrappers +// around the system calls in linux_syscall_support.h. +// * You may not malloc. There's an alternative allocator in memory.h and +// a canonical instance in the LinuxDumper object. We use the placement +// new form to allocate objects and we don't delete them. + +#include "client/linux/handler/minidump_descriptor.h" +#include "client/linux/minidump_writer/minidump_writer.h" +#include "client/minidump_file_writer-inl.h" + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <link.h> +#include <stdio.h> +#if defined(__ANDROID__) +#include <sys/system_properties.h> +#endif +#include <sys/types.h> +#include <sys/ucontext.h> +#include <sys/user.h> +#include <sys/utsname.h> +#include <time.h> +#include <unistd.h> + +#include <algorithm> + +#include "client/linux/dump_writer_common/thread_info.h" +#include "client/linux/dump_writer_common/ucontext_reader.h" +#include "client/linux/handler/exception_handler.h" +#include "client/linux/minidump_writer/cpu_set.h" +#include "client/linux/minidump_writer/line_reader.h" +#include "client/linux/minidump_writer/linux_dumper.h" +#include "client/linux/minidump_writer/linux_ptrace_dumper.h" +#include "client/linux/minidump_writer/proc_cpuinfo_reader.h" +#include "client/minidump_file_writer.h" +#include "common/linux/file_id.h" +#include "common/linux/linux_libc_support.h" +#include "common/minidump_type_helper.h" +#include "google_breakpad/common/minidump_format.h" +#include "third_party/lss/linux_syscall_support.h" + +namespace { + +using google_breakpad::AppMemoryList; +using google_breakpad::auto_wasteful_vector; +using google_breakpad::ExceptionHandler; +using google_breakpad::CpuSet; +using google_breakpad::kDefaultBuildIdSize; +using google_breakpad::LineReader; +using google_breakpad::LinuxDumper; +using google_breakpad::LinuxPtraceDumper; +using google_breakpad::MDTypeHelper; +using google_breakpad::MappingEntry; +using google_breakpad::MappingInfo; +using google_breakpad::MappingList; +using google_breakpad::MinidumpFileWriter; +using google_breakpad::PageAllocator; +using google_breakpad::ProcCpuInfoReader; +using google_breakpad::RawContextCPU; +using google_breakpad::ThreadInfo; +using google_breakpad::TypedMDRVA; +using google_breakpad::UContextReader; +using google_breakpad::UntypedMDRVA; +using google_breakpad::wasteful_vector; + +typedef MDTypeHelper<sizeof(void*)>::MDRawDebug MDRawDebug; +typedef MDTypeHelper<sizeof(void*)>::MDRawLinkMap MDRawLinkMap; + +class MinidumpWriter { + public: + // The following kLimit* constants are for when minidump_size_limit_ is set + // and the minidump size might exceed it. + // + // Estimate for how big each thread's stack will be (in bytes). + static const unsigned kLimitAverageThreadStackLength = 8 * 1024; + // Number of threads whose stack size we don't want to limit. These base + // threads will simply be the first N threads returned by the dumper (although + // the crashing thread will never be limited). Threads beyond this count are + // the extra threads. + static const unsigned kLimitBaseThreadCount = 20; + // Maximum stack size to dump for any extra thread (in bytes). + static const unsigned kLimitMaxExtraThreadStackLen = 2 * 1024; + // Make sure this number of additional bytes can fit in the minidump + // (exclude the stack data). + static const unsigned kLimitMinidumpFudgeFactor = 64 * 1024; + + MinidumpWriter(const char* minidump_path, + int minidump_fd, + const ExceptionHandler::CrashContext* context, + const MappingList& mappings, + const AppMemoryList& appmem, + LinuxDumper* dumper) + : fd_(minidump_fd), + path_(minidump_path), + ucontext_(context ? &context->context : NULL), +#if !defined(__ARM_EABI__) && !defined(__mips__) + float_state_(context ? &context->float_state : NULL), +#endif + dumper_(dumper), + minidump_size_limit_(-1), + memory_blocks_(dumper_->allocator()), + mapping_list_(mappings), + app_memory_list_(appmem) { + // Assert there should be either a valid fd or a valid path, not both. + assert(fd_ != -1 || minidump_path); + assert(fd_ == -1 || !minidump_path); + } + + bool Init() { + if (!dumper_->Init()) + return false; + + if (fd_ != -1) + minidump_writer_.SetFile(fd_); + else if (!minidump_writer_.Open(path_)) + return false; + + return dumper_->ThreadsSuspend() && dumper_->LateInit(); + } + + ~MinidumpWriter() { + // Don't close the file descriptor when it's been provided explicitly. + // Callers might still need to use it. + if (fd_ == -1) + minidump_writer_.Close(); + dumper_->ThreadsResume(); + } + + bool Dump() { + // A minidump file contains a number of tagged streams. This is the number + // of stream which we write. + unsigned kNumWriters = 13; + + TypedMDRVA<MDRawDirectory> dir(&minidump_writer_); + { + // Ensure the header gets flushed, as that happens in the destructor. + // If we crash somewhere below, we should have a mostly-intact dump + TypedMDRVA<MDRawHeader> header(&minidump_writer_); + if (!header.Allocate()) + return false; + + if (!dir.AllocateArray(kNumWriters)) + return false; + + my_memset(header.get(), 0, sizeof(MDRawHeader)); + + header.get()->signature = MD_HEADER_SIGNATURE; + header.get()->version = MD_HEADER_VERSION; + header.get()->time_date_stamp = time(NULL); + header.get()->stream_count = kNumWriters; + header.get()->stream_directory_rva = dir.position(); + } + + unsigned dir_index = 0; + MDRawDirectory dirent; + + if (!WriteThreadListStream(&dirent)) + return false; + dir.CopyIndex(dir_index++, &dirent); + + if (!WriteMappings(&dirent)) + return false; + dir.CopyIndex(dir_index++, &dirent); + + if (!WriteAppMemory()) + return false; + + if (!WriteMemoryListStream(&dirent)) + return false; + dir.CopyIndex(dir_index++, &dirent); + + if (!WriteExceptionStream(&dirent)) + return false; + dir.CopyIndex(dir_index++, &dirent); + + if (!WriteSystemInfoStream(&dirent)) + return false; + dir.CopyIndex(dir_index++, &dirent); + + dirent.stream_type = MD_LINUX_CPU_INFO; + if (!WriteFile(&dirent.location, "/proc/cpuinfo")) + NullifyDirectoryEntry(&dirent); + dir.CopyIndex(dir_index++, &dirent); + + dirent.stream_type = MD_LINUX_PROC_STATUS; + if (!WriteProcFile(&dirent.location, GetCrashThread(), "status")) + NullifyDirectoryEntry(&dirent); + dir.CopyIndex(dir_index++, &dirent); + + dirent.stream_type = MD_LINUX_LSB_RELEASE; + if (!WriteFile(&dirent.location, "/etc/lsb-release")) + NullifyDirectoryEntry(&dirent); + dir.CopyIndex(dir_index++, &dirent); + + dirent.stream_type = MD_LINUX_CMD_LINE; + if (!WriteProcFile(&dirent.location, GetCrashThread(), "cmdline")) + NullifyDirectoryEntry(&dirent); + dir.CopyIndex(dir_index++, &dirent); + + dirent.stream_type = MD_LINUX_ENVIRON; + if (!WriteProcFile(&dirent.location, GetCrashThread(), "environ")) + NullifyDirectoryEntry(&dirent); + dir.CopyIndex(dir_index++, &dirent); + + dirent.stream_type = MD_LINUX_AUXV; + if (!WriteProcFile(&dirent.location, GetCrashThread(), "auxv")) + NullifyDirectoryEntry(&dirent); + dir.CopyIndex(dir_index++, &dirent); + + dirent.stream_type = MD_LINUX_MAPS; + if (!WriteProcFile(&dirent.location, GetCrashThread(), "maps")) + NullifyDirectoryEntry(&dirent); + dir.CopyIndex(dir_index++, &dirent); + + dirent.stream_type = MD_LINUX_DSO_DEBUG; + if (!WriteDSODebugStream(&dirent)) + NullifyDirectoryEntry(&dirent); + dir.CopyIndex(dir_index++, &dirent); + + // If you add more directory entries, don't forget to update kNumWriters, + // above. + + dumper_->ThreadsResume(); + return true; + } + + bool FillThreadStack(MDRawThread* thread, uintptr_t stack_pointer, + int max_stack_len, uint8_t** stack_copy) { + *stack_copy = NULL; + const void* stack; + size_t stack_len; + if (dumper_->GetStackInfo(&stack, &stack_len, stack_pointer)) { + UntypedMDRVA memory(&minidump_writer_); + if (max_stack_len >= 0 && + stack_len > static_cast<unsigned int>(max_stack_len)) { + stack_len = max_stack_len; + // Skip empty chunks of length max_stack_len. + uintptr_t int_stack = reinterpret_cast<uintptr_t>(stack); + if (max_stack_len > 0) { + while (int_stack + max_stack_len < stack_pointer) { + int_stack += max_stack_len; + } + } + stack = reinterpret_cast<const void*>(int_stack); + } + if (!memory.Allocate(stack_len)) + return false; + *stack_copy = reinterpret_cast<uint8_t*>(Alloc(stack_len)); + dumper_->CopyFromProcess(*stack_copy, thread->thread_id, stack, + stack_len); + memory.Copy(*stack_copy, stack_len); + thread->stack.start_of_memory_range = + reinterpret_cast<uintptr_t>(stack); + thread->stack.memory = memory.location(); + memory_blocks_.push_back(thread->stack); + } else { + thread->stack.start_of_memory_range = stack_pointer; + thread->stack.memory.data_size = 0; + thread->stack.memory.rva = minidump_writer_.position(); + } + return true; + } + + // Write information about the threads. + bool WriteThreadListStream(MDRawDirectory* dirent) { + const unsigned num_threads = dumper_->threads().size(); + + TypedMDRVA<uint32_t> list(&minidump_writer_); + if (!list.AllocateObjectAndArray(num_threads, sizeof(MDRawThread))) + return false; + + dirent->stream_type = MD_THREAD_LIST_STREAM; + dirent->location = list.location(); + + *list.get() = num_threads; + + // If there's a minidump size limit, check if it might be exceeded. Since + // most of the space is filled with stack data, just check against that. + // If this expects to exceed the limit, set extra_thread_stack_len such + // that any thread beyond the first kLimitBaseThreadCount threads will + // have only kLimitMaxExtraThreadStackLen bytes dumped. + int extra_thread_stack_len = -1; // default to no maximum + if (minidump_size_limit_ >= 0) { + const unsigned estimated_total_stack_size = num_threads * + kLimitAverageThreadStackLength; + const off_t estimated_minidump_size = minidump_writer_.position() + + estimated_total_stack_size + kLimitMinidumpFudgeFactor; + if (estimated_minidump_size > minidump_size_limit_) + extra_thread_stack_len = kLimitMaxExtraThreadStackLen; + } + + for (unsigned i = 0; i < num_threads; ++i) { + MDRawThread thread; + my_memset(&thread, 0, sizeof(thread)); + thread.thread_id = dumper_->threads()[i]; + + // We have a different source of information for the crashing thread. If + // we used the actual state of the thread we would find it running in the + // signal handler with the alternative stack, which would be deeply + // unhelpful. + if (static_cast<pid_t>(thread.thread_id) == GetCrashThread() && + ucontext_ && + !dumper_->IsPostMortem()) { + uint8_t* stack_copy; + const uintptr_t stack_ptr = UContextReader::GetStackPointer(ucontext_); + if (!FillThreadStack(&thread, stack_ptr, -1, &stack_copy)) + return false; + + // Copy 256 bytes around crashing instruction pointer to minidump. + const size_t kIPMemorySize = 256; + uint64_t ip = UContextReader::GetInstructionPointer(ucontext_); + // Bound it to the upper and lower bounds of the memory map + // it's contained within. If it's not in mapped memory, + // don't bother trying to write it. + bool ip_is_mapped = false; + MDMemoryDescriptor ip_memory_d; + for (unsigned j = 0; j < dumper_->mappings().size(); ++j) { + const MappingInfo& mapping = *dumper_->mappings()[j]; + if (ip >= mapping.start_addr && + ip < mapping.start_addr + mapping.size) { + ip_is_mapped = true; + // Try to get 128 bytes before and after the IP, but + // settle for whatever's available. + ip_memory_d.start_of_memory_range = + std::max(mapping.start_addr, + uintptr_t(ip - (kIPMemorySize / 2))); + uintptr_t end_of_range = + std::min(uintptr_t(ip + (kIPMemorySize / 2)), + uintptr_t(mapping.start_addr + mapping.size)); + ip_memory_d.memory.data_size = + end_of_range - ip_memory_d.start_of_memory_range; + break; + } + } + + if (ip_is_mapped) { + UntypedMDRVA ip_memory(&minidump_writer_); + if (!ip_memory.Allocate(ip_memory_d.memory.data_size)) + return false; + uint8_t* memory_copy = + reinterpret_cast<uint8_t*>(Alloc(ip_memory_d.memory.data_size)); + dumper_->CopyFromProcess( + memory_copy, + thread.thread_id, + reinterpret_cast<void*>(ip_memory_d.start_of_memory_range), + ip_memory_d.memory.data_size); + ip_memory.Copy(memory_copy, ip_memory_d.memory.data_size); + ip_memory_d.memory = ip_memory.location(); + memory_blocks_.push_back(ip_memory_d); + } + + TypedMDRVA<RawContextCPU> cpu(&minidump_writer_); + if (!cpu.Allocate()) + return false; + my_memset(cpu.get(), 0, sizeof(RawContextCPU)); +#if !defined(__ARM_EABI__) && !defined(__mips__) + UContextReader::FillCPUContext(cpu.get(), ucontext_, float_state_); +#else + UContextReader::FillCPUContext(cpu.get(), ucontext_); +#endif + thread.thread_context = cpu.location(); + crashing_thread_context_ = cpu.location(); + } else { + ThreadInfo info; + if (!dumper_->GetThreadInfoByIndex(i, &info)) + return false; + + uint8_t* stack_copy; + int max_stack_len = -1; // default to no maximum for this thread + if (minidump_size_limit_ >= 0 && i >= kLimitBaseThreadCount) + max_stack_len = extra_thread_stack_len; + if (!FillThreadStack(&thread, info.stack_pointer, max_stack_len, + &stack_copy)) + return false; + + TypedMDRVA<RawContextCPU> cpu(&minidump_writer_); + if (!cpu.Allocate()) + return false; + my_memset(cpu.get(), 0, sizeof(RawContextCPU)); + info.FillCPUContext(cpu.get()); + thread.thread_context = cpu.location(); + if (dumper_->threads()[i] == GetCrashThread()) { + crashing_thread_context_ = cpu.location(); + if (!dumper_->IsPostMortem()) { + // This is the crashing thread of a live process, but + // no context was provided, so set the crash address + // while the instruction pointer is already here. + dumper_->set_crash_address(info.GetInstructionPointer()); + } + } + } + + list.CopyIndexAfterObject(i, &thread, sizeof(thread)); + } + + return true; + } + + // Write application-provided memory regions. + bool WriteAppMemory() { + for (AppMemoryList::const_iterator iter = app_memory_list_.begin(); + iter != app_memory_list_.end(); + ++iter) { + uint8_t* data_copy = + reinterpret_cast<uint8_t*>(dumper_->allocator()->Alloc(iter->length)); + dumper_->CopyFromProcess(data_copy, GetCrashThread(), iter->ptr, + iter->length); + + UntypedMDRVA memory(&minidump_writer_); + if (!memory.Allocate(iter->length)) { + return false; + } + memory.Copy(data_copy, iter->length); + MDMemoryDescriptor desc; + desc.start_of_memory_range = reinterpret_cast<uintptr_t>(iter->ptr); + desc.memory = memory.location(); + memory_blocks_.push_back(desc); + } + + return true; + } + + static bool ShouldIncludeMapping(const MappingInfo& mapping) { + if (mapping.name[0] == 0 || // only want modules with filenames. + // Only want to include one mapping per shared lib. + // Avoid filtering executable mappings. + (mapping.offset != 0 && !mapping.exec) || + mapping.size < 4096) { // too small to get a signature for. + return false; + } + + return true; + } + + // If there is caller-provided information about this mapping + // in the mapping_list_ list, return true. Otherwise, return false. + bool HaveMappingInfo(const MappingInfo& mapping) { + for (MappingList::const_iterator iter = mapping_list_.begin(); + iter != mapping_list_.end(); + ++iter) { + // Ignore any mappings that are wholly contained within + // mappings in the mapping_info_ list. + if (mapping.start_addr >= iter->first.start_addr && + (mapping.start_addr + mapping.size) <= + (iter->first.start_addr + iter->first.size)) { + return true; + } + } + return false; + } + + // Write information about the mappings in effect. Because we are using the + // minidump format, the information about the mappings is pretty limited. + // Because of this, we also include the full, unparsed, /proc/$x/maps file in + // another stream in the file. + bool WriteMappings(MDRawDirectory* dirent) { + const unsigned num_mappings = dumper_->mappings().size(); + unsigned num_output_mappings = mapping_list_.size(); + + for (unsigned i = 0; i < dumper_->mappings().size(); ++i) { + const MappingInfo& mapping = *dumper_->mappings()[i]; + if (ShouldIncludeMapping(mapping) && !HaveMappingInfo(mapping)) + num_output_mappings++; + } + + TypedMDRVA<uint32_t> list(&minidump_writer_); + if (num_output_mappings) { + if (!list.AllocateObjectAndArray(num_output_mappings, MD_MODULE_SIZE)) + return false; + } else { + // Still create the module list stream, although it will have zero + // modules. + if (!list.Allocate()) + return false; + } + + dirent->stream_type = MD_MODULE_LIST_STREAM; + dirent->location = list.location(); + *list.get() = num_output_mappings; + + // First write all the mappings from the dumper + unsigned int j = 0; + for (unsigned i = 0; i < num_mappings; ++i) { + const MappingInfo& mapping = *dumper_->mappings()[i]; + if (!ShouldIncludeMapping(mapping) || HaveMappingInfo(mapping)) + continue; + + MDRawModule mod; + if (!FillRawModule(mapping, true, i, &mod, NULL)) + return false; + list.CopyIndexAfterObject(j++, &mod, MD_MODULE_SIZE); + } + // Next write all the mappings provided by the caller + for (MappingList::const_iterator iter = mapping_list_.begin(); + iter != mapping_list_.end(); + ++iter) { + MDRawModule mod; + if (!FillRawModule(iter->first, false, 0, &mod, iter->second)) + return false; + list.CopyIndexAfterObject(j++, &mod, MD_MODULE_SIZE); + } + + return true; + } + + // Fill the MDRawModule |mod| with information about the provided + // |mapping|. If |identifier| is non-NULL, use it instead of calculating + // a file ID from the mapping. + bool FillRawModule(const MappingInfo& mapping, + bool member, + unsigned int mapping_id, + MDRawModule* mod, + const uint8_t* identifier) { + my_memset(mod, 0, MD_MODULE_SIZE); + + mod->base_of_image = mapping.start_addr; + mod->size_of_image = mapping.size; + + auto_wasteful_vector<uint8_t, kDefaultBuildIdSize> identifier_bytes( + dumper_->allocator()); + + if (identifier) { + // GUID was provided by caller. + identifier_bytes.insert(identifier_bytes.end(), + identifier, + identifier + sizeof(MDGUID)); + } else { + // Note: ElfFileIdentifierForMapping() can manipulate the |mapping.name|. + dumper_->ElfFileIdentifierForMapping(mapping, + member, + mapping_id, + identifier_bytes); + } + + if (!identifier_bytes.empty()) { + UntypedMDRVA cv(&minidump_writer_); + if (!cv.Allocate(MDCVInfoELF_minsize + identifier_bytes.size())) + return false; + + const uint32_t cv_signature = MD_CVINFOELF_SIGNATURE; + cv.Copy(&cv_signature, sizeof(cv_signature)); + cv.Copy(cv.position() + sizeof(cv_signature), &identifier_bytes[0], + identifier_bytes.size()); + + mod->cv_record = cv.location(); + } + + char file_name[NAME_MAX]; + char file_path[NAME_MAX]; + dumper_->GetMappingEffectiveNameAndPath( + mapping, file_path, sizeof(file_path), file_name, sizeof(file_name)); + + MDLocationDescriptor ld; + if (!minidump_writer_.WriteString(file_path, my_strlen(file_path), &ld)) + return false; + mod->module_name_rva = ld.rva; + return true; + } + + bool WriteMemoryListStream(MDRawDirectory* dirent) { + TypedMDRVA<uint32_t> list(&minidump_writer_); + if (memory_blocks_.size()) { + if (!list.AllocateObjectAndArray(memory_blocks_.size(), + sizeof(MDMemoryDescriptor))) + return false; + } else { + // Still create the memory list stream, although it will have zero + // memory blocks. + if (!list.Allocate()) + return false; + } + + dirent->stream_type = MD_MEMORY_LIST_STREAM; + dirent->location = list.location(); + + *list.get() = memory_blocks_.size(); + + for (size_t i = 0; i < memory_blocks_.size(); ++i) { + list.CopyIndexAfterObject(i, &memory_blocks_[i], + sizeof(MDMemoryDescriptor)); + } + return true; + } + + bool WriteExceptionStream(MDRawDirectory* dirent) { + TypedMDRVA<MDRawExceptionStream> exc(&minidump_writer_); + if (!exc.Allocate()) + return false; + my_memset(exc.get(), 0, sizeof(MDRawExceptionStream)); + + dirent->stream_type = MD_EXCEPTION_STREAM; + dirent->location = exc.location(); + + exc.get()->thread_id = GetCrashThread(); + exc.get()->exception_record.exception_code = dumper_->crash_signal(); + exc.get()->exception_record.exception_address = dumper_->crash_address(); + exc.get()->thread_context = crashing_thread_context_; + + return true; + } + + bool WriteSystemInfoStream(MDRawDirectory* dirent) { + TypedMDRVA<MDRawSystemInfo> si(&minidump_writer_); + if (!si.Allocate()) + return false; + my_memset(si.get(), 0, sizeof(MDRawSystemInfo)); + + dirent->stream_type = MD_SYSTEM_INFO_STREAM; + dirent->location = si.location(); + + WriteCPUInformation(si.get()); + WriteOSInformation(si.get()); + + return true; + } + + bool WriteDSODebugStream(MDRawDirectory* dirent) { + ElfW(Phdr)* phdr = reinterpret_cast<ElfW(Phdr) *>(dumper_->auxv()[AT_PHDR]); + char* base; + int phnum = dumper_->auxv()[AT_PHNUM]; + if (!phnum || !phdr) + return false; + + // Assume the program base is at the beginning of the same page as the PHDR + base = reinterpret_cast<char *>(reinterpret_cast<uintptr_t>(phdr) & ~0xfff); + + // Search for the program PT_DYNAMIC segment + ElfW(Addr) dyn_addr = 0; + for (; phnum >= 0; phnum--, phdr++) { + ElfW(Phdr) ph; + if (!dumper_->CopyFromProcess(&ph, GetCrashThread(), phdr, sizeof(ph))) + return false; + + // Adjust base address with the virtual address of the PT_LOAD segment + // corresponding to offset 0 + if (ph.p_type == PT_LOAD && ph.p_offset == 0) { + base -= ph.p_vaddr; + } + if (ph.p_type == PT_DYNAMIC) { + dyn_addr = ph.p_vaddr; + } + } + if (!dyn_addr) + return false; + + ElfW(Dyn) *dynamic = reinterpret_cast<ElfW(Dyn) *>(dyn_addr + base); + + // The dynamic linker makes information available that helps gdb find all + // DSOs loaded into the program. If this information is indeed available, + // dump it to a MD_LINUX_DSO_DEBUG stream. + struct r_debug* r_debug = NULL; + uint32_t dynamic_length = 0; + + for (int i = 0; ; ++i) { + ElfW(Dyn) dyn; + dynamic_length += sizeof(dyn); + if (!dumper_->CopyFromProcess(&dyn, GetCrashThread(), dynamic + i, + sizeof(dyn))) { + return false; + } + +#ifdef __mips__ + const int32_t debug_tag = DT_MIPS_RLD_MAP; +#else + const int32_t debug_tag = DT_DEBUG; +#endif + if (dyn.d_tag == debug_tag) { + r_debug = reinterpret_cast<struct r_debug*>(dyn.d_un.d_ptr); + continue; + } else if (dyn.d_tag == DT_NULL) { + break; + } + } + + // The "r_map" field of that r_debug struct contains a linked list of all + // loaded DSOs. + // Our list of DSOs potentially is different from the ones in the crashing + // process. So, we have to be careful to never dereference pointers + // directly. Instead, we use CopyFromProcess() everywhere. + // See <link.h> for a more detailed discussion of the how the dynamic + // loader communicates with debuggers. + + // Count the number of loaded DSOs + int dso_count = 0; + struct r_debug debug_entry; + if (!dumper_->CopyFromProcess(&debug_entry, GetCrashThread(), r_debug, + sizeof(debug_entry))) { + return false; + } + for (struct link_map* ptr = debug_entry.r_map; ptr; ) { + struct link_map map; + if (!dumper_->CopyFromProcess(&map, GetCrashThread(), ptr, sizeof(map))) + return false; + + ptr = map.l_next; + dso_count++; + } + + MDRVA linkmap_rva = minidump_writer_.kInvalidMDRVA; + if (dso_count > 0) { + // If we have at least one DSO, create an array of MDRawLinkMap + // entries in the minidump file. + TypedMDRVA<MDRawLinkMap> linkmap(&minidump_writer_); + if (!linkmap.AllocateArray(dso_count)) + return false; + linkmap_rva = linkmap.location().rva; + int idx = 0; + + // Iterate over DSOs and write their information to mini dump + for (struct link_map* ptr = debug_entry.r_map; ptr; ) { + struct link_map map; + if (!dumper_->CopyFromProcess(&map, GetCrashThread(), ptr, sizeof(map))) + return false; + + ptr = map.l_next; + char filename[257] = { 0 }; + if (map.l_name) { + dumper_->CopyFromProcess(filename, GetCrashThread(), map.l_name, + sizeof(filename) - 1); + } + MDLocationDescriptor location; + if (!minidump_writer_.WriteString(filename, 0, &location)) + return false; + MDRawLinkMap entry; + entry.name = location.rva; + entry.addr = map.l_addr; + entry.ld = reinterpret_cast<uintptr_t>(map.l_ld); + linkmap.CopyIndex(idx++, &entry); + } + } + + // Write MD_LINUX_DSO_DEBUG record + TypedMDRVA<MDRawDebug> debug(&minidump_writer_); + if (!debug.AllocateObjectAndArray(1, dynamic_length)) + return false; + my_memset(debug.get(), 0, sizeof(MDRawDebug)); + dirent->stream_type = MD_LINUX_DSO_DEBUG; + dirent->location = debug.location(); + + debug.get()->version = debug_entry.r_version; + debug.get()->map = linkmap_rva; + debug.get()->dso_count = dso_count; + debug.get()->brk = debug_entry.r_brk; + debug.get()->ldbase = debug_entry.r_ldbase; + debug.get()->dynamic = reinterpret_cast<uintptr_t>(dynamic); + + wasteful_vector<char> dso_debug_data(dumper_->allocator(), dynamic_length); + // The passed-in size to the constructor (above) is only a hint. + // Must call .resize() to do actual initialization of the elements. + dso_debug_data.resize(dynamic_length); + dumper_->CopyFromProcess(&dso_debug_data[0], GetCrashThread(), dynamic, + dynamic_length); + debug.CopyIndexAfterObject(0, &dso_debug_data[0], dynamic_length); + + return true; + } + + void set_minidump_size_limit(off_t limit) { minidump_size_limit_ = limit; } + + private: + void* Alloc(unsigned bytes) { + return dumper_->allocator()->Alloc(bytes); + } + + pid_t GetCrashThread() const { + return dumper_->crash_thread(); + } + + void NullifyDirectoryEntry(MDRawDirectory* dirent) { + dirent->stream_type = 0; + dirent->location.data_size = 0; + dirent->location.rva = 0; + } + +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) + bool WriteCPUInformation(MDRawSystemInfo* sys_info) { + char vendor_id[sizeof(sys_info->cpu.x86_cpu_info.vendor_id) + 1] = {0}; + static const char vendor_id_name[] = "vendor_id"; + + struct CpuInfoEntry { + const char* info_name; + int value; + bool found; + } cpu_info_table[] = { + { "processor", -1, false }, +#if defined(__i386__) || defined(__x86_64__) + { "model", 0, false }, + { "stepping", 0, false }, + { "cpu family", 0, false }, +#endif + }; + + // processor_architecture should always be set, do this first + sys_info->processor_architecture = +#if defined(__mips__) +# if _MIPS_SIM == _ABIO32 + MD_CPU_ARCHITECTURE_MIPS; +# elif _MIPS_SIM == _ABI64 + MD_CPU_ARCHITECTURE_MIPS64; +# else +# error "This mips ABI is currently not supported (n32)" +#endif +#elif defined(__i386__) + MD_CPU_ARCHITECTURE_X86; +#else + MD_CPU_ARCHITECTURE_AMD64; +#endif + + const int fd = sys_open("/proc/cpuinfo", O_RDONLY, 0); + if (fd < 0) + return false; + + { + PageAllocator allocator; + ProcCpuInfoReader* const reader = new(allocator) ProcCpuInfoReader(fd); + const char* field; + while (reader->GetNextField(&field)) { + bool is_first_entry = true; + for (CpuInfoEntry& entry : cpu_info_table) { + if (!is_first_entry && entry.found) { + // except for the 'processor' field, ignore repeated values. + continue; + } + is_first_entry = false; + if (!my_strcmp(field, entry.info_name)) { + size_t value_len; + const char* value = reader->GetValueAndLen(&value_len); + if (value_len == 0) + continue; + + uintptr_t val; + if (my_read_decimal_ptr(&val, value) == value) + continue; + + entry.value = static_cast<int>(val); + entry.found = true; + } + } + + // special case for vendor_id + if (!my_strcmp(field, vendor_id_name)) { + size_t value_len; + const char* value = reader->GetValueAndLen(&value_len); + if (value_len > 0) + my_strlcpy(vendor_id, value, sizeof(vendor_id)); + } + } + sys_close(fd); + } + + // make sure we got everything we wanted + for (const CpuInfoEntry& entry : cpu_info_table) { + if (!entry.found) { + return false; + } + } + // cpu_info_table[0] holds the last cpu id listed in /proc/cpuinfo, + // assuming this is the highest id, change it to the number of CPUs + // by adding one. + cpu_info_table[0].value++; + + sys_info->number_of_processors = cpu_info_table[0].value; +#if defined(__i386__) || defined(__x86_64__) + sys_info->processor_level = cpu_info_table[3].value; + sys_info->processor_revision = cpu_info_table[1].value << 8 | + cpu_info_table[2].value; +#endif + + if (vendor_id[0] != '\0') { + my_memcpy(sys_info->cpu.x86_cpu_info.vendor_id, vendor_id, + sizeof(sys_info->cpu.x86_cpu_info.vendor_id)); + } + return true; + } +#elif defined(__arm__) || defined(__aarch64__) + bool WriteCPUInformation(MDRawSystemInfo* sys_info) { + // The CPUID value is broken up in several entries in /proc/cpuinfo. + // This table is used to rebuild it from the entries. + const struct CpuIdEntry { + const char* field; + char format; + char bit_lshift; + char bit_length; + } cpu_id_entries[] = { + { "CPU implementer", 'x', 24, 8 }, + { "CPU variant", 'x', 20, 4 }, + { "CPU part", 'x', 4, 12 }, + { "CPU revision", 'd', 0, 4 }, + }; + + // The ELF hwcaps are listed in the "Features" entry as textual tags. + // This table is used to rebuild them. + const struct CpuFeaturesEntry { + const char* tag; + uint32_t hwcaps; + } cpu_features_entries[] = { +#if defined(__arm__) + { "swp", MD_CPU_ARM_ELF_HWCAP_SWP }, + { "half", MD_CPU_ARM_ELF_HWCAP_HALF }, + { "thumb", MD_CPU_ARM_ELF_HWCAP_THUMB }, + { "26bit", MD_CPU_ARM_ELF_HWCAP_26BIT }, + { "fastmult", MD_CPU_ARM_ELF_HWCAP_FAST_MULT }, + { "fpa", MD_CPU_ARM_ELF_HWCAP_FPA }, + { "vfp", MD_CPU_ARM_ELF_HWCAP_VFP }, + { "edsp", MD_CPU_ARM_ELF_HWCAP_EDSP }, + { "java", MD_CPU_ARM_ELF_HWCAP_JAVA }, + { "iwmmxt", MD_CPU_ARM_ELF_HWCAP_IWMMXT }, + { "crunch", MD_CPU_ARM_ELF_HWCAP_CRUNCH }, + { "thumbee", MD_CPU_ARM_ELF_HWCAP_THUMBEE }, + { "neon", MD_CPU_ARM_ELF_HWCAP_NEON }, + { "vfpv3", MD_CPU_ARM_ELF_HWCAP_VFPv3 }, + { "vfpv3d16", MD_CPU_ARM_ELF_HWCAP_VFPv3D16 }, + { "tls", MD_CPU_ARM_ELF_HWCAP_TLS }, + { "vfpv4", MD_CPU_ARM_ELF_HWCAP_VFPv4 }, + { "idiva", MD_CPU_ARM_ELF_HWCAP_IDIVA }, + { "idivt", MD_CPU_ARM_ELF_HWCAP_IDIVT }, + { "idiv", MD_CPU_ARM_ELF_HWCAP_IDIVA | MD_CPU_ARM_ELF_HWCAP_IDIVT }, +#elif defined(__aarch64__) + // No hwcaps on aarch64. +#endif + }; + + // processor_architecture should always be set, do this first + sys_info->processor_architecture = +#if defined(__aarch64__) + MD_CPU_ARCHITECTURE_ARM64; +#else + MD_CPU_ARCHITECTURE_ARM; +#endif + + // /proc/cpuinfo is not readable under various sandboxed environments + // (e.g. Android services with the android:isolatedProcess attribute) + // prepare for this by setting default values now, which will be + // returned when this happens. + // + // Note: Bogus values are used to distinguish between failures (to + // read /sys and /proc files) and really badly configured kernels. + sys_info->number_of_processors = 0; + sys_info->processor_level = 1U; // There is no ARMv1 + sys_info->processor_revision = 42; + sys_info->cpu.arm_cpu_info.cpuid = 0; + sys_info->cpu.arm_cpu_info.elf_hwcaps = 0; + + // Counting the number of CPUs involves parsing two sysfs files, + // because the content of /proc/cpuinfo will only mirror the number + // of 'online' cores, and thus will vary with time. + // See http://www.kernel.org/doc/Documentation/cputopology.txt + { + CpuSet cpus_present; + CpuSet cpus_possible; + + int fd = sys_open("/sys/devices/system/cpu/present", O_RDONLY, 0); + if (fd >= 0) { + cpus_present.ParseSysFile(fd); + sys_close(fd); + + fd = sys_open("/sys/devices/system/cpu/possible", O_RDONLY, 0); + if (fd >= 0) { + cpus_possible.ParseSysFile(fd); + sys_close(fd); + + cpus_present.IntersectWith(cpus_possible); + int cpu_count = cpus_present.GetCount(); + if (cpu_count > 255) + cpu_count = 255; + sys_info->number_of_processors = static_cast<uint8_t>(cpu_count); + } + } + } + + // Parse /proc/cpuinfo to reconstruct the CPUID value, as well + // as the ELF hwcaps field. For the latter, it would be easier to + // read /proc/self/auxv but unfortunately, this file is not always + // readable from regular Android applications on later versions + // (>= 4.1) of the Android platform. + const int fd = sys_open("/proc/cpuinfo", O_RDONLY, 0); + if (fd < 0) { + // Do not return false here to allow the minidump generation + // to happen properly. + return true; + } + + { + PageAllocator allocator; + ProcCpuInfoReader* const reader = + new(allocator) ProcCpuInfoReader(fd); + const char* field; + while (reader->GetNextField(&field)) { + for (const CpuIdEntry& entry : cpu_id_entries) { + if (my_strcmp(entry.field, field) != 0) + continue; + uintptr_t result = 0; + const char* value = reader->GetValue(); + const char* p = value; + if (value[0] == '0' && value[1] == 'x') { + p = my_read_hex_ptr(&result, value+2); + } else if (entry.format == 'x') { + p = my_read_hex_ptr(&result, value); + } else { + p = my_read_decimal_ptr(&result, value); + } + if (p == value) + continue; + + result &= (1U << entry.bit_length)-1; + result <<= entry.bit_lshift; + sys_info->cpu.arm_cpu_info.cpuid |= + static_cast<uint32_t>(result); + } +#if defined(__arm__) + // Get the architecture version from the "Processor" field. + // Note that it is also available in the "CPU architecture" field, + // however, some existing kernels are misconfigured and will report + // invalid values here (e.g. 6, while the CPU is ARMv7-A based). + // The "Processor" field doesn't have this issue. + if (!my_strcmp(field, "Processor")) { + size_t value_len; + const char* value = reader->GetValueAndLen(&value_len); + // Expected format: <text> (v<level><endian>) + // Where <text> is some text like "ARMv7 Processor rev 2" + // and <level> is a decimal corresponding to the ARM + // architecture number. <endian> is either 'l' or 'b' + // and corresponds to the endianess, it is ignored here. + while (value_len > 0 && my_isspace(value[value_len-1])) + value_len--; + + size_t nn = value_len; + while (nn > 0 && value[nn-1] != '(') + nn--; + if (nn > 0 && value[nn] == 'v') { + uintptr_t arch_level = 5; + my_read_decimal_ptr(&arch_level, value + nn + 1); + sys_info->processor_level = static_cast<uint16_t>(arch_level); + } + } +#elif defined(__aarch64__) + // The aarch64 architecture does not provide the architecture level + // in the Processor field, so we instead check the "CPU architecture" + // field. + if (!my_strcmp(field, "CPU architecture")) { + uintptr_t arch_level = 0; + const char* value = reader->GetValue(); + const char* p = value; + p = my_read_decimal_ptr(&arch_level, value); + if (p == value) + continue; + sys_info->processor_level = static_cast<uint16_t>(arch_level); + } +#endif + // Rebuild the ELF hwcaps from the 'Features' field. + if (!my_strcmp(field, "Features")) { + size_t value_len; + const char* value = reader->GetValueAndLen(&value_len); + + // Parse each space-separated tag. + while (value_len > 0) { + const char* tag = value; + size_t tag_len = value_len; + const char* p = my_strchr(tag, ' '); + if (p) { + tag_len = static_cast<size_t>(p - tag); + value += tag_len + 1; + value_len -= tag_len + 1; + } else { + tag_len = strlen(tag); + value_len = 0; + } + for (const CpuFeaturesEntry& entry : cpu_features_entries) { + if (tag_len == strlen(entry.tag) && + !memcmp(tag, entry.tag, tag_len)) { + sys_info->cpu.arm_cpu_info.elf_hwcaps |= entry.hwcaps; + break; + } + } + } + } + } + sys_close(fd); + } + + return true; + } +#else +# error "Unsupported CPU" +#endif + + bool WriteFile(MDLocationDescriptor* result, const char* filename) { + const int fd = sys_open(filename, O_RDONLY, 0); + if (fd < 0) + return false; + + // We can't stat the files because several of the files that we want to + // read are kernel seqfiles, which always have a length of zero. So we have + // to read as much as we can into a buffer. + static const unsigned kBufSize = 1024 - 2*sizeof(void*); + struct Buffers { + Buffers* next; + size_t len; + uint8_t data[kBufSize]; + } *buffers = reinterpret_cast<Buffers*>(Alloc(sizeof(Buffers))); + buffers->next = NULL; + buffers->len = 0; + + size_t total = 0; + for (Buffers* bufptr = buffers;;) { + ssize_t r; + do { + r = sys_read(fd, &bufptr->data[bufptr->len], kBufSize - bufptr->len); + } while (r == -1 && errno == EINTR); + + if (r < 1) + break; + + total += r; + bufptr->len += r; + if (bufptr->len == kBufSize) { + bufptr->next = reinterpret_cast<Buffers*>(Alloc(sizeof(Buffers))); + bufptr = bufptr->next; + bufptr->next = NULL; + bufptr->len = 0; + } + } + sys_close(fd); + + if (!total) + return false; + + UntypedMDRVA memory(&minidump_writer_); + if (!memory.Allocate(total)) + return false; + for (MDRVA pos = memory.position(); buffers; buffers = buffers->next) { + // Check for special case of a zero-length buffer. This should only + // occur if a file's size happens to be a multiple of the buffer's + // size, in which case the final sys_read() will have resulted in + // zero bytes being read after the final buffer was just allocated. + if (buffers->len == 0) { + // This can only occur with final buffer. + assert(buffers->next == NULL); + continue; + } + memory.Copy(pos, &buffers->data, buffers->len); + pos += buffers->len; + } + *result = memory.location(); + return true; + } + + bool WriteOSInformation(MDRawSystemInfo* sys_info) { +#if defined(__ANDROID__) + sys_info->platform_id = MD_OS_ANDROID; +#else + sys_info->platform_id = MD_OS_LINUX; +#endif + + struct utsname uts; + if (uname(&uts)) + return false; + + static const size_t buf_len = 512; + char buf[buf_len] = {0}; + size_t space_left = buf_len - 1; + const char* info_table[] = { + uts.sysname, + uts.release, + uts.version, + uts.machine, + NULL + }; + bool first_item = true; + for (const char** cur_info = info_table; *cur_info; cur_info++) { + static const char separator[] = " "; + size_t separator_len = sizeof(separator) - 1; + size_t info_len = my_strlen(*cur_info); + if (info_len == 0) + continue; + + if (space_left < info_len + (first_item ? 0 : separator_len)) + break; + + if (!first_item) { + my_strlcat(buf, separator, sizeof(buf)); + space_left -= separator_len; + } + + first_item = false; + my_strlcat(buf, *cur_info, sizeof(buf)); + space_left -= info_len; + } + + MDLocationDescriptor location; + if (!minidump_writer_.WriteString(buf, 0, &location)) + return false; + sys_info->csd_version_rva = location.rva; + + return true; + } + + bool WriteProcFile(MDLocationDescriptor* result, pid_t pid, + const char* filename) { + char buf[NAME_MAX]; + if (!dumper_->BuildProcPath(buf, pid, filename)) + return false; + return WriteFile(result, buf); + } + + // Only one of the 2 member variables below should be set to a valid value. + const int fd_; // File descriptor where the minidum should be written. + const char* path_; // Path to the file where the minidum should be written. + + const ucontext_t* const ucontext_; // also from the signal handler +#if !defined(__ARM_EABI__) && !defined(__mips__) + const google_breakpad::fpstate_t* const float_state_; // ditto +#endif + LinuxDumper* dumper_; + MinidumpFileWriter minidump_writer_; + off_t minidump_size_limit_; + MDLocationDescriptor crashing_thread_context_; + // 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. + wasteful_vector<MDMemoryDescriptor> memory_blocks_; + // Additional information about some mappings provided by the caller. + const MappingList& mapping_list_; + // Additional memory regions to be included in the dump, + // provided by the caller. + const AppMemoryList& app_memory_list_; +}; + + +bool WriteMinidumpImpl(const char* minidump_path, + int minidump_fd, + off_t minidump_size_limit, + pid_t crashing_process, + const void* blob, size_t blob_size, + const MappingList& mappings, + const AppMemoryList& appmem) { + LinuxPtraceDumper dumper(crashing_process); + const ExceptionHandler::CrashContext* context = NULL; + if (blob) { + if (blob_size != sizeof(ExceptionHandler::CrashContext)) + return false; + context = reinterpret_cast<const ExceptionHandler::CrashContext*>(blob); + dumper.set_crash_address( + reinterpret_cast<uintptr_t>(context->siginfo.si_addr)); + dumper.set_crash_signal(context->siginfo.si_signo); + dumper.set_crash_thread(context->tid); + } + MinidumpWriter writer(minidump_path, minidump_fd, context, mappings, + appmem, &dumper); + // Set desired limit for file size of minidump (-1 means no limit). + writer.set_minidump_size_limit(minidump_size_limit); + if (!writer.Init()) + return false; + return writer.Dump(); +} + +} // namespace + +namespace google_breakpad { + +bool WriteMinidump(const char* minidump_path, pid_t crashing_process, + const void* blob, size_t blob_size) { + return WriteMinidumpImpl(minidump_path, -1, -1, + crashing_process, blob, blob_size, + MappingList(), AppMemoryList()); +} + +bool WriteMinidump(int minidump_fd, pid_t crashing_process, + const void* blob, size_t blob_size) { + return WriteMinidumpImpl(NULL, minidump_fd, -1, + crashing_process, blob, blob_size, + MappingList(), AppMemoryList()); +} + +bool WriteMinidump(const char* minidump_path, pid_t process, + pid_t process_blamed_thread) { + LinuxPtraceDumper dumper(process); + // MinidumpWriter will set crash address + dumper.set_crash_signal(MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED); + dumper.set_crash_thread(process_blamed_thread); + MinidumpWriter writer(minidump_path, -1, NULL, MappingList(), + AppMemoryList(), &dumper); + if (!writer.Init()) + return false; + return writer.Dump(); +} + +bool WriteMinidump(const char* minidump_path, pid_t crashing_process, + const void* blob, size_t blob_size, + const MappingList& mappings, + const AppMemoryList& appmem) { + return WriteMinidumpImpl(minidump_path, -1, -1, crashing_process, + blob, blob_size, + mappings, appmem); +} + +bool WriteMinidump(int minidump_fd, pid_t crashing_process, + const void* blob, size_t blob_size, + const MappingList& mappings, + const AppMemoryList& appmem) { + return WriteMinidumpImpl(NULL, minidump_fd, -1, crashing_process, + blob, blob_size, + mappings, appmem); +} + +bool WriteMinidump(const char* minidump_path, off_t minidump_size_limit, + pid_t crashing_process, + const void* blob, size_t blob_size, + const MappingList& mappings, + const AppMemoryList& appmem) { + return WriteMinidumpImpl(minidump_path, -1, minidump_size_limit, + crashing_process, blob, blob_size, + mappings, appmem); +} + +bool WriteMinidump(int minidump_fd, off_t minidump_size_limit, + pid_t crashing_process, + const void* blob, size_t blob_size, + const MappingList& mappings, + const AppMemoryList& appmem) { + return WriteMinidumpImpl(NULL, minidump_fd, minidump_size_limit, + crashing_process, blob, blob_size, + mappings, appmem); +} + +bool WriteMinidump(const char* filename, + const MappingList& mappings, + const AppMemoryList& appmem, + LinuxDumper* dumper) { + MinidumpWriter writer(filename, -1, NULL, mappings, appmem, dumper); + if (!writer.Init()) + return false; + return writer.Dump(); +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer.h b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer.h new file mode 100644 index 000000000..d13fb120b --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer.h @@ -0,0 +1,124 @@ +// Copyright (c) 2009, 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_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_ +#define CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_ + +#include <stdint.h> +#include <sys/types.h> +#include <sys/ucontext.h> +#include <unistd.h> + +#include <list> +#include <utility> + +#include "client/linux/minidump_writer/linux_dumper.h" +#include "google_breakpad/common/minidump_format.h" + +namespace google_breakpad { + +class ExceptionHandler; + +#if defined(__aarch64__) +typedef struct fpsimd_context fpstate_t; +#elif !defined(__ARM_EABI__) && !defined(__mips__) +typedef struct _libc_fpstate fpstate_t; +#endif + +// These entries store a list of memory regions that the client wants included +// in the minidump. +struct AppMemory { + void* ptr; + size_t length; + + bool operator==(const struct AppMemory& other) const { + return ptr == other.ptr; + } + + bool operator==(const void* other) const { + return ptr == other; + } +}; +typedef std::list<AppMemory> AppMemoryList; + +// Writes a minidump to the filesystem. These functions do not malloc nor use +// libc functions which may. Thus, it can be used in contexts where the state +// of the heap may be corrupt. +// minidump_path: the path to the file to write to. This is opened O_EXCL and +// fails open fails. +// crashing_process: the pid of the crashing process. This must be trusted. +// blob: a blob of data from the crashing process. See exception_handler.h +// blob_size: the length of |blob|, in bytes +// +// Returns true iff successful. +bool WriteMinidump(const char* minidump_path, pid_t crashing_process, + const void* blob, size_t blob_size); +// Same as above but takes an open file descriptor instead of a path. +bool WriteMinidump(int minidump_fd, pid_t crashing_process, + const void* blob, size_t blob_size); + +// Alternate form of WriteMinidump() that works with processes that +// are not expected to have crashed. If |process_blamed_thread| is +// meaningful, it will be the one from which a crash signature is +// extracted. It is not expected that this function will be called +// from a compromised context, but it is safe to do so. +bool WriteMinidump(const char* minidump_path, pid_t process, + pid_t process_blamed_thread); + +// These overloads also allow passing a list of known mappings and +// a list of additional memory regions to be included in the minidump. +bool WriteMinidump(const char* minidump_path, pid_t crashing_process, + const void* blob, size_t blob_size, + const MappingList& mappings, + const AppMemoryList& appdata); +bool WriteMinidump(int minidump_fd, pid_t crashing_process, + const void* blob, size_t blob_size, + const MappingList& mappings, + const AppMemoryList& appdata); + +// These overloads also allow passing a file size limit for the minidump. +bool WriteMinidump(const char* minidump_path, off_t minidump_size_limit, + pid_t crashing_process, + const void* blob, size_t blob_size, + const MappingList& mappings, + const AppMemoryList& appdata); +bool WriteMinidump(int minidump_fd, off_t minidump_size_limit, + pid_t crashing_process, + const void* blob, size_t blob_size, + const MappingList& mappings, + const AppMemoryList& appdata); + +bool WriteMinidump(const char* filename, + const MappingList& mappings, + const AppMemoryList& appdata, + LinuxDumper* dumper); + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer_unittest.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer_unittest.cc new file mode 100644 index 000000000..2e4749e7e --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer_unittest.cc @@ -0,0 +1,775 @@ +// 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. + +#include <fcntl.h> +#include <sys/poll.h> +#include <sys/stat.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <ucontext.h> +#include <unistd.h> + +#include <string> + +#include "breakpad_googletest_includes.h" +#include "client/linux/handler/exception_handler.h" +#include "client/linux/minidump_writer/linux_dumper.h" +#include "client/linux/minidump_writer/minidump_writer.h" +#include "client/linux/minidump_writer/minidump_writer_unittest_utils.h" +#include "common/linux/eintr_wrapper.h" +#include "common/linux/file_id.h" +#include "common/linux/ignore_ret.h" +#include "common/linux/safe_readlink.h" +#include "common/scoped_ptr.h" +#include "common/tests/auto_tempdir.h" +#include "common/tests/file_utils.h" +#include "common/using_std_string.h" +#include "google_breakpad/processor/minidump.h" + +using namespace google_breakpad; + +namespace { + +typedef testing::Test MinidumpWriterTest; + +const char kMDWriterUnitTestFileName[] = "/minidump-writer-unittest"; + +TEST(MinidumpWriterTest, SetupWithPath) { + int fds[2]; + ASSERT_NE(-1, pipe(fds)); + + const pid_t child = fork(); + if (child == 0) { + close(fds[1]); + char b; + IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b)))); + close(fds[0]); + syscall(__NR_exit); + } + close(fds[0]); + + ExceptionHandler::CrashContext context; + memset(&context, 0, sizeof(context)); + + AutoTempDir temp_dir; + string templ = temp_dir.path() + kMDWriterUnitTestFileName; + // Set a non-zero tid to avoid tripping asserts. + context.tid = child; + ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context))); + struct stat st; + ASSERT_EQ(0, stat(templ.c_str(), &st)); + ASSERT_GT(st.st_size, 0); + + close(fds[1]); +} + +TEST(MinidumpWriterTest, SetupWithFD) { + int fds[2]; + ASSERT_NE(-1, pipe(fds)); + + const pid_t child = fork(); + if (child == 0) { + close(fds[1]); + char b; + HANDLE_EINTR(read(fds[0], &b, sizeof(b))); + close(fds[0]); + syscall(__NR_exit); + } + close(fds[0]); + + ExceptionHandler::CrashContext context; + memset(&context, 0, sizeof(context)); + + AutoTempDir temp_dir; + string templ = temp_dir.path() + kMDWriterUnitTestFileName; + int fd = open(templ.c_str(), O_CREAT | O_WRONLY, S_IRWXU); + // Set a non-zero tid to avoid tripping asserts. + context.tid = child; + ASSERT_TRUE(WriteMinidump(fd, child, &context, sizeof(context))); + struct stat st; + ASSERT_EQ(0, stat(templ.c_str(), &st)); + ASSERT_GT(st.st_size, 0); + + close(fds[1]); +} + +// Test that mapping info can be specified when writing a minidump, +// and that it ends up in the module list of the minidump. +TEST(MinidumpWriterTest, MappingInfo) { + int fds[2]; + ASSERT_NE(-1, pipe(fds)); + + // These are defined here so the parent can use them to check the + // data from the minidump afterwards. + const uint32_t memory_size = sysconf(_SC_PAGESIZE); + const char* kMemoryName = "a fake module"; + const uint8_t kModuleGUID[sizeof(MDGUID)] = { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF + }; + const string module_identifier = "33221100554477668899AABBCCDDEEFF0"; + + // Get some memory. + char* memory = + reinterpret_cast<char*>(mmap(NULL, + memory_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, + -1, + 0)); + const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory); + ASSERT_TRUE(memory); + + const pid_t child = fork(); + if (child == 0) { + close(fds[1]); + char b; + IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b)))); + close(fds[0]); + syscall(__NR_exit); + } + close(fds[0]); + + ExceptionHandler::CrashContext context; + memset(&context, 0, sizeof(context)); + ASSERT_EQ(0, getcontext(&context.context)); + context.tid = child; + + AutoTempDir temp_dir; + string templ = temp_dir.path() + kMDWriterUnitTestFileName; + + // Add information about the mapped memory. + MappingInfo info; + info.start_addr = kMemoryAddress; + info.size = memory_size; + info.offset = 0; + info.exec = false; + strcpy(info.name, kMemoryName); + + MappingList mappings; + AppMemoryList memory_list; + MappingEntry mapping; + mapping.first = info; + memcpy(mapping.second, kModuleGUID, sizeof(MDGUID)); + mappings.push_back(mapping); + ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context), + mappings, memory_list)); + + // Read the minidump. Load the module list, and ensure that + // the mmap'ed |memory| is listed with the given module name + // and debug ID. + Minidump minidump(templ); + ASSERT_TRUE(minidump.Read()); + + MinidumpModuleList* module_list = minidump.GetModuleList(); + ASSERT_TRUE(module_list); + const MinidumpModule* module = + module_list->GetModuleForAddress(kMemoryAddress); + ASSERT_TRUE(module); + + EXPECT_EQ(kMemoryAddress, module->base_address()); + EXPECT_EQ(memory_size, module->size()); + EXPECT_EQ(kMemoryName, module->code_file()); + EXPECT_EQ(module_identifier, module->debug_identifier()); + + uint32_t len; + // These streams are expected to be there + EXPECT_TRUE(minidump.SeekToStreamType(MD_THREAD_LIST_STREAM, &len)); + EXPECT_TRUE(minidump.SeekToStreamType(MD_MEMORY_LIST_STREAM, &len)); + EXPECT_TRUE(minidump.SeekToStreamType(MD_EXCEPTION_STREAM, &len)); + EXPECT_TRUE(minidump.SeekToStreamType(MD_SYSTEM_INFO_STREAM, &len)); + EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_CPU_INFO, &len)); + EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_PROC_STATUS, &len)); + EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_CMD_LINE, &len)); + EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_ENVIRON, &len)); + EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_AUXV, &len)); + EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_MAPS, &len)); + EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_DSO_DEBUG, &len)); + + close(fds[1]); +} + +// Test that a binary with a longer-than-usual build id note +// makes its way all the way through to the minidump unscathed. +// The linux_client_unittest is linked with an explicit --build-id +// in Makefile.am. +TEST(MinidumpWriterTest, BuildIDLong) { + int fds[2]; + ASSERT_NE(-1, pipe(fds)); + + const pid_t child = fork(); + if (child == 0) { + close(fds[1]); + char b; + IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b)))); + close(fds[0]); + syscall(__NR_exit); + } + close(fds[0]); + + ExceptionHandler::CrashContext context; + memset(&context, 0, sizeof(context)); + ASSERT_EQ(0, getcontext(&context.context)); + context.tid = child; + + AutoTempDir temp_dir; + const string dump_path = temp_dir.path() + kMDWriterUnitTestFileName; + + EXPECT_TRUE(WriteMinidump(dump_path.c_str(), + child, &context, sizeof(context))); + close(fds[1]); + + // Read the minidump. Load the module list, and ensure that + // the main module has the correct debug id and code id. + Minidump minidump(dump_path); + ASSERT_TRUE(minidump.Read()); + + MinidumpModuleList* module_list = minidump.GetModuleList(); + ASSERT_TRUE(module_list); + const MinidumpModule* module = module_list->GetMainModule(); + ASSERT_TRUE(module); + const string module_identifier = "030201000504070608090A0B0C0D0E0F0"; + // This is passed explicitly to the linker in Makefile.am + const string build_id = + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"; + EXPECT_EQ(module_identifier, module->debug_identifier()); + EXPECT_EQ(build_id, module->code_identifier()); +} + +// Test that mapping info can be specified, and that it overrides +// existing mappings that are wholly contained within the specified +// range. +TEST(MinidumpWriterTest, MappingInfoContained) { + int fds[2]; + ASSERT_NE(-1, pipe(fds)); + + // These are defined here so the parent can use them to check the + // data from the minidump afterwards. + const int32_t memory_size = sysconf(_SC_PAGESIZE); + const char* kMemoryName = "a fake module"; + const uint8_t kModuleGUID[sizeof(MDGUID)] = { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF + }; + const string module_identifier = "33221100554477668899AABBCCDDEEFF0"; + + // mmap a file + AutoTempDir temp_dir; + string tempfile = temp_dir.path() + "/minidump-writer-unittest-temp"; + int fd = open(tempfile.c_str(), O_RDWR | O_CREAT, 0); + ASSERT_NE(-1, fd); + unlink(tempfile.c_str()); + // fill with zeros + google_breakpad::scoped_array<char> buffer(new char[memory_size]); + memset(buffer.get(), 0, memory_size); + ASSERT_EQ(memory_size, write(fd, buffer.get(), memory_size)); + lseek(fd, 0, SEEK_SET); + + char* memory = + reinterpret_cast<char*>(mmap(NULL, + memory_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE, + fd, + 0)); + const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory); + ASSERT_TRUE(memory); + close(fd); + + const pid_t child = fork(); + if (child == 0) { + close(fds[1]); + char b; + IGNORE_RET(HANDLE_EINTR(read(fds[0], &b, sizeof(b)))); + close(fds[0]); + syscall(__NR_exit); + } + close(fds[0]); + + ExceptionHandler::CrashContext context; + memset(&context, 0, sizeof(context)); + context.tid = 1; + + string dumpfile = temp_dir.path() + kMDWriterUnitTestFileName; + + // Add information about the mapped memory. Report it as being larger than + // it actually is. + MappingInfo info; + info.start_addr = kMemoryAddress - memory_size; + info.size = memory_size * 3; + info.offset = 0; + info.exec = false; + strcpy(info.name, kMemoryName); + + MappingList mappings; + AppMemoryList memory_list; + MappingEntry mapping; + mapping.first = info; + memcpy(mapping.second, kModuleGUID, sizeof(MDGUID)); + mappings.push_back(mapping); + ASSERT_TRUE(WriteMinidump(dumpfile.c_str(), child, &context, sizeof(context), + mappings, memory_list)); + + // Read the minidump. Load the module list, and ensure that + // the mmap'ed |memory| is listed with the given module name + // and debug ID. + Minidump minidump(dumpfile); + ASSERT_TRUE(minidump.Read()); + + MinidumpModuleList* module_list = minidump.GetModuleList(); + ASSERT_TRUE(module_list); + const MinidumpModule* module = + module_list->GetModuleForAddress(kMemoryAddress); + ASSERT_TRUE(module); + + EXPECT_EQ(info.start_addr, module->base_address()); + EXPECT_EQ(info.size, module->size()); + EXPECT_EQ(kMemoryName, module->code_file()); + EXPECT_EQ(module_identifier, module->debug_identifier()); + + close(fds[1]); +} + +TEST(MinidumpWriterTest, DeletedBinary) { + const string kNumberOfThreadsArgument = "1"; + const string helper_path(GetHelperBinary()); + if (helper_path.empty()) { + FAIL() << "Couldn't find helper binary"; + exit(1); + } + + // Copy binary to a temp file. + AutoTempDir temp_dir; + string binpath = temp_dir.path() + "/linux-dumper-unittest-helper"; + ASSERT_TRUE(CopyFile(helper_path.c_str(), binpath.c_str())) + << "Failed to copy " << helper_path << " to " << binpath; + ASSERT_EQ(0, chmod(binpath.c_str(), 0755)); + + int fds[2]; + ASSERT_NE(-1, pipe(fds)); + + pid_t child_pid = fork(); + if (child_pid == 0) { + // In child process. + close(fds[0]); + + // Pass the pipe fd and the number of threads as arguments. + char pipe_fd_string[8]; + sprintf(pipe_fd_string, "%d", fds[1]); + execl(binpath.c_str(), + binpath.c_str(), + pipe_fd_string, + kNumberOfThreadsArgument.c_str(), + NULL); + } + close(fds[1]); + // Wait for the child process to signal that it's ready. + struct pollfd pfd; + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = fds[0]; + pfd.events = POLLIN | POLLERR; + + const int r = HANDLE_EINTR(poll(&pfd, 1, 1000)); + ASSERT_EQ(1, r); + ASSERT_TRUE(pfd.revents & POLLIN); + uint8_t junk; + const int nr = HANDLE_EINTR(read(fds[0], &junk, sizeof(junk))); + ASSERT_EQ(static_cast<ssize_t>(sizeof(junk)), nr); + close(fds[0]); + + // Child is ready now. + // Unlink the test binary. + unlink(binpath.c_str()); + + ExceptionHandler::CrashContext context; + memset(&context, 0, sizeof(context)); + + string templ = temp_dir.path() + kMDWriterUnitTestFileName; + // Set a non-zero tid to avoid tripping asserts. + context.tid = child_pid; + ASSERT_TRUE(WriteMinidump(templ.c_str(), child_pid, &context, + sizeof(context))); + kill(child_pid, SIGKILL); + + struct stat st; + ASSERT_EQ(0, stat(templ.c_str(), &st)); + ASSERT_GT(st.st_size, 0); + + Minidump minidump(templ); + ASSERT_TRUE(minidump.Read()); + + // Check that the main module filename is correct. + MinidumpModuleList* module_list = minidump.GetModuleList(); + ASSERT_TRUE(module_list); + const MinidumpModule* module = module_list->GetMainModule(); + EXPECT_STREQ(binpath.c_str(), module->code_file().c_str()); + // Check that the file ID is correct. + FileID fileid(helper_path.c_str()); + PageAllocator allocator; + wasteful_vector<uint8_t> identifier(&allocator, kDefaultBuildIdSize); + EXPECT_TRUE(fileid.ElfFileIdentifier(identifier)); + string identifier_string = FileID::ConvertIdentifierToUUIDString(identifier); + string module_identifier(identifier_string); + // Strip out dashes + size_t pos; + while ((pos = module_identifier.find('-')) != string::npos) { + module_identifier.erase(pos, 1); + } + // And append a zero, because module IDs include an "age" field + // which is always zero on Linux. + module_identifier += "0"; + EXPECT_EQ(module_identifier, module->debug_identifier()); +} + +// Test that an additional memory region can be added to the minidump. +TEST(MinidumpWriterTest, AdditionalMemory) { + int fds[2]; + ASSERT_NE(-1, pipe(fds)); + + // These are defined here so the parent can use them to check the + // data from the minidump afterwards. + const uint32_t kMemorySize = sysconf(_SC_PAGESIZE); + + // Get some heap memory. + uint8_t* memory = new uint8_t[kMemorySize]; + const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory); + ASSERT_TRUE(memory); + + // Stick some data into the memory so the contents can be verified. + for (uint32_t i = 0; i < kMemorySize; ++i) { + memory[i] = i % 255; + } + + const pid_t child = fork(); + if (child == 0) { + close(fds[1]); + char b; + HANDLE_EINTR(read(fds[0], &b, sizeof(b))); + close(fds[0]); + syscall(__NR_exit); + } + close(fds[0]); + + ExceptionHandler::CrashContext context; + + // This needs a valid context for minidump writing to work, but getting + // a useful one from the child is too much work, so just use one from + // the parent since the child is just a forked copy anyway. + ASSERT_EQ(0, getcontext(&context.context)); + context.tid = child; + + AutoTempDir temp_dir; + string templ = temp_dir.path() + kMDWriterUnitTestFileName; + unlink(templ.c_str()); + + MappingList mappings; + AppMemoryList memory_list; + + // Add the memory region to the list of memory to be included. + AppMemory app_memory; + app_memory.ptr = memory; + app_memory.length = kMemorySize; + memory_list.push_back(app_memory); + ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context), + mappings, memory_list)); + + // Read the minidump. Ensure that the memory region is present + Minidump minidump(templ); + ASSERT_TRUE(minidump.Read()); + + MinidumpMemoryList* dump_memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(dump_memory_list); + const MinidumpMemoryRegion* region = + dump_memory_list->GetMemoryRegionForAddress(kMemoryAddress); + ASSERT_TRUE(region); + + EXPECT_EQ(kMemoryAddress, region->GetBase()); + EXPECT_EQ(kMemorySize, region->GetSize()); + + // Verify memory contents. + EXPECT_EQ(0, memcmp(region->GetMemory(), memory, kMemorySize)); + + delete[] memory; + close(fds[1]); +} + +// Test that an invalid thread stack pointer still results in a minidump. +TEST(MinidumpWriterTest, InvalidStackPointer) { + int fds[2]; + ASSERT_NE(-1, pipe(fds)); + + const pid_t child = fork(); + if (child == 0) { + close(fds[1]); + char b; + HANDLE_EINTR(read(fds[0], &b, sizeof(b))); + close(fds[0]); + syscall(__NR_exit); + } + close(fds[0]); + + ExceptionHandler::CrashContext context; + + // This needs a valid context for minidump writing to work, but getting + // a useful one from the child is too much work, so just use one from + // the parent since the child is just a forked copy anyway. + ASSERT_EQ(0, getcontext(&context.context)); + context.tid = child; + + // Fake the child's stack pointer for its crashing thread. NOTE: This must + // be an invalid memory address for the child process (stack or otherwise). + // Try 1MB below the current stack. + uintptr_t invalid_stack_pointer = + reinterpret_cast<uintptr_t>(&context) - 1024*1024; +#if defined(__i386) + context.context.uc_mcontext.gregs[REG_ESP] = invalid_stack_pointer; +#elif defined(__x86_64) + context.context.uc_mcontext.gregs[REG_RSP] = invalid_stack_pointer; +#elif defined(__ARM_EABI__) + context.context.uc_mcontext.arm_sp = invalid_stack_pointer; +#elif defined(__aarch64__) + context.context.uc_mcontext.sp = invalid_stack_pointer; +#elif defined(__mips__) + context.context.uc_mcontext.gregs[MD_CONTEXT_MIPS_REG_SP] = + invalid_stack_pointer; +#else +# error "This code has not been ported to your platform yet." +#endif + + AutoTempDir temp_dir; + string templ = temp_dir.path() + kMDWriterUnitTestFileName; + // NOTE: In previous versions of Breakpad, WriteMinidump() would fail if + // presented with an invalid stack pointer. + ASSERT_TRUE(WriteMinidump(templ.c_str(), child, &context, sizeof(context))); + + // Read the minidump. Ensure that the memory region is present + Minidump minidump(templ); + ASSERT_TRUE(minidump.Read()); + + // TODO(ted.mielczarek,mkrebs): Enable this part of the test once + // https://breakpad.appspot.com/413002/ is committed. +#if 0 + // Make sure there's a thread without a stack. NOTE: It's okay if + // GetThreadList() shows the error: "ERROR: MinidumpThread has a memory + // region problem". + MinidumpThreadList* dump_thread_list = minidump.GetThreadList(); + ASSERT_TRUE(dump_thread_list); + bool found_empty_stack = false; + for (int i = 0; i < dump_thread_list->thread_count(); i++) { + MinidumpThread* thread = dump_thread_list->GetThreadAtIndex(i); + ASSERT_TRUE(thread->thread() != NULL); + // When the stack size is zero bytes, GetMemory() returns NULL. + if (thread->GetMemory() == NULL) { + found_empty_stack = true; + break; + } + } + // NOTE: If you fail this, first make sure that "invalid_stack_pointer" + // above is indeed set to an invalid address. + ASSERT_TRUE(found_empty_stack); +#endif + + close(fds[1]); +} + +// Test that limiting the size of the minidump works. +TEST(MinidumpWriterTest, MinidumpSizeLimit) { + static const int kNumberOfThreadsInHelperProgram = 40; + + char number_of_threads_arg[3]; + sprintf(number_of_threads_arg, "%d", kNumberOfThreadsInHelperProgram); + + string helper_path(GetHelperBinary()); + if (helper_path.empty()) { + FAIL() << "Couldn't find helper binary"; + exit(1); + } + + int fds[2]; + ASSERT_NE(-1, pipe(fds)); + + pid_t child_pid = fork(); + if (child_pid == 0) { + // In child process. + close(fds[0]); + + // Pass the pipe fd and the number of threads as arguments. + char pipe_fd_string[8]; + sprintf(pipe_fd_string, "%d", fds[1]); + execl(helper_path.c_str(), + helper_path.c_str(), + pipe_fd_string, + number_of_threads_arg, + NULL); + } + close(fds[1]); + + // Wait for all child threads to indicate that they have started + for (int threads = 0; threads < kNumberOfThreadsInHelperProgram; threads++) { + struct pollfd pfd; + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = fds[0]; + pfd.events = POLLIN | POLLERR; + + const int r = HANDLE_EINTR(poll(&pfd, 1, 1000)); + ASSERT_EQ(1, r); + ASSERT_TRUE(pfd.revents & POLLIN); + uint8_t junk; + ASSERT_EQ(read(fds[0], &junk, sizeof(junk)), + static_cast<ssize_t>(sizeof(junk))); + } + close(fds[0]); + + // There is a race here because we may stop a child thread before + // it is actually running the busy loop. Empirically this sleep + // is sufficient to avoid the race. + usleep(100000); + + // Child and its threads are ready now. + + + off_t normal_file_size; + int total_normal_stack_size = 0; + AutoTempDir temp_dir; + + // First, write a minidump with no size limit. + { + string normal_dump = temp_dir.path() + + "/minidump-writer-unittest.dmp"; + ASSERT_TRUE(WriteMinidump(normal_dump.c_str(), -1, + child_pid, NULL, 0, + MappingList(), AppMemoryList())); + struct stat st; + ASSERT_EQ(0, stat(normal_dump.c_str(), &st)); + ASSERT_GT(st.st_size, 0); + normal_file_size = st.st_size; + + Minidump minidump(normal_dump); + ASSERT_TRUE(minidump.Read()); + MinidumpThreadList* dump_thread_list = minidump.GetThreadList(); + ASSERT_TRUE(dump_thread_list); + for (unsigned int i = 0; i < dump_thread_list->thread_count(); i++) { + MinidumpThread* thread = dump_thread_list->GetThreadAtIndex(i); + ASSERT_TRUE(thread->thread() != NULL); + // When the stack size is zero bytes, GetMemory() returns NULL. + MinidumpMemoryRegion* memory = thread->GetMemory(); + ASSERT_TRUE(memory != NULL); + total_normal_stack_size += memory->GetSize(); + } + } + + // Second, write a minidump with a size limit big enough to not trigger + // anything. + { + // Set size limit arbitrarily 1MB larger than the normal file size -- such + // that the limiting code will not kick in. + const off_t minidump_size_limit = normal_file_size + 1024*1024; + + string same_dump = temp_dir.path() + + "/minidump-writer-unittest-same.dmp"; + ASSERT_TRUE(WriteMinidump(same_dump.c_str(), minidump_size_limit, + child_pid, NULL, 0, + MappingList(), AppMemoryList())); + struct stat st; + ASSERT_EQ(0, stat(same_dump.c_str(), &st)); + // Make sure limiting wasn't actually triggered. NOTE: If you fail this, + // first make sure that "minidump_size_limit" above is indeed set to a + // large enough value -- the limit-checking code in minidump_writer.cc + // does just a rough estimate. + ASSERT_EQ(normal_file_size, st.st_size); + } + + // Third, write a minidump with a size limit small enough to be triggered. + { + // Set size limit to some arbitrary amount, such that the limiting code + // will kick in. The equation used to set this value was determined by + // simply reversing the size-limit logic a little bit in order to pick a + // size we know will trigger it. The definition of + // kLimitAverageThreadStackLength here was copied from class + // MinidumpWriter in minidump_writer.cc. + static const unsigned kLimitAverageThreadStackLength = 8 * 1024; + off_t minidump_size_limit = kNumberOfThreadsInHelperProgram * + kLimitAverageThreadStackLength; + // If, in reality, each of the threads' stack is *smaller* than + // kLimitAverageThreadStackLength, the normal file size could very well be + // smaller than the arbitrary limit that was just set. In that case, + // either of these numbers should trigger the size-limiting code, but we + // might as well pick the smallest. + if (normal_file_size < minidump_size_limit) + minidump_size_limit = normal_file_size; + + string limit_dump = temp_dir.path() + + "/minidump-writer-unittest-limit.dmp"; + ASSERT_TRUE(WriteMinidump(limit_dump.c_str(), minidump_size_limit, + child_pid, NULL, 0, + MappingList(), AppMemoryList())); + struct stat st; + ASSERT_EQ(0, stat(limit_dump.c_str(), &st)); + ASSERT_GT(st.st_size, 0); + // Make sure the file size is at least smaller than the original. If this + // fails because it's the same size, then the size-limit logic didn't kick + // in like it was supposed to. + EXPECT_LT(st.st_size, normal_file_size); + + Minidump minidump(limit_dump); + ASSERT_TRUE(minidump.Read()); + MinidumpThreadList* dump_thread_list = minidump.GetThreadList(); + ASSERT_TRUE(dump_thread_list); + int total_limit_stack_size = 0; + for (unsigned int i = 0; i < dump_thread_list->thread_count(); i++) { + MinidumpThread* thread = dump_thread_list->GetThreadAtIndex(i); + ASSERT_TRUE(thread->thread() != NULL); + // When the stack size is zero bytes, GetMemory() returns NULL. + MinidumpMemoryRegion* memory = thread->GetMemory(); + ASSERT_TRUE(memory != NULL); + total_limit_stack_size += memory->GetSize(); + } + + // Make sure stack size shrunk by at least 1KB per extra thread. The + // definition of kLimitBaseThreadCount here was copied from class + // MinidumpWriter in minidump_writer.cc. + // Note: The 1KB is arbitrary, and assumes that the thread stacks are big + // enough to shrink by that much. For example, if each thread stack was + // originally only 2KB, the current size-limit logic wouldn't actually + // shrink them because that's the size to which it tries to shrink. If + // you fail this part of the test due to something like that, the test + // logic should probably be improved to account for your situation. + const unsigned kLimitBaseThreadCount = 20; + const unsigned kMinPerExtraThreadStackReduction = 1024; + const int min_expected_reduction = (kNumberOfThreadsInHelperProgram - + kLimitBaseThreadCount) * kMinPerExtraThreadStackReduction; + EXPECT_LT(total_limit_stack_size, + total_normal_stack_size - min_expected_reduction); + } + + // Kill the helper program. + kill(child_pid, SIGKILL); +} + +} // namespace diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer_unittest_utils.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer_unittest_utils.cc new file mode 100644 index 000000000..9f46fa65c --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer_unittest_utils.cc @@ -0,0 +1,66 @@ +// 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. + +// minidump_writer_unittest_utils.cc: +// Shared routines used by unittests under client/linux/minidump_writer. + +#include <limits.h> +#include <stdlib.h> + +#include "client/linux/minidump_writer/minidump_writer_unittest_utils.h" +#include "common/linux/safe_readlink.h" +#include "common/using_std_string.h" + +namespace google_breakpad { + +string GetHelperBinary() { + string helper_path; + char *bindir = getenv("bindir"); + if (bindir) { + helper_path = string(bindir) + "/"; + } else { + // Locate helper binary next to the current binary. + char self_path[PATH_MAX]; + if (!SafeReadLink("/proc/self/exe", self_path)) { + return ""; + } + helper_path = string(self_path); + size_t pos = helper_path.rfind('/'); + if (pos == string::npos) { + return ""; + } + helper_path.erase(pos + 1); + } + + helper_path += "linux_dumper_unittest_helper"; + + return helper_path; +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer_unittest_utils.h b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer_unittest_utils.h new file mode 100644 index 000000000..f16cc086b --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer_unittest_utils.h @@ -0,0 +1,49 @@ +// 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. + +// minidump_writer_unittest_utils.h: +// Shared routines used by unittests under client/linux/minidump_writer. + +#ifndef CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_UNITTEST_UTILS_H_ +#define CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_UNITTEST_UTILS_H_ + +#include <string> + +#include "common/using_std_string.h" + +namespace google_breakpad { + +// Returns the full path to linux_dumper_unittest_helper. The full path is +// discovered either by using the environment variable "bindir" or by using +// the location of the main module of the currently running process. +string GetHelperBinary(); + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_UNITTEST_UTILS_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/proc_cpuinfo_reader.h b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/proc_cpuinfo_reader.h new file mode 100644 index 000000000..d9461bf30 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/proc_cpuinfo_reader.h @@ -0,0 +1,130 @@ +// Copyright (c) 2013, 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_LINUX_MINIDUMP_WRITER_PROC_CPUINFO_READER_H_ +#define CLIENT_LINUX_MINIDUMP_WRITER_PROC_CPUINFO_READER_H_ + +#include <stdint.h> +#include <assert.h> +#include <string.h> + +#include "client/linux/minidump_writer/line_reader.h" +#include "common/linux/linux_libc_support.h" +#include "third_party/lss/linux_syscall_support.h" + +namespace google_breakpad { + +// A class for reading /proc/cpuinfo without using fopen/fgets or other +// functions which may allocate memory. +class ProcCpuInfoReader { +public: + ProcCpuInfoReader(int fd) + : line_reader_(fd), pop_count_(-1) { + } + + // Return the next field name, or NULL in case of EOF. + // field: (output) Pointer to zero-terminated field name. + // Returns true on success, or false on EOF or error (line too long). + bool GetNextField(const char** field) { + for (;;) { + const char* line; + unsigned line_len; + + // Try to read next line. + if (pop_count_ >= 0) { + line_reader_.PopLine(pop_count_); + pop_count_ = -1; + } + + if (!line_reader_.GetNextLine(&line, &line_len)) + return false; + + pop_count_ = static_cast<int>(line_len); + + const char* line_end = line + line_len; + + // Expected format: <field-name> <space>+ ':' <space> <value> + // Note that: + // - empty lines happen. + // - <field-name> can contain spaces. + // - some fields have an empty <value> + char* sep = static_cast<char*>(my_memchr(line, ':', line_len)); + if (sep == NULL) + continue; + + // Record the value. Skip leading space after the column to get + // its start. + const char* val = sep+1; + while (val < line_end && my_isspace(*val)) + val++; + + value_ = val; + value_len_ = static_cast<size_t>(line_end - val); + + // Remove trailing spaces before the column to properly 0-terminate + // the field name. + while (sep > line && my_isspace(sep[-1])) + sep--; + + if (sep == line) + continue; + + // zero-terminate field name. + *sep = '\0'; + + *field = line; + return true; + } + } + + // Return the field value. This must be called after a succesful + // call to GetNextField(). + const char* GetValue() { + assert(value_); + return value_; + } + + // Same as GetValue(), but also returns the length in characters of + // the value. + const char* GetValueAndLen(size_t* length) { + assert(value_); + *length = value_len_; + return value_; + } + +private: + LineReader line_reader_; + int pop_count_; + const char* value_; + size_t value_len_; +}; + +} // namespace google_breakpad + +#endif // CLIENT_LINUX_MINIDUMP_WRITER_PROC_CPUINFO_READER_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/proc_cpuinfo_reader_unittest.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/proc_cpuinfo_reader_unittest.cc new file mode 100644 index 000000000..6037c7e66 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/proc_cpuinfo_reader_unittest.cc @@ -0,0 +1,199 @@ +// Copyright (c) 2013, 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 <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> + +#include "client/linux/minidump_writer/proc_cpuinfo_reader.h" +#include "breakpad_googletest_includes.h" +#include "common/linux/tests/auto_testfile.h" + +using namespace google_breakpad; + +#if !defined(__ANDROID__) +#define TEMPDIR "/tmp" +#else +#define TEMPDIR "/data/local/tmp" +#endif + + +namespace { + +typedef testing::Test ProcCpuInfoReaderTest; + +class ScopedTestFile : public AutoTestFile { +public: + explicit ScopedTestFile(const char* text) + : AutoTestFile("proc_cpuinfo_reader", text) { + } +}; + +} + +TEST(ProcCpuInfoReaderTest, EmptyFile) { + ScopedTestFile file(""); + ASSERT_TRUE(file.IsOk()); + ProcCpuInfoReader reader(file.GetFd()); + + const char *field; + ASSERT_FALSE(reader.GetNextField(&field)); +} + +TEST(ProcCpuInfoReaderTest, OneLineTerminated) { + ScopedTestFile file("foo : bar\n"); + ASSERT_TRUE(file.IsOk()); + ProcCpuInfoReader reader(file.GetFd()); + + const char *field; + ASSERT_TRUE(reader.GetNextField(&field)); + ASSERT_STREQ("foo", field); + ASSERT_STREQ("bar", reader.GetValue()); + + ASSERT_FALSE(reader.GetNextField(&field)); +} + +TEST(ProcCpuInfoReaderTest, OneLine) { + ScopedTestFile file("foo : bar"); + ASSERT_TRUE(file.IsOk()); + ProcCpuInfoReader reader(file.GetFd()); + + const char *field; + size_t value_len; + ASSERT_TRUE(reader.GetNextField(&field)); + ASSERT_STREQ("foo", field); + ASSERT_STREQ("bar", reader.GetValueAndLen(&value_len)); + ASSERT_EQ(3U, value_len); + + ASSERT_FALSE(reader.GetNextField(&field)); +} + +TEST(ProcCpuInfoReaderTest, TwoLinesTerminated) { + ScopedTestFile file("foo : bar\nzoo : tut\n"); + ASSERT_TRUE(file.IsOk()); + ProcCpuInfoReader reader(file.GetFd()); + + const char* field; + ASSERT_TRUE(reader.GetNextField(&field)); + ASSERT_STREQ("foo", field); + ASSERT_STREQ("bar", reader.GetValue()); + + ASSERT_TRUE(reader.GetNextField(&field)); + ASSERT_STREQ("zoo", field); + ASSERT_STREQ("tut", reader.GetValue()); + + ASSERT_FALSE(reader.GetNextField(&field)); +} + +TEST(ProcCpuInfoReaderTest, SkipMalformedLine) { + ScopedTestFile file("this line should have a column\nfoo : bar\n"); + ASSERT_TRUE(file.IsOk()); + ProcCpuInfoReader reader(file.GetFd()); + + const char* field; + ASSERT_TRUE(reader.GetNextField(&field)); + ASSERT_STREQ("foo", field); + ASSERT_STREQ("bar", reader.GetValue()); + + ASSERT_FALSE(reader.GetNextField(&field)); +} + +TEST(ProcCpuInfoReaderTest, SkipOneEmptyLine) { + ScopedTestFile file("\n\nfoo : bar\n"); + ASSERT_TRUE(file.IsOk()); + ProcCpuInfoReader reader(file.GetFd()); + + const char* field; + ASSERT_TRUE(reader.GetNextField(&field)); + ASSERT_STREQ("foo", field); + ASSERT_STREQ("bar", reader.GetValue()); + + ASSERT_FALSE(reader.GetNextField(&field)); +} + +TEST(ProcCpuInfoReaderTest, SkipEmptyField) { + ScopedTestFile file(" : bar\nzoo : tut\n"); + ASSERT_TRUE(file.IsOk()); + ProcCpuInfoReader reader(file.GetFd()); + + const char* field; + ASSERT_TRUE(reader.GetNextField(&field)); + ASSERT_STREQ("zoo", field); + ASSERT_STREQ("tut", reader.GetValue()); + + ASSERT_FALSE(reader.GetNextField(&field)); +} + +TEST(ProcCpuInfoReaderTest, SkipTwoEmptyLines) { + ScopedTestFile file("foo : bar\n\n\nfoo : bar\n"); + ASSERT_TRUE(file.IsOk()); + ProcCpuInfoReader reader(file.GetFd()); + + const char* field; + ASSERT_TRUE(reader.GetNextField(&field)); + ASSERT_STREQ("foo", field); + ASSERT_STREQ("bar", reader.GetValue()); + + ASSERT_TRUE(reader.GetNextField(&field)); + ASSERT_STREQ("foo", field); + ASSERT_STREQ("bar", reader.GetValue()); + + ASSERT_FALSE(reader.GetNextField(&field)); +} + +TEST(ProcCpuInfoReaderTest, FieldWithSpaces) { + ScopedTestFile file("foo bar : zoo\n"); + ASSERT_TRUE(file.IsOk()); + ProcCpuInfoReader reader(file.GetFd()); + + const char* field; + ASSERT_TRUE(reader.GetNextField(&field)); + ASSERT_STREQ("foo bar", field); + ASSERT_STREQ("zoo", reader.GetValue()); + + ASSERT_FALSE(reader.GetNextField(&field)); +} + +TEST(ProcCpuInfoReaderTest, EmptyValue) { + ScopedTestFile file("foo :\n"); + ASSERT_TRUE(file.IsOk()); + ProcCpuInfoReader reader(file.GetFd()); + + const char* field; + ASSERT_TRUE(reader.GetNextField(&field)); + ASSERT_STREQ("foo", field); + size_t value_len; + ASSERT_STREQ("", reader.GetValueAndLen(&value_len)); + ASSERT_EQ(0U, value_len); + + ASSERT_FALSE(reader.GetNextField(&field)); +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/moz.build b/toolkit/crashreporter/google-breakpad/src/client/linux/moz.build new file mode 100644 index 000000000..71a438940 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/moz.build @@ -0,0 +1,38 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES += [ + 'crash_generation/crash_generation_client.cc', + 'crash_generation/crash_generation_server.cc', + 'dump_writer_common/thread_info.cc', + 'dump_writer_common/ucontext_reader.cc', + 'handler/exception_handler.cc', + 'handler/minidump_descriptor.cc', + 'log/log.cc', + 'microdump_writer/microdump_writer.cc', + 'minidump_writer/linux_dumper.cc', + 'minidump_writer/linux_ptrace_dumper.cc', + 'minidump_writer/minidump_writer.cc', +] + +if CONFIG['OS_TARGET'] == 'Android': + LOCAL_INCLUDES += [ + '/toolkit/crashreporter/google-breakpad/src/common/android/include', + ] + +# We allow warnings for third-party code that can be updated from upstream. +ALLOW_COMPILER_WARNINGS = True + +FINAL_LIBRARY = 'xul' + +if CONFIG['OS_TARGET'] == 'Android' and CONFIG['CPU_ARCH'] == 'x86': + # The NDK's user.h defines this struct with a different name. + DEFINES['user_fpxregs_struct'] = 'user_fxsr_struct' + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': + DEFINES['getcontext'] = 'breakpad_getcontext' + +include('/toolkit/crashreporter/crashreporter.mozbuild') diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/sender/google_crash_report_sender.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/sender/google_crash_report_sender.cc new file mode 100644 index 000000000..ec6c06e87 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/sender/google_crash_report_sender.cc @@ -0,0 +1,104 @@ +// Copyright (c) 2009, 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 "common/linux/google_crashdump_uploader.h" +#include "third_party/linux/include/gflags/gflags.h" +#include <string> +#include <iostream> + +#include "common/using_std_string.h" + +DEFINE_string(crash_server, "https://clients2.google.com/cr", + "The crash server to upload minidumps to."); +DEFINE_string(product_name, "", + "The product name that the minidump corresponds to."); +DEFINE_string(product_version, "", + "The version of the product that produced the minidump."); +DEFINE_string(client_id, "", + "The client GUID"); +DEFINE_string(minidump_path, "", + "The path of the minidump file."); +DEFINE_string(ptime, "", + "The process uptime in milliseconds."); +DEFINE_string(ctime, "", + "The cumulative process uptime in milliseconds."); +DEFINE_string(email, "", + "The user's email address."); +DEFINE_string(comments, "", + "Extra user comments"); +DEFINE_string(proxy_host, "", + "Proxy host"); +DEFINE_string(proxy_userpasswd, "", + "Proxy username/password in user:pass format."); + + +bool CheckForRequiredFlagsOrDie() { + string error_text = ""; + if (FLAGS_product_name.empty()) { + error_text.append("\nProduct name must be specified."); + } + + if (FLAGS_product_version.empty()) { + error_text.append("\nProduct version must be specified."); + } + + if (FLAGS_client_id.empty()) { + error_text.append("\nClient ID must be specified."); + } + + if (FLAGS_minidump_path.empty()) { + error_text.append("\nMinidump pathname must be specified."); + } + + if (!error_text.empty()) { + std::cout << error_text; + return false; + } + return true; +} + +int main(int argc, char *argv[]) { + google::InitGoogleLogging(argv[0]); + google::ParseCommandLineFlags(&argc, &argv, true); + if (!CheckForRequiredFlagsOrDie()) { + return 1; + } + google_breakpad::GoogleCrashdumpUploader g(FLAGS_product_name, + FLAGS_product_version, + FLAGS_client_id, + FLAGS_ptime, + FLAGS_ctime, + FLAGS_email, + FLAGS_comments, + FLAGS_minidump_path, + FLAGS_crash_server, + FLAGS_proxy_host, + FLAGS_proxy_userpasswd); + g.Upload(NULL, NULL, NULL); +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/Breakpad.xcodeproj/project.pbxproj b/toolkit/crashreporter/google-breakpad/src/client/mac/Breakpad.xcodeproj/project.pbxproj new file mode 100644 index 000000000..1a93ce6dd --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/Breakpad.xcodeproj/project.pbxproj @@ -0,0 +1,2788 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 45; + objects = { + +/* Begin PBXAggregateTarget section */ + F94585840F782326009A47BF /* All */ = { + isa = PBXAggregateTarget; + buildConfigurationList = F94585930F78235C009A47BF /* Build configuration list for PBXAggregateTarget "All" */; + buildPhases = ( + ); + dependencies = ( + F94585880F78232B009A47BF /* PBXTargetDependency */, + F945858A0F78232E009A47BF /* PBXTargetDependency */, + F945858C0F782330009A47BF /* PBXTargetDependency */, + F945858E0F782333009A47BF /* PBXTargetDependency */, + F94585900F782336009A47BF /* PBXTargetDependency */, + F93DE3A70F830D1D00608B94 /* PBXTargetDependency */, + F95BB8B3101F94D300AA053B /* PBXTargetDependency */, + F95BB8B5101F94D300AA053B /* PBXTargetDependency */, + F95BB8B7101F94D300AA053B /* PBXTargetDependency */, + 8B31023911F0CF0600FCF3E4 /* PBXTargetDependency */, + 8B31051711F1010E00FCF3E4 /* PBXTargetDependency */, + 8B31051911F1010E00FCF3E4 /* PBXTargetDependency */, + 8B31051B11F1010E00FCF3E4 /* PBXTargetDependency */, + 8B31051D11F1010E00FCF3E4 /* PBXTargetDependency */, + 8B31051F11F1010E00FCF3E4 /* PBXTargetDependency */, + ); + name = All; + productName = All; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 162F64F2161C577500CD68D5 /* arch_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = 162F64F0161C577500CD68D5 /* arch_utilities.cc */; }; + 162F64F3161C577500CD68D5 /* arch_utilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 162F64F1161C577500CD68D5 /* arch_utilities.h */; }; + 162F64F4161C579B00CD68D5 /* arch_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = 162F64F0161C577500CD68D5 /* arch_utilities.cc */; }; + 162F64F5161C579B00CD68D5 /* arch_utilities.h in Sources */ = {isa = PBXBuildFile; fileRef = 162F64F1161C577500CD68D5 /* arch_utilities.h */; }; + 163201D61443019E00C4DBF5 /* ConfigFile.h in Headers */ = {isa = PBXBuildFile; fileRef = 163201D41443019E00C4DBF5 /* ConfigFile.h */; }; + 163201D71443019E00C4DBF5 /* ConfigFile.mm in Sources */ = {isa = PBXBuildFile; fileRef = 163201D51443019E00C4DBF5 /* ConfigFile.mm */; }; + 163201E31443029300C4DBF5 /* ConfigFile.mm in Sources */ = {isa = PBXBuildFile; fileRef = 163201D51443019E00C4DBF5 /* ConfigFile.mm */; }; + 16C7C918147D45AE00776EAD /* BreakpadDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 16C7C917147D45AE00776EAD /* BreakpadDefines.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 16E02DB8147410F0008C604D /* uploader.mm in Sources */ = {isa = PBXBuildFile; fileRef = 16E02DB4147410D4008C604D /* uploader.mm */; }; + 1EEEB6231720829E00F7E689 /* simple_string_dictionary.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1EEEB6211720829E00F7E689 /* simple_string_dictionary.cc */; }; + 1EEEB6241720829E00F7E689 /* simple_string_dictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 1EEEB6221720829E00F7E689 /* simple_string_dictionary.h */; }; + 1EEEB6271720831E00F7E689 /* BreakpadFramework_Test.mm in Sources */ = {isa = PBXBuildFile; fileRef = F91AF5CF0FD60393009D8BE2 /* BreakpadFramework_Test.mm */; }; + 1EEEB62A1720859200F7E689 /* simple_string_dictionary_unittest.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1EEEB6251720830600F7E689 /* simple_string_dictionary_unittest.cc */; }; + 1EEEB62B1720868C00F7E689 /* simple_string_dictionary.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1EEEB6211720829E00F7E689 /* simple_string_dictionary.cc */; }; + 3329D4ED0FA16D820007BBC5 /* Breakpad.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3329D4EC0FA16D820007BBC5 /* Breakpad.xib */; }; + 33880C800F9E097100817F82 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 33880C7E0F9E097100817F82 /* InfoPlist.strings */; }; + 4084699D0F5D9CF900FDCA37 /* crash_report_sender.icns in Resources */ = {isa = PBXBuildFile; fileRef = 4084699C0F5D9CF900FDCA37 /* crash_report_sender.icns */; }; + 4D61A25F14F43CFC002D5862 /* bootstrap_compat.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4D61A25D14F43CFC002D5862 /* bootstrap_compat.cc */; }; + 4D61A26B14F43D3C002D5862 /* bootstrap_compat.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4D61A25D14F43CFC002D5862 /* bootstrap_compat.cc */; }; + 4D61A26C14F43D42002D5862 /* bootstrap_compat.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4D61A25D14F43CFC002D5862 /* bootstrap_compat.cc */; }; + 4D61A26D14F43D43002D5862 /* bootstrap_compat.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4D61A25D14F43CFC002D5862 /* bootstrap_compat.cc */; }; + 4D61A26E14F43D45002D5862 /* bootstrap_compat.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4D61A25D14F43CFC002D5862 /* bootstrap_compat.cc */; }; + 4D61A26F14F43D48002D5862 /* bootstrap_compat.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4D61A25D14F43CFC002D5862 /* bootstrap_compat.cc */; }; + 4D72CA0E13DFAD5C006CABE3 /* md5.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4D72CA0D13DFAD5C006CABE3 /* md5.cc */; }; + 4D72CA2513DFAE1C006CABE3 /* md5.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4D72CA0D13DFAD5C006CABE3 /* md5.cc */; }; + 4D72CA2F13DFAE65006CABE3 /* md5.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4D72CA0D13DFAD5C006CABE3 /* md5.cc */; }; + 4D72CA3813DFAE91006CABE3 /* md5.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4D72CA0D13DFAD5C006CABE3 /* md5.cc */; }; + 4D72CA3913DFAE92006CABE3 /* md5.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4D72CA0D13DFAD5C006CABE3 /* md5.cc */; }; + 4DBE49A6134A4F200072546A /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4DBE4769134A4F080072546A /* CoreServices.framework */; }; + 4DBE49A7134A4F280072546A /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4DBE4769134A4F080072546A /* CoreServices.framework */; }; + 4DBE49A8134A4F380072546A /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4DBE4769134A4F080072546A /* CoreServices.framework */; }; + 4DBE49A9134A4F460072546A /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4DBE4769134A4F080072546A /* CoreServices.framework */; }; + 8B3101C611F0CD9F00FCF3E4 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D6A5FE840307C02AAC07 /* AppKit.framework */; }; + 8B3101C711F0CD9F00FCF3E4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; }; + 8B3101CA11F0CDB000FCF3E4 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D6A5FE840307C02AAC07 /* AppKit.framework */; }; + 8B3101CB11F0CDB000FCF3E4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; }; + 8B3101EA11F0CDE300FCF3E4 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B3101E911F0CDE300FCF3E4 /* SenTestingKit.framework */; }; + 8B31029411F0D54300FCF3E4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; }; + 8B3102E611F0D74C00FCF3E4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; }; + 8B3102EB11F0D78000FCF3E4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; }; + 8B31FC8211EFD2B800FCF3E4 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; }; + 8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; }; + D23F4B2E12A7E13200686C8D /* minidump_generator_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D23F4B2C12A7E13200686C8D /* minidump_generator_test.cc */; }; + D23F4B3312A7E17700686C8D /* libgtest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D2F9A41512131EF0002747C1 /* libgtest.a */; }; + D23F4BB112A868CB00686C8D /* minidump_generator_test_helper.cc in Sources */ = {isa = PBXBuildFile; fileRef = D23F4B9A12A8688800686C8D /* minidump_generator_test_helper.cc */; }; + D23F4BB812A868F700686C8D /* MachIPC.mm in Sources */ = {isa = PBXBuildFile; fileRef = F92C53790ECCE635009BE4BA /* MachIPC.mm */; }; + D244536A12426F00009BBCE0 /* logging.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244535112426EBB009BBCE0 /* logging.cc */; }; + D244536B12426F00009BBCE0 /* minidump.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244535212426EBB009BBCE0 /* minidump.cc */; }; + D244536C12426F00009BBCE0 /* pathname_stripper.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244535312426EBB009BBCE0 /* pathname_stripper.cc */; }; + D244536D12426F00009BBCE0 /* basic_code_modules.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244534F12426E98009BBCE0 /* basic_code_modules.cc */; }; + D244540B12439BA0009BBCE0 /* memory_unittest.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244540A12439BA0009BBCE0 /* memory_unittest.cc */; }; + D246417012BAA40E005170D0 /* exception_handler.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C536D0ECCE3FD009BE4BA /* exception_handler.cc */; }; + D246417112BAA41C005170D0 /* crash_generation_client.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F9A4C6121336C7002747C1 /* crash_generation_client.cc */; }; + D246417512BAA438005170D0 /* minidump_generator.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C536F0ECCE3FD009BE4BA /* minidump_generator.cc */; }; + D246417612BAA43F005170D0 /* dynamic_images.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C536B0ECCE3FD009BE4BA /* dynamic_images.cc */; }; + D246417712BAA444005170D0 /* breakpad_nlist_64.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53690ECCE3FD009BE4BA /* breakpad_nlist_64.cc */; }; + D246418412BAA4BA005170D0 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; }; + D246418812BAA4E3005170D0 /* string_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53820ECCE635009BE4BA /* string_utilities.cc */; }; + D246418C12BAA508005170D0 /* minidump_file_writer.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C538F0ECCE70A009BE4BA /* minidump_file_writer.cc */; }; + D246419012BAA52A005170D0 /* string_conversion.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53850ECCE6AD009BE4BA /* string_conversion.cc */; }; + D246419112BAA52F005170D0 /* convert_UTF.c in Sources */ = {isa = PBXBuildFile; fileRef = F92C53870ECCE6C0009BE4BA /* convert_UTF.c */; }; + D246419512BAA54C005170D0 /* file_id.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53740ECCE635009BE4BA /* file_id.cc */; }; + D246419612BAA55A005170D0 /* macho_id.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C537A0ECCE635009BE4BA /* macho_id.cc */; }; + D24641A012BAA67F005170D0 /* macho_walker.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C537E0ECCE635009BE4BA /* macho_walker.cc */; }; + D24641AF12BAA82D005170D0 /* macho_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C537C0ECCE635009BE4BA /* macho_utilities.cc */; }; + D24641EC12BAC6FB005170D0 /* logging.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244535112426EBB009BBCE0 /* logging.cc */; }; + D24641ED12BAC6FB005170D0 /* minidump.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244535212426EBB009BBCE0 /* minidump.cc */; }; + D24641EE12BAC6FB005170D0 /* pathname_stripper.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244535312426EBB009BBCE0 /* pathname_stripper.cc */; }; + D24641EF12BAC6FB005170D0 /* basic_code_modules.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244534F12426E98009BBCE0 /* basic_code_modules.cc */; }; + D24BBBFD121050F000F3D417 /* breakpadUtilities.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F92C563C0ECD10B3009BE4BA /* breakpadUtilities.dylib */; }; + D24BBD291211EDB100F3D417 /* MachIPC.mm in Sources */ = {isa = PBXBuildFile; fileRef = F92C53790ECCE635009BE4BA /* MachIPC.mm */; }; + D24BBD321212CACF00F3D417 /* MachIPC.mm in Sources */ = {isa = PBXBuildFile; fileRef = F92C53790ECCE635009BE4BA /* MachIPC.mm */; }; + D2A5DD301188633800081F03 /* breakpad_nlist_64.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53690ECCE3FD009BE4BA /* breakpad_nlist_64.cc */; }; + D2A5DD401188640400081F03 /* breakpad_nlist_64.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53690ECCE3FD009BE4BA /* breakpad_nlist_64.cc */; }; + D2A5DD411188642E00081F03 /* breakpad_nlist_64.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53690ECCE3FD009BE4BA /* breakpad_nlist_64.cc */; }; + D2C1DBE412AFC270006917BD /* logging.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244535112426EBB009BBCE0 /* logging.cc */; }; + D2C1DBE512AFC270006917BD /* minidump.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244535212426EBB009BBCE0 /* minidump.cc */; }; + D2C1DBE612AFC270006917BD /* pathname_stripper.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244535312426EBB009BBCE0 /* pathname_stripper.cc */; }; + D2C1DBE712AFC270006917BD /* basic_code_modules.cc in Sources */ = {isa = PBXBuildFile; fileRef = D244534F12426E98009BBCE0 /* basic_code_modules.cc */; }; + D2F9A3D51212F87C002747C1 /* exception_handler_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F9A3D41212F87C002747C1 /* exception_handler_test.cc */; }; + D2F9A43D12131F55002747C1 /* gmock-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F9A43C12131F55002747C1 /* gmock-all.cc */; }; + D2F9A44012131F65002747C1 /* gtest_main.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F9A43E12131F65002747C1 /* gtest_main.cc */; }; + D2F9A44112131F65002747C1 /* gtest-all.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F9A43F12131F65002747C1 /* gtest-all.cc */; }; + D2F9A44412131F84002747C1 /* libgtest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D2F9A41512131EF0002747C1 /* libgtest.a */; }; + D2F9A4C9121336C7002747C1 /* client_info.h in Headers */ = {isa = PBXBuildFile; fileRef = D2F9A4C4121336C7002747C1 /* client_info.h */; }; + D2F9A4CA121336C7002747C1 /* crash_generation_client.h in Headers */ = {isa = PBXBuildFile; fileRef = D2F9A4C5121336C7002747C1 /* crash_generation_client.h */; }; + D2F9A4CB121336C7002747C1 /* crash_generation_client.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F9A4C6121336C7002747C1 /* crash_generation_client.cc */; }; + D2F9A4CC121336C7002747C1 /* crash_generation_server.h in Headers */ = {isa = PBXBuildFile; fileRef = D2F9A4C7121336C7002747C1 /* crash_generation_server.h */; }; + D2F9A4CD121336C7002747C1 /* crash_generation_server.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F9A4C8121336C7002747C1 /* crash_generation_server.cc */; }; + D2F9A4DF12133AD9002747C1 /* crash_generation_client.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F9A4C6121336C7002747C1 /* crash_generation_client.cc */; }; + D2F9A4E012133AD9002747C1 /* crash_generation_server.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F9A4C8121336C7002747C1 /* crash_generation_server.cc */; }; + D2F9A4E112133AE2002747C1 /* crash_generation_client.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F9A4C6121336C7002747C1 /* crash_generation_client.cc */; }; + D2F9A4E212133AE2002747C1 /* crash_generation_server.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F9A4C8121336C7002747C1 /* crash_generation_server.cc */; }; + D2F9A52E121383A1002747C1 /* crash_generation_client.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F9A4C6121336C7002747C1 /* crash_generation_client.cc */; }; + D2F9A52F121383A1002747C1 /* crash_generation_server.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F9A4C8121336C7002747C1 /* crash_generation_server.cc */; }; + D2F9A530121383A1002747C1 /* MachIPC.mm in Sources */ = {isa = PBXBuildFile; fileRef = F92C53790ECCE635009BE4BA /* MachIPC.mm */; }; + D2F9A531121383A1002747C1 /* breakpad_nlist_64.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53690ECCE3FD009BE4BA /* breakpad_nlist_64.cc */; }; + D2F9A532121383A1002747C1 /* dynamic_images.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C536B0ECCE3FD009BE4BA /* dynamic_images.cc */; }; + D2F9A533121383A1002747C1 /* exception_handler.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C536D0ECCE3FD009BE4BA /* exception_handler.cc */; }; + D2F9A534121383A1002747C1 /* minidump_generator.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C536F0ECCE3FD009BE4BA /* minidump_generator.cc */; }; + D2F9A535121383A1002747C1 /* minidump_file_writer.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C538F0ECCE70A009BE4BA /* minidump_file_writer.cc */; }; + D2F9A536121383A1002747C1 /* convert_UTF.c in Sources */ = {isa = PBXBuildFile; fileRef = F92C53870ECCE6C0009BE4BA /* convert_UTF.c */; }; + D2F9A537121383A1002747C1 /* string_conversion.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53850ECCE6AD009BE4BA /* string_conversion.cc */; }; + D2F9A538121383A1002747C1 /* file_id.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53740ECCE635009BE4BA /* file_id.cc */; }; + D2F9A539121383A1002747C1 /* macho_id.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C537A0ECCE635009BE4BA /* macho_id.cc */; }; + D2F9A53A121383A1002747C1 /* macho_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C537C0ECCE635009BE4BA /* macho_utilities.cc */; }; + D2F9A53B121383A1002747C1 /* macho_walker.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C537E0ECCE635009BE4BA /* macho_walker.cc */; }; + D2F9A53C121383A1002747C1 /* string_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53820ECCE635009BE4BA /* string_utilities.cc */; }; + D2F9A53F121383A1002747C1 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; }; + D2F9A541121383A1002747C1 /* libgtest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D2F9A41512131EF0002747C1 /* libgtest.a */; }; + D2F9A553121383DC002747C1 /* crash_generation_server_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F9A4CE121336F7002747C1 /* crash_generation_server_test.cc */; }; + F4DAB1DD19F1027100A5A838 /* launch_reporter.cc in Sources */ = {isa = PBXBuildFile; fileRef = F4DAB1DB19F1027100A5A838 /* launch_reporter.cc */; }; + F4DAB1DE19F1027100A5A838 /* launch_reporter.h in Headers */ = {isa = PBXBuildFile; fileRef = F4DAB1DC19F1027100A5A838 /* launch_reporter.h */; }; + F4F916B619F10FFC00B83BE4 /* launch_reporter.cc in Sources */ = {isa = PBXBuildFile; fileRef = F4DAB1DB19F1027100A5A838 /* launch_reporter.cc */; }; + F91AF6210FD60784009D8BE2 /* Breakpad.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DC2EF5B0486A6940098B216 /* Breakpad.framework */; }; + F9286B3A0F7EB25800A4DCC8 /* InspectorMain.mm in Sources */ = {isa = PBXBuildFile; fileRef = F9286B390F7EB25800A4DCC8 /* InspectorMain.mm */; }; + F92C53B80ECCE7B3009BE4BA /* Inspector.mm in Sources */ = {isa = PBXBuildFile; fileRef = F92C53B70ECCE7B3009BE4BA /* Inspector.mm */; }; + F92C554C0ECCF534009BE4BA /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; }; + F92C55D00ECD0064009BE4BA /* Breakpad.h in Headers */ = {isa = PBXBuildFile; fileRef = F92C55CE0ECD0064009BE4BA /* Breakpad.h */; settings = {ATTRIBUTES = (Public, ); }; }; + F92C55D10ECD0064009BE4BA /* Breakpad.mm in Sources */ = {isa = PBXBuildFile; fileRef = F92C55CF0ECD0064009BE4BA /* Breakpad.mm */; }; + F92C56330ECD0DF1009BE4BA /* OnDemandServer.h in Headers */ = {isa = PBXBuildFile; fileRef = F92C56310ECD0DF1009BE4BA /* OnDemandServer.h */; }; + F92C56340ECD0DF1009BE4BA /* OnDemandServer.mm in Sources */ = {isa = PBXBuildFile; fileRef = F92C56320ECD0DF1009BE4BA /* OnDemandServer.mm */; }; + F92C563F0ECD10CA009BE4BA /* convert_UTF.c in Sources */ = {isa = PBXBuildFile; fileRef = F92C53870ECCE6C0009BE4BA /* convert_UTF.c */; }; + F92C56400ECD10CA009BE4BA /* dynamic_images.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C536B0ECCE3FD009BE4BA /* dynamic_images.cc */; }; + F92C56410ECD10CA009BE4BA /* file_id.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53740ECCE635009BE4BA /* file_id.cc */; }; + F92C56420ECD10CA009BE4BA /* macho_id.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C537A0ECCE635009BE4BA /* macho_id.cc */; }; + F92C56430ECD10CA009BE4BA /* macho_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C537C0ECCE635009BE4BA /* macho_utilities.cc */; }; + F92C56440ECD10CA009BE4BA /* macho_walker.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C537E0ECCE635009BE4BA /* macho_walker.cc */; }; + F92C56450ECD10CA009BE4BA /* MachIPC.mm in Sources */ = {isa = PBXBuildFile; fileRef = F92C53790ECCE635009BE4BA /* MachIPC.mm */; }; + F92C56460ECD10CA009BE4BA /* minidump_file_writer.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C538F0ECCE70A009BE4BA /* minidump_file_writer.cc */; }; + F92C56470ECD10CA009BE4BA /* minidump_generator.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C536F0ECCE3FD009BE4BA /* minidump_generator.cc */; }; + F92C56490ECD10CA009BE4BA /* string_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53820ECCE635009BE4BA /* string_utilities.cc */; }; + F92C564A0ECD10CA009BE4BA /* string_conversion.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53850ECCE6AD009BE4BA /* string_conversion.cc */; }; + F92C564C0ECD10DD009BE4BA /* breakpadUtilities.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F92C563C0ECD10B3009BE4BA /* breakpadUtilities.dylib */; }; + F92C56570ECD113E009BE4BA /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F92C554A0ECCF530009BE4BA /* Carbon.framework */; }; + F92C565C0ECD1158009BE4BA /* breakpadUtilities.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = F92C563C0ECD10B3009BE4BA /* breakpadUtilities.dylib */; }; + F92C565F0ECD116B009BE4BA /* protected_memory_allocator.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53720ECCE3FD009BE4BA /* protected_memory_allocator.cc */; }; + F92C56630ECD1179009BE4BA /* exception_handler.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C536D0ECCE3FD009BE4BA /* exception_handler.cc */; }; + F92C56650ECD1185009BE4BA /* breakpadUtilities.dylib in Resources */ = {isa = PBXBuildFile; fileRef = F92C563C0ECD10B3009BE4BA /* breakpadUtilities.dylib */; }; + F92C568A0ECD15F9009BE4BA /* Inspector in Resources */ = {isa = PBXBuildFile; fileRef = F92C53540ECCE349009BE4BA /* Inspector */; }; + F92C56A90ECE04C5009BE4BA /* crash_report_sender.m in Sources */ = {isa = PBXBuildFile; fileRef = F92C56A80ECE04C5009BE4BA /* crash_report_sender.m */; }; + F93803CD0F8083B7004D428B /* dynamic_images.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C536B0ECCE3FD009BE4BA /* dynamic_images.cc */; }; + F93803CE0F8083B7004D428B /* exception_handler.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C536D0ECCE3FD009BE4BA /* exception_handler.cc */; }; + F93803CF0F8083B7004D428B /* minidump_generator.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C536F0ECCE3FD009BE4BA /* minidump_generator.cc */; }; + F93803D00F8083B7004D428B /* minidump_file_writer.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C538F0ECCE70A009BE4BA /* minidump_file_writer.cc */; }; + F93803D10F8083B7004D428B /* convert_UTF.c in Sources */ = {isa = PBXBuildFile; fileRef = F92C53870ECCE6C0009BE4BA /* convert_UTF.c */; }; + F93803D20F8083B7004D428B /* string_conversion.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53850ECCE6AD009BE4BA /* string_conversion.cc */; }; + F93803D30F8083B7004D428B /* file_id.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53740ECCE635009BE4BA /* file_id.cc */; }; + F93803D40F8083B7004D428B /* macho_id.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C537A0ECCE635009BE4BA /* macho_id.cc */; }; + F93803D50F8083B7004D428B /* macho_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C537C0ECCE635009BE4BA /* macho_utilities.cc */; }; + F93803D60F8083B7004D428B /* macho_walker.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C537E0ECCE635009BE4BA /* macho_walker.cc */; }; + F93803D70F8083B7004D428B /* string_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53820ECCE635009BE4BA /* string_utilities.cc */; }; + F93DE2D80F82A70E00608B94 /* minidump_file_writer_unittest.cc in Sources */ = {isa = PBXBuildFile; fileRef = F93DE2D70F82A70E00608B94 /* minidump_file_writer_unittest.cc */; }; + F93DE2D90F82A73500608B94 /* minidump_file_writer.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C538F0ECCE70A009BE4BA /* minidump_file_writer.cc */; }; + F93DE2DA0F82A73500608B94 /* convert_UTF.c in Sources */ = {isa = PBXBuildFile; fileRef = F92C53870ECCE6C0009BE4BA /* convert_UTF.c */; }; + F93DE2DB0F82A73500608B94 /* string_conversion.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53850ECCE6AD009BE4BA /* string_conversion.cc */; }; + F93DE3350F82C66B00608B94 /* dynamic_images.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C536B0ECCE3FD009BE4BA /* dynamic_images.cc */; }; + F93DE3360F82C66B00608B94 /* exception_handler.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C536D0ECCE3FD009BE4BA /* exception_handler.cc */; }; + F93DE3370F82C66B00608B94 /* minidump_generator.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C536F0ECCE3FD009BE4BA /* minidump_generator.cc */; }; + F93DE3380F82C66B00608B94 /* minidump_file_writer.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C538F0ECCE70A009BE4BA /* minidump_file_writer.cc */; }; + F93DE3390F82C66B00608B94 /* convert_UTF.c in Sources */ = {isa = PBXBuildFile; fileRef = F92C53870ECCE6C0009BE4BA /* convert_UTF.c */; }; + F93DE33A0F82C66B00608B94 /* string_conversion.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53850ECCE6AD009BE4BA /* string_conversion.cc */; }; + F93DE33B0F82C66B00608B94 /* file_id.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53740ECCE635009BE4BA /* file_id.cc */; }; + F93DE33C0F82C66B00608B94 /* macho_id.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C537A0ECCE635009BE4BA /* macho_id.cc */; }; + F93DE33D0F82C66B00608B94 /* macho_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C537C0ECCE635009BE4BA /* macho_utilities.cc */; }; + F93DE33E0F82C66B00608B94 /* macho_walker.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C537E0ECCE635009BE4BA /* macho_walker.cc */; }; + F93DE33F0F82C66B00608B94 /* string_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53820ECCE635009BE4BA /* string_utilities.cc */; }; + F945849E0F280E3C009A47BF /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F945849C0F280E3C009A47BF /* Localizable.strings */; }; + F9B630A0100FF96B00D0F4AC /* goArrow.png in Resources */ = {isa = PBXBuildFile; fileRef = F9B6309F100FF96B00D0F4AC /* goArrow.png */; }; + F9C44DB20EF07288003AEBAA /* Controller.m in Sources */ = {isa = PBXBuildFile; fileRef = F9C44DAC0EF07288003AEBAA /* Controller.m */; }; + F9C44DB30EF07288003AEBAA /* crashduringload in Resources */ = {isa = PBXBuildFile; fileRef = F9C44DAD0EF07288003AEBAA /* crashduringload */; }; + F9C44DB40EF07288003AEBAA /* crashInMain in Resources */ = {isa = PBXBuildFile; fileRef = F9C44DAE0EF07288003AEBAA /* crashInMain */; }; + F9C44DB60EF07288003AEBAA /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = F9C44DB00EF07288003AEBAA /* main.m */; }; + F9C44DB70EF07288003AEBAA /* TestClass.mm in Sources */ = {isa = PBXBuildFile; fileRef = F9C44DB10EF07288003AEBAA /* TestClass.mm */; }; + F9C44DBC0EF072A0003AEBAA /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = F9C44DB80EF072A0003AEBAA /* InfoPlist.strings */; }; + F9C44DBD0EF072A0003AEBAA /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = F9C44DBA0EF072A0003AEBAA /* MainMenu.xib */; }; + F9C44E000EF077CD003AEBAA /* Breakpad.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DC2EF5B0486A6940098B216 /* Breakpad.framework */; }; + F9C44E3C0EF08B12003AEBAA /* Breakpad.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 8DC2EF5B0486A6940098B216 /* Breakpad.framework */; }; + F9C44E980EF09F56003AEBAA /* crash_report_sender.app in Resources */ = {isa = PBXBuildFile; fileRef = F92C56A00ECE04A7009BE4BA /* crash_report_sender.app */; }; + F9C44EA20EF09F93003AEBAA /* HTTPMultipartUpload.m in Sources */ = {isa = PBXBuildFile; fileRef = F92C53770ECCE635009BE4BA /* HTTPMultipartUpload.m */; }; + F9C44EE50EF0A006003AEBAA /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9C44EE40EF0A006003AEBAA /* SystemConfiguration.framework */; }; + F9C44EE90EF0A3C1003AEBAA /* GTMLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = F9C44EE80EF0A3C1003AEBAA /* GTMLogger.m */; }; + F9C77E130F7DDF810045F7DB /* GTMSenTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = F9C77E120F7DDF810045F7DB /* GTMSenTestCase.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 8B31023811F0CF0600FCF3E4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = B88FAFC9116BDCAD00407530; + remoteInfo = all_unittests; + }; + 8B31051611F1010E00FCF3E4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F93803BD0F80820F004D428B; + remoteInfo = generator_test; + }; + 8B31051811F1010E00FCF3E4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F93DE2D00F82A67300608B94; + remoteInfo = minidump_file_writer_unittest; + }; + 8B31051A11F1010E00FCF3E4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F93DE32B0F82C55600608B94; + remoteInfo = handler_test; + }; + 8B31051C11F1010E00FCF3E4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = B89E0E731166575200DD08C9; + remoteInfo = macho_dump; + }; + 8B31051E11F1010E00FCF3E4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F95BB894101F94C000AA053B /* symupload.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 9BD835FA0B0544950055103E; + remoteInfo = minidump_upload; + }; + 8B31F7A011EF9A8700FCF3E4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B89E0E741166575200DD08C9; + remoteInfo = macho_dump; + }; + 8B31F7A211EF9A8700FCF3E4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B88FB024116BDFFF00407530; + remoteInfo = gtestmockall; + }; + 8B31F7A411EF9A8700FCF3E4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B88FB14B116CF4A700407530; + remoteInfo = byte_cursor_unittest; + }; + 8B31F7A611EF9A8700FCF3E4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B89E0E9511665A6400DD08C9; + remoteInfo = macho_reader_unittest; + }; + 8B31F7A811EF9A8700FCF3E4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B88FB007116BDE8300407530; + remoteInfo = stabs_reader_unittest; + }; + 8B31F7AA11EF9A8700FCF3E4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B88FB135116CF30F00407530; + remoteInfo = bytereader_unittest; + }; + 8B31F7AC11EF9A8700FCF3E4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B88FAF2F116A591E00407530; + remoteInfo = dwarf2reader_cfi_unittest; + }; + 8B31F7AE11EF9A8700FCF3E4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B88FB0DF116CEEA800407530; + remoteInfo = dwarf2diehandler_unittest; + }; + 8B31F7B011EF9A8700FCF3E4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B88FB10A116CF07900407530; + remoteInfo = dwarf_cu_to_module_unittest; + }; + 8B31F7B211EF9A8700FCF3E4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B88FB0F2116CEF1900407530; + remoteInfo = dwarf_line_to_module_unittest; + }; + 8B31F7B411EF9A8700FCF3E4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B88FB11F116CF27F00407530; + remoteInfo = dwarf_cfi_to_module_unittest; + }; + 8B31F7B611EF9A8700FCF3E4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B84A91F4116CF784006C210E; + remoteInfo = stabs_to_module_unittest; + }; + 8B31F7B811EF9A8700FCF3E4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B88FB0B9116CEABF00407530; + remoteInfo = module_unittest; + }; + 8B31F7BA11EF9A8700FCF3E4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = D21F97D211CBA0F200239E38; + remoteInfo = test_assembler_unittest; + }; + D23F4B2F12A7E16200686C8D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D2F9A41412131EF0002747C1; + remoteInfo = gtest; + }; + D23F4BB912A8694C00686C8D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D23F4BAA12A868A500686C8D; + remoteInfo = minidump_generator_test_helper; + }; + D2F9A44212131F80002747C1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D2F9A41412131EF0002747C1; + remoteInfo = gtest; + }; + D2F9A52C121383A1002747C1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D2F9A41412131EF0002747C1; + remoteInfo = gtest; + }; + D2F9A5DE12142A6A002747C1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D2F9A52A121383A1002747C1; + remoteInfo = crash_generation_server_test; + }; + F91AF6370FD60A74009D8BE2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8DC2EF4F0486A6940098B216; + remoteInfo = Breakpad; + }; + F92C564D0ECD10E5009BE4BA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F92C563B0ECD10B3009BE4BA; + remoteInfo = breakpadUtilities; + }; + F92C56850ECD15EF009BE4BA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F92C563B0ECD10B3009BE4BA; + remoteInfo = breakpadUtilities; + }; + F92C56870ECD15F1009BE4BA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F92C53530ECCE349009BE4BA; + remoteInfo = Inspector; + }; + F93DE2FB0F82C3C600608B94 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F93803BD0F80820F004D428B; + remoteInfo = generator_test; + }; + F93DE36F0F82CC1300608B94 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F93DE32B0F82C55600608B94; + remoteInfo = handler_test; + }; + F93DE3A60F830D1D00608B94 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F9C77DD90F7DD5CF0045F7DB; + remoteInfo = UnitTests; + }; + F94585870F78232B009A47BF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8DC2EF4F0486A6940098B216; + remoteInfo = Breakpad; + }; + F94585890F78232E009A47BF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F92C53530ECCE349009BE4BA; + remoteInfo = Inspector; + }; + F945858B0F782330009A47BF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F92C563B0ECD10B3009BE4BA; + remoteInfo = breakpadUtilities; + }; + F945858D0F782333009A47BF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F92C569F0ECE04A7009BE4BA; + remoteInfo = crash_report_sender; + }; + F945858F0F782336009A47BF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F9C44DA40EF060A8003AEBAA; + remoteInfo = BreakpadTest; + }; + F95BB884101F949F00AA053B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F95BB87C101F949F00AA053B /* crash_report.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 8DD76FA10486AA7600D96B5E; + remoteInfo = crash_report; + }; + F95BB891101F94AC00AA053B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 8DD76FA10486AA7600D96B5E; + remoteInfo = dump_syms; + }; + F95BB89E101F94C000AA053B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F95BB894101F94C000AA053B /* symupload.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 8DD76FA10486AA7600D96B5E; + remoteInfo = symupload; + }; + F95BB8A0101F94C000AA053B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F95BB894101F94C000AA053B /* symupload.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 9BD835FB0B0544950055103E; + remoteInfo = minidump_upload; + }; + F95BB8B2101F94D300AA053B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = B8C5B5101166531A00D34F4E; + remoteInfo = dump_syms; + }; + F95BB8B4101F94D300AA053B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F95BB894101F94C000AA053B /* symupload.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 8DD76F960486AA7600D96B5E; + remoteInfo = symupload; + }; + F95BB8B6101F94D300AA053B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = F95BB87C101F949F00AA053B /* crash_report.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = 8DD76F960486AA7600D96B5E; + remoteInfo = crash_report; + }; + F9C44E190EF0790F003AEBAA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8DC2EF4F0486A6940098B216; + remoteInfo = Breakpad; + }; + F9C44E960EF09F4B003AEBAA /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = F92C569F0ECE04A7009BE4BA; + remoteInfo = crash_report_sender; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + F9C44E410EF08B17003AEBAA /* Copy Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + F9C44E3C0EF08B12003AEBAA /* Breakpad.framework in Copy Frameworks */, + ); + name = "Copy Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0867D69BFE84028FC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 0867D6A5FE840307C02AAC07 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; + 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; + 162F64F0161C577500CD68D5 /* arch_utilities.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = arch_utilities.cc; path = ../../common/mac/arch_utilities.cc; sourceTree = "<group>"; }; + 162F64F1161C577500CD68D5 /* arch_utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = arch_utilities.h; path = ../../common/mac/arch_utilities.h; sourceTree = "<group>"; }; + 163201D41443019E00C4DBF5 /* ConfigFile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ConfigFile.h; path = crash_generation/ConfigFile.h; sourceTree = "<group>"; }; + 163201D51443019E00C4DBF5 /* ConfigFile.mm */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; name = ConfigFile.mm; path = crash_generation/ConfigFile.mm; sourceTree = "<group>"; }; + 163202431443201300C4DBF5 /* uploader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = uploader.h; path = sender/uploader.h; sourceTree = "<group>"; }; + 16C7C917147D45AE00776EAD /* BreakpadDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BreakpadDefines.h; sourceTree = "<group>"; }; + 16E02DB4147410D4008C604D /* uploader.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = uploader.mm; path = sender/uploader.mm; sourceTree = "<group>"; }; + 1EEEB6211720829E00F7E689 /* simple_string_dictionary.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = simple_string_dictionary.cc; path = ../../common/simple_string_dictionary.cc; sourceTree = "<group>"; }; + 1EEEB6221720829E00F7E689 /* simple_string_dictionary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = simple_string_dictionary.h; path = ../../common/simple_string_dictionary.h; sourceTree = "<group>"; }; + 1EEEB6251720830600F7E689 /* simple_string_dictionary_unittest.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = simple_string_dictionary_unittest.cc; path = ../../common/simple_string_dictionary_unittest.cc; sourceTree = "<group>"; }; + 32DBCF5E0370ADEE00C91783 /* Breakpad_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Breakpad_Prefix.pch; path = Framework/Breakpad_Prefix.pch; sourceTree = "<group>"; }; + 3329D4EC0FA16D820007BBC5 /* Breakpad.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Breakpad.xib; path = sender/Breakpad.xib; sourceTree = "<group>"; }; + 33880C7F0F9E097100817F82 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = sender/English.lproj/InfoPlist.strings; sourceTree = "<group>"; }; + 4084699C0F5D9CF900FDCA37 /* crash_report_sender.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = crash_report_sender.icns; path = sender/crash_report_sender.icns; sourceTree = "<group>"; }; + 4D61A25D14F43CFC002D5862 /* bootstrap_compat.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = bootstrap_compat.cc; path = ../../common/mac/bootstrap_compat.cc; sourceTree = SOURCE_ROOT; }; + 4D61A25E14F43CFC002D5862 /* bootstrap_compat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = bootstrap_compat.h; path = ../../common/mac/bootstrap_compat.h; sourceTree = SOURCE_ROOT; }; + 4D72CA0D13DFAD5C006CABE3 /* md5.cc */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.cpp; fileEncoding = 4; name = md5.cc; path = ../../common/md5.cc; sourceTree = SOURCE_ROOT; }; + 4DBE4769134A4F080072546A /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; + 8B31007011F0CD3C00FCF3E4 /* GTMDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTMDefines.h; path = ../../common/mac/GTMDefines.h; sourceTree = SOURCE_ROOT; }; + 8B3101E911F0CDE300FCF3E4 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; }; + 8B31027711F0D3AF00FCF3E4 /* BreakpadDebug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = BreakpadDebug.xcconfig; path = ../../common/mac/BreakpadDebug.xcconfig; sourceTree = SOURCE_ROOT; }; + 8B31027811F0D3AF00FCF3E4 /* BreakpadRelease.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = BreakpadRelease.xcconfig; path = ../../common/mac/BreakpadRelease.xcconfig; sourceTree = SOURCE_ROOT; }; + 8B31FFF611F0C90500FCF3E4 /* Breakpad.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Breakpad.xcconfig; path = ../../common/mac/Breakpad.xcconfig; sourceTree = SOURCE_ROOT; }; + 8DC2EF5B0486A6940098B216 /* Breakpad.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Breakpad.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D23F4B2C12A7E13200686C8D /* minidump_generator_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = minidump_generator_test.cc; path = tests/minidump_generator_test.cc; sourceTree = "<group>"; }; + D23F4B9A12A8688800686C8D /* minidump_generator_test_helper.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = minidump_generator_test_helper.cc; path = tests/minidump_generator_test_helper.cc; sourceTree = "<group>"; }; + D23F4BAB12A868A500686C8D /* minidump_generator_test_helper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = minidump_generator_test_helper; sourceTree = BUILT_PRODUCTS_DIR; }; + D244534F12426E98009BBCE0 /* basic_code_modules.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = basic_code_modules.cc; path = ../../processor/basic_code_modules.cc; sourceTree = SOURCE_ROOT; }; + D244535112426EBB009BBCE0 /* logging.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = logging.cc; path = ../../processor/logging.cc; sourceTree = SOURCE_ROOT; }; + D244535212426EBB009BBCE0 /* minidump.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = minidump.cc; path = ../../processor/minidump.cc; sourceTree = SOURCE_ROOT; }; + D244535312426EBB009BBCE0 /* pathname_stripper.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = pathname_stripper.cc; path = ../../processor/pathname_stripper.cc; sourceTree = SOURCE_ROOT; }; + D244540A12439BA0009BBCE0 /* memory_unittest.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = memory_unittest.cc; path = ../../common/memory_unittest.cc; sourceTree = SOURCE_ROOT; }; + D2F9A3D41212F87C002747C1 /* exception_handler_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = exception_handler_test.cc; path = tests/exception_handler_test.cc; sourceTree = "<group>"; }; + D2F9A41512131EF0002747C1 /* libgtest.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libgtest.a; sourceTree = BUILT_PRODUCTS_DIR; }; + D2F9A43C12131F55002747C1 /* gmock-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gmock-all.cc"; path = "../../testing/src/gmock-all.cc"; sourceTree = SOURCE_ROOT; }; + D2F9A43E12131F65002747C1 /* gtest_main.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = gtest_main.cc; path = ../../testing/gtest/src/gtest_main.cc; sourceTree = "<group>"; }; + D2F9A43F12131F65002747C1 /* gtest-all.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "gtest-all.cc"; path = "../../testing/gtest/src/gtest-all.cc"; sourceTree = "<group>"; }; + D2F9A4C4121336C7002747C1 /* client_info.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = client_info.h; path = crash_generation/client_info.h; sourceTree = "<group>"; }; + D2F9A4C5121336C7002747C1 /* crash_generation_client.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = crash_generation_client.h; path = crash_generation/crash_generation_client.h; sourceTree = "<group>"; }; + D2F9A4C6121336C7002747C1 /* crash_generation_client.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = crash_generation_client.cc; path = crash_generation/crash_generation_client.cc; sourceTree = "<group>"; }; + D2F9A4C7121336C7002747C1 /* crash_generation_server.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = crash_generation_server.h; path = crash_generation/crash_generation_server.h; sourceTree = "<group>"; }; + D2F9A4C8121336C7002747C1 /* crash_generation_server.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = crash_generation_server.cc; path = crash_generation/crash_generation_server.cc; sourceTree = "<group>"; }; + D2F9A4CE121336F7002747C1 /* crash_generation_server_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = crash_generation_server_test.cc; path = tests/crash_generation_server_test.cc; sourceTree = "<group>"; }; + D2F9A546121383A1002747C1 /* crash_generation_server_test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = crash_generation_server_test; sourceTree = BUILT_PRODUCTS_DIR; }; + DE43467411C72855004F095F /* da */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = da; path = sender/da.lproj/Localizable.strings; sourceTree = "<group>"; }; + DE43467511C72857004F095F /* de */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = de; path = sender/de.lproj/Localizable.strings; sourceTree = "<group>"; }; + DE43467611C7285B004F095F /* es */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = es; path = sender/es.lproj/Localizable.strings; sourceTree = "<group>"; }; + DE43467711C72862004F095F /* fr */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = fr; path = sender/fr.lproj/Localizable.strings; sourceTree = "<group>"; }; + DE43467811C72869004F095F /* it */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = it; path = sender/it.lproj/Localizable.strings; sourceTree = "<group>"; }; + DE43467911C7286D004F095F /* nl */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = nl; path = sender/nl.lproj/Localizable.strings; sourceTree = "<group>"; }; + DE43467A11C72873004F095F /* no */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = no; path = sender/no.lproj/Localizable.strings; sourceTree = "<group>"; }; + DE43467B11C72877004F095F /* sl */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = sl; path = sender/sl.lproj/Localizable.strings; sourceTree = "<group>"; }; + DE43467C11C7287A004F095F /* sv */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = sv; path = sender/sv.lproj/Localizable.strings; sourceTree = "<group>"; }; + DE43467E11C728DC004F095F /* ja */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = ja; path = sender/ja.lproj/Localizable.strings; sourceTree = "<group>"; }; + DE43467F11C728E1004F095F /* tr */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = tr; path = sender/tr.lproj/Localizable.strings; sourceTree = "<group>"; }; + DE43468611C72958004F095F /* de */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = de; path = sender/de.lproj/InfoPlist.strings; sourceTree = "<group>"; }; + DE43468711C7295D004F095F /* da */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = da; path = sender/da.lproj/InfoPlist.strings; sourceTree = "<group>"; }; + DE43468811C7295F004F095F /* es */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = es; path = sender/es.lproj/InfoPlist.strings; sourceTree = "<group>"; }; + DE43468911C72964004F095F /* fr */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = fr; path = sender/fr.lproj/InfoPlist.strings; sourceTree = "<group>"; }; + DE43468A11C72967004F095F /* it */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = it; path = sender/it.lproj/InfoPlist.strings; sourceTree = "<group>"; }; + DE43468B11C7296B004F095F /* ja */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = ja; path = sender/ja.lproj/InfoPlist.strings; sourceTree = "<group>"; }; + DE43468C11C7296D004F095F /* nl */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = nl; path = sender/nl.lproj/InfoPlist.strings; sourceTree = "<group>"; }; + DE43468D11C7296F004F095F /* no */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = no; path = sender/no.lproj/InfoPlist.strings; sourceTree = "<group>"; }; + DE43468E11C72971004F095F /* sl */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = sl; path = sender/sl.lproj/InfoPlist.strings; sourceTree = "<group>"; }; + DE43468F11C72973004F095F /* sv */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = sv; path = sender/sv.lproj/InfoPlist.strings; sourceTree = "<group>"; }; + DE43469011C72976004F095F /* tr */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = tr; path = sender/tr.lproj/InfoPlist.strings; sourceTree = "<group>"; }; + F4DAB1DB19F1027100A5A838 /* launch_reporter.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = launch_reporter.cc; path = ../../common/mac/launch_reporter.cc; sourceTree = SOURCE_ROOT; }; + F4DAB1DC19F1027100A5A838 /* launch_reporter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = launch_reporter.h; path = ../../common/mac/launch_reporter.h; sourceTree = SOURCE_ROOT; }; + F91AF5CF0FD60393009D8BE2 /* BreakpadFramework_Test.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BreakpadFramework_Test.mm; path = tests/BreakpadFramework_Test.mm; sourceTree = "<group>"; }; + F9286B380F7EB25800A4DCC8 /* Inspector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Inspector.h; path = crash_generation/Inspector.h; sourceTree = "<group>"; }; + F9286B390F7EB25800A4DCC8 /* InspectorMain.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = InspectorMain.mm; path = crash_generation/InspectorMain.mm; sourceTree = "<group>"; }; + F92C53540ECCE349009BE4BA /* Inspector */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = Inspector; sourceTree = BUILT_PRODUCTS_DIR; }; + F92C53670ECCE3FD009BE4BA /* breakpad_exc_server.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = breakpad_exc_server.c; path = handler/breakpad_exc_server.c; sourceTree = SOURCE_ROOT; }; + F92C53680ECCE3FD009BE4BA /* breakpad_exc_server.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = breakpad_exc_server.h; path = handler/breakpad_exc_server.h; sourceTree = SOURCE_ROOT; }; + F92C53690ECCE3FD009BE4BA /* breakpad_nlist_64.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = breakpad_nlist_64.cc; path = handler/breakpad_nlist_64.cc; sourceTree = SOURCE_ROOT; }; + F92C536A0ECCE3FD009BE4BA /* breakpad_nlist_64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = breakpad_nlist_64.h; path = handler/breakpad_nlist_64.h; sourceTree = SOURCE_ROOT; }; + F92C536B0ECCE3FD009BE4BA /* dynamic_images.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = dynamic_images.cc; path = handler/dynamic_images.cc; sourceTree = SOURCE_ROOT; }; + F92C536C0ECCE3FD009BE4BA /* dynamic_images.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dynamic_images.h; path = handler/dynamic_images.h; sourceTree = SOURCE_ROOT; }; + F92C536D0ECCE3FD009BE4BA /* exception_handler.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = exception_handler.cc; path = handler/exception_handler.cc; sourceTree = SOURCE_ROOT; }; + F92C536E0ECCE3FD009BE4BA /* exception_handler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = exception_handler.h; path = handler/exception_handler.h; sourceTree = SOURCE_ROOT; }; + F92C536F0ECCE3FD009BE4BA /* minidump_generator.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = minidump_generator.cc; path = handler/minidump_generator.cc; sourceTree = SOURCE_ROOT; }; + F92C53700ECCE3FD009BE4BA /* minidump_generator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = minidump_generator.h; path = handler/minidump_generator.h; sourceTree = SOURCE_ROOT; }; + F92C53720ECCE3FD009BE4BA /* protected_memory_allocator.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = protected_memory_allocator.cc; path = handler/protected_memory_allocator.cc; sourceTree = SOURCE_ROOT; }; + F92C53730ECCE3FD009BE4BA /* protected_memory_allocator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = protected_memory_allocator.h; path = handler/protected_memory_allocator.h; sourceTree = SOURCE_ROOT; }; + F92C53740ECCE635009BE4BA /* file_id.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = file_id.cc; path = ../../common/mac/file_id.cc; sourceTree = SOURCE_ROOT; }; + F92C53750ECCE635009BE4BA /* file_id.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = file_id.h; path = ../../common/mac/file_id.h; sourceTree = SOURCE_ROOT; }; + F92C53760ECCE635009BE4BA /* HTTPMultipartUpload.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HTTPMultipartUpload.h; path = ../../common/mac/HTTPMultipartUpload.h; sourceTree = SOURCE_ROOT; }; + F92C53770ECCE635009BE4BA /* HTTPMultipartUpload.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HTTPMultipartUpload.m; path = ../../common/mac/HTTPMultipartUpload.m; sourceTree = SOURCE_ROOT; }; + F92C53780ECCE635009BE4BA /* MachIPC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MachIPC.h; path = ../../common/mac/MachIPC.h; sourceTree = SOURCE_ROOT; }; + F92C53790ECCE635009BE4BA /* MachIPC.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MachIPC.mm; path = ../../common/mac/MachIPC.mm; sourceTree = SOURCE_ROOT; }; + F92C537A0ECCE635009BE4BA /* macho_id.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = macho_id.cc; path = ../../common/mac/macho_id.cc; sourceTree = SOURCE_ROOT; }; + F92C537B0ECCE635009BE4BA /* macho_id.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = macho_id.h; path = ../../common/mac/macho_id.h; sourceTree = SOURCE_ROOT; }; + F92C537C0ECCE635009BE4BA /* macho_utilities.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = macho_utilities.cc; path = ../../common/mac/macho_utilities.cc; sourceTree = SOURCE_ROOT; }; + F92C537D0ECCE635009BE4BA /* macho_utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = macho_utilities.h; path = ../../common/mac/macho_utilities.h; sourceTree = SOURCE_ROOT; }; + F92C537E0ECCE635009BE4BA /* macho_walker.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = macho_walker.cc; path = ../../common/mac/macho_walker.cc; sourceTree = SOURCE_ROOT; }; + F92C537F0ECCE635009BE4BA /* macho_walker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = macho_walker.h; path = ../../common/mac/macho_walker.h; sourceTree = SOURCE_ROOT; }; + F92C53820ECCE635009BE4BA /* string_utilities.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = string_utilities.cc; path = ../../common/mac/string_utilities.cc; sourceTree = SOURCE_ROOT; }; + F92C53830ECCE635009BE4BA /* string_utilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = string_utilities.h; path = ../../common/mac/string_utilities.h; sourceTree = SOURCE_ROOT; }; + F92C53850ECCE6AD009BE4BA /* string_conversion.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = string_conversion.cc; path = ../../common/string_conversion.cc; sourceTree = SOURCE_ROOT; }; + F92C53860ECCE6AD009BE4BA /* string_conversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = string_conversion.h; path = ../../common/string_conversion.h; sourceTree = SOURCE_ROOT; }; + F92C53870ECCE6C0009BE4BA /* convert_UTF.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = convert_UTF.c; path = ../../common/convert_UTF.c; sourceTree = SOURCE_ROOT; }; + F92C53880ECCE6C0009BE4BA /* convert_UTF.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = convert_UTF.h; path = ../../common/convert_UTF.h; sourceTree = SOURCE_ROOT; }; + F92C538E0ECCE70A009BE4BA /* minidump_file_writer-inl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "minidump_file_writer-inl.h"; path = "../minidump_file_writer-inl.h"; sourceTree = SOURCE_ROOT; }; + F92C538F0ECCE70A009BE4BA /* minidump_file_writer.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = minidump_file_writer.cc; path = ../minidump_file_writer.cc; sourceTree = SOURCE_ROOT; }; + F92C53900ECCE70A009BE4BA /* minidump_file_writer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = minidump_file_writer.h; path = ../minidump_file_writer.h; sourceTree = SOURCE_ROOT; }; + F92C53B70ECCE7B3009BE4BA /* Inspector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = Inspector.mm; path = crash_generation/Inspector.mm; sourceTree = SOURCE_ROOT; }; + F92C554A0ECCF530009BE4BA /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; }; + F92C55CE0ECD0064009BE4BA /* Breakpad.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Breakpad.h; path = Framework/Breakpad.h; sourceTree = "<group>"; }; + F92C55CF0ECD0064009BE4BA /* Breakpad.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = Breakpad.mm; path = Framework/Breakpad.mm; sourceTree = "<group>"; }; + F92C56310ECD0DF1009BE4BA /* OnDemandServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OnDemandServer.h; path = Framework/OnDemandServer.h; sourceTree = "<group>"; }; + F92C56320ECD0DF1009BE4BA /* OnDemandServer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = OnDemandServer.mm; path = Framework/OnDemandServer.mm; sourceTree = "<group>"; }; + F92C563C0ECD10B3009BE4BA /* breakpadUtilities.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = breakpadUtilities.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; + F92C56A00ECE04A7009BE4BA /* crash_report_sender.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = crash_report_sender.app; sourceTree = BUILT_PRODUCTS_DIR; }; + F92C56A20ECE04A7009BE4BA /* crash_report_sender-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "crash_report_sender-Info.plist"; path = "sender/crash_report_sender-Info.plist"; sourceTree = "<group>"; }; + F92C56A70ECE04C5009BE4BA /* crash_report_sender.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = crash_report_sender.h; path = sender/crash_report_sender.h; sourceTree = "<group>"; }; + F92C56A80ECE04C5009BE4BA /* crash_report_sender.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = crash_report_sender.m; path = sender/crash_report_sender.m; sourceTree = "<group>"; }; + F93803BE0F80820F004D428B /* generator_test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = generator_test; sourceTree = BUILT_PRODUCTS_DIR; }; + F93DE2D10F82A67300608B94 /* minidump_file_writer_unittest */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = minidump_file_writer_unittest; sourceTree = BUILT_PRODUCTS_DIR; }; + F93DE2D70F82A70E00608B94 /* minidump_file_writer_unittest.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = minidump_file_writer_unittest.cc; path = ../minidump_file_writer_unittest.cc; sourceTree = SOURCE_ROOT; }; + F93DE32C0F82C55600608B94 /* handler_test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = handler_test; sourceTree = BUILT_PRODUCTS_DIR; }; + F945849D0F280E3C009A47BF /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = sender/English.lproj/Localizable.strings; sourceTree = "<group>"; }; + F945859D0F78241E009A47BF /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Framework/Info.plist; sourceTree = "<group>"; }; + F95BB87C101F949F00AA053B /* crash_report.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = crash_report.xcodeproj; path = ../../tools/mac/crash_report/crash_report.xcodeproj; sourceTree = SOURCE_ROOT; }; + F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = dump_syms.xcodeproj; path = ../../tools/mac/dump_syms/dump_syms.xcodeproj; sourceTree = SOURCE_ROOT; }; + F95BB894101F94C000AA053B /* symupload.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = symupload.xcodeproj; path = ../../tools/mac/symupload/symupload.xcodeproj; sourceTree = SOURCE_ROOT; }; + F9B6309F100FF96B00D0F4AC /* goArrow.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = goArrow.png; path = sender/goArrow.png; sourceTree = "<group>"; }; + F9C44DA50EF060A8003AEBAA /* BreakpadTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BreakpadTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; + F9C44DAC0EF07288003AEBAA /* Controller.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Controller.m; path = testapp/Controller.m; sourceTree = "<group>"; }; + F9C44DAD0EF07288003AEBAA /* crashduringload */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; name = crashduringload; path = testapp/crashduringload; sourceTree = "<group>"; }; + F9C44DAE0EF07288003AEBAA /* crashInMain */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; name = crashInMain; path = testapp/crashInMain; sourceTree = "<group>"; }; + F9C44DAF0EF07288003AEBAA /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = testapp/Info.plist; sourceTree = "<group>"; }; + F9C44DB00EF07288003AEBAA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = testapp/main.m; sourceTree = "<group>"; }; + F9C44DB10EF07288003AEBAA /* TestClass.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = TestClass.mm; path = testapp/TestClass.mm; sourceTree = "<group>"; }; + F9C44DB90EF072A0003AEBAA /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = testapp/English.lproj/InfoPlist.strings; sourceTree = "<group>"; }; + F9C44DBB0EF072A0003AEBAA /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = testapp/English.lproj/MainMenu.xib; sourceTree = "<group>"; }; + F9C44DBF0EF0778F003AEBAA /* Controller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Controller.h; path = testapp/Controller.h; sourceTree = "<group>"; }; + F9C44DC00EF0778F003AEBAA /* TestClass.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TestClass.h; path = testapp/TestClass.h; sourceTree = "<group>"; }; + F9C44EE40EF0A006003AEBAA /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; + F9C44EE70EF0A3C1003AEBAA /* GTMLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTMLogger.h; path = ../../common/mac/GTMLogger.h; sourceTree = SOURCE_ROOT; }; + F9C44EE80EF0A3C1003AEBAA /* GTMLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTMLogger.m; path = ../../common/mac/GTMLogger.m; sourceTree = SOURCE_ROOT; }; + F9C77DDA0F7DD5CF0045F7DB /* UnitTests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UnitTests.octest; sourceTree = BUILT_PRODUCTS_DIR; }; + F9C77DDB0F7DD5CF0045F7DB /* UnitTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "UnitTests-Info.plist"; sourceTree = "<group>"; }; + F9C77E110F7DDF810045F7DB /* GTMSenTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTMSenTestCase.h; path = ../../common/mac/testing/GTMSenTestCase.h; sourceTree = SOURCE_ROOT; }; + F9C77E120F7DDF810045F7DB /* GTMSenTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTMSenTestCase.m; path = ../../common/mac/testing/GTMSenTestCase.m; sourceTree = SOURCE_ROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8DC2EF560486A6940098B216 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F92C565C0ECD1158009BE4BA /* breakpadUtilities.dylib in Frameworks */, + 8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D23F4BA912A868A500686C8D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D246418412BAA4BA005170D0 /* Foundation.framework in Frameworks */, + 4DBE49A6134A4F200072546A /* CoreServices.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D2F9A41312131EF0002747C1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D2F9A53E121383A1002747C1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D2F9A53F121383A1002747C1 /* Foundation.framework in Frameworks */, + D2F9A541121383A1002747C1 /* libgtest.a in Frameworks */, + 4DBE49A9134A4F460072546A /* CoreServices.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F92C53520ECCE349009BE4BA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F92C564C0ECD10DD009BE4BA /* breakpadUtilities.dylib in Frameworks */, + F92C554C0ECCF534009BE4BA /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F92C563A0ECD10B3009BE4BA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8B31FC8211EFD2B800FCF3E4 /* Foundation.framework in Frameworks */, + F92C56570ECD113E009BE4BA /* Carbon.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F92C569E0ECE04A7009BE4BA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F9C44EE50EF0A006003AEBAA /* SystemConfiguration.framework in Frameworks */, + 8B3101C611F0CD9F00FCF3E4 /* AppKit.framework in Frameworks */, + 8B3101C711F0CD9F00FCF3E4 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F93803BC0F80820F004D428B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8B31029411F0D54300FCF3E4 /* Foundation.framework in Frameworks */, + D23F4B3312A7E17700686C8D /* libgtest.a in Frameworks */, + 4DBE49A7134A4F280072546A /* CoreServices.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F93DE2CF0F82A67300608B94 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F93DE32A0F82C55600608B94 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8B3102E611F0D74C00FCF3E4 /* Foundation.framework in Frameworks */, + D2F9A44412131F84002747C1 /* libgtest.a in Frameworks */, + 4DBE49A8134A4F380072546A /* CoreServices.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F9C44DA30EF060A8003AEBAA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F9C44E000EF077CD003AEBAA /* Breakpad.framework in Frameworks */, + 8B3101CA11F0CDB000FCF3E4 /* AppKit.framework in Frameworks */, + 8B3101CB11F0CDB000FCF3E4 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F9C77DD70F7DD5CF0045F7DB /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F91AF6210FD60784009D8BE2 /* Breakpad.framework in Frameworks */, + 8B3101EA11F0CDE300FCF3E4 /* SenTestingKit.framework in Frameworks */, + 8B3102EB11F0D78000FCF3E4 /* Foundation.framework in Frameworks */, + D24BBBFD121050F000F3D417 /* breakpadUtilities.dylib in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 034768DFFF38A50411DB9C8B /* Products */ = { + isa = PBXGroup; + children = ( + 8DC2EF5B0486A6940098B216 /* Breakpad.framework */, + F92C53540ECCE349009BE4BA /* Inspector */, + F92C563C0ECD10B3009BE4BA /* breakpadUtilities.dylib */, + F92C56A00ECE04A7009BE4BA /* crash_report_sender.app */, + F9C44DA50EF060A8003AEBAA /* BreakpadTest.app */, + F9C77DDA0F7DD5CF0045F7DB /* UnitTests.octest */, + F93803BE0F80820F004D428B /* generator_test */, + F93DE2D10F82A67300608B94 /* minidump_file_writer_unittest */, + F93DE32C0F82C55600608B94 /* handler_test */, + D2F9A41512131EF0002747C1 /* libgtest.a */, + D2F9A546121383A1002747C1 /* crash_generation_server_test */, + D23F4BAB12A868A500686C8D /* minidump_generator_test_helper */, + ); + name = Products; + sourceTree = "<group>"; + }; + 0867D691FE84028FC02AAC07 /* Breakpad */ = { + isa = PBXGroup; + children = ( + D2F9A43812131F3B002747C1 /* gtest */, + 8B31FFF611F0C90500FCF3E4 /* Breakpad.xcconfig */, + 8B31027711F0D3AF00FCF3E4 /* BreakpadDebug.xcconfig */, + 8B31027811F0D3AF00FCF3E4 /* BreakpadRelease.xcconfig */, + F95BB8A3101F94C300AA053B /* Tools */, + 32DBCF5E0370ADEE00C91783 /* Breakpad_Prefix.pch */, + F92C538D0ECCE6F2009BE4BA /* client */, + F92C53600ECCE3D6009BE4BA /* common */, + D244536912426EE7009BBCE0 /* processor */, + 0867D69AFE84028FC02AAC07 /* Frameworks */, + 034768DFFF38A50411DB9C8B /* Products */, + F9C77DDB0F7DD5CF0045F7DB /* UnitTests-Info.plist */, + ); + name = Breakpad; + sourceTree = "<group>"; + }; + 0867D69AFE84028FC02AAC07 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 8B3101E911F0CDE300FCF3E4 /* SenTestingKit.framework */, + F9C44EE40EF0A006003AEBAA /* SystemConfiguration.framework */, + F92C554A0ECCF530009BE4BA /* Carbon.framework */, + 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */, + 0867D6A5FE840307C02AAC07 /* AppKit.framework */, + 0867D69BFE84028FC02AAC07 /* Foundation.framework */, + 4DBE4769134A4F080072546A /* CoreServices.framework */, + ); + name = Frameworks; + sourceTree = "<group>"; + }; + 16C7C915147D45AE00776EAD /* apple */ = { + isa = PBXGroup; + children = ( + 16C7C916147D45AE00776EAD /* Framework */, + ); + name = apple; + path = ../apple; + sourceTree = SOURCE_ROOT; + }; + 16C7C916147D45AE00776EAD /* Framework */ = { + isa = PBXGroup; + children = ( + 16C7C917147D45AE00776EAD /* BreakpadDefines.h */, + ); + path = Framework; + sourceTree = "<group>"; + }; + D244536912426EE7009BBCE0 /* processor */ = { + isa = PBXGroup; + children = ( + D244535112426EBB009BBCE0 /* logging.cc */, + D244535212426EBB009BBCE0 /* minidump.cc */, + D244535312426EBB009BBCE0 /* pathname_stripper.cc */, + D244534F12426E98009BBCE0 /* basic_code_modules.cc */, + ); + name = processor; + sourceTree = "<group>"; + }; + D2F9A43812131F3B002747C1 /* gtest */ = { + isa = PBXGroup; + children = ( + D2F9A43E12131F65002747C1 /* gtest_main.cc */, + D2F9A43F12131F65002747C1 /* gtest-all.cc */, + D2F9A43C12131F55002747C1 /* gmock-all.cc */, + ); + name = gtest; + sourceTree = "<group>"; + }; + F92C53590ECCE3BB009BE4BA /* handler */ = { + isa = PBXGroup; + children = ( + F92C53670ECCE3FD009BE4BA /* breakpad_exc_server.c */, + F92C53680ECCE3FD009BE4BA /* breakpad_exc_server.h */, + F92C53690ECCE3FD009BE4BA /* breakpad_nlist_64.cc */, + F92C536A0ECCE3FD009BE4BA /* breakpad_nlist_64.h */, + F92C536B0ECCE3FD009BE4BA /* dynamic_images.cc */, + F92C536C0ECCE3FD009BE4BA /* dynamic_images.h */, + F92C536D0ECCE3FD009BE4BA /* exception_handler.cc */, + F92C536E0ECCE3FD009BE4BA /* exception_handler.h */, + F92C536F0ECCE3FD009BE4BA /* minidump_generator.cc */, + F92C53700ECCE3FD009BE4BA /* minidump_generator.h */, + F92C53720ECCE3FD009BE4BA /* protected_memory_allocator.cc */, + F92C53730ECCE3FD009BE4BA /* protected_memory_allocator.h */, + ); + name = handler; + sourceTree = "<group>"; + }; + F92C53600ECCE3D6009BE4BA /* common */ = { + isa = PBXGroup; + children = ( + D244540A12439BA0009BBCE0 /* memory_unittest.cc */, + F92C53870ECCE6C0009BE4BA /* convert_UTF.c */, + F92C53880ECCE6C0009BE4BA /* convert_UTF.h */, + 4D72CA0D13DFAD5C006CABE3 /* md5.cc */, + 1EEEB6211720829E00F7E689 /* simple_string_dictionary.cc */, + 1EEEB6221720829E00F7E689 /* simple_string_dictionary.h */, + F92C53850ECCE6AD009BE4BA /* string_conversion.cc */, + F92C53860ECCE6AD009BE4BA /* string_conversion.h */, + F92C53840ECCE68D009BE4BA /* mac */, + ); + name = common; + sourceTree = "<group>"; + }; + F92C53840ECCE68D009BE4BA /* mac */ = { + isa = PBXGroup; + children = ( + 162F64F0161C577500CD68D5 /* arch_utilities.cc */, + 162F64F1161C577500CD68D5 /* arch_utilities.h */, + 8B31007011F0CD3C00FCF3E4 /* GTMDefines.h */, + F9C77E0F0F7DDF650045F7DB /* testing */, + F9C44EE70EF0A3C1003AEBAA /* GTMLogger.h */, + F9C44EE80EF0A3C1003AEBAA /* GTMLogger.m */, + F92C53740ECCE635009BE4BA /* file_id.cc */, + F92C53750ECCE635009BE4BA /* file_id.h */, + F92C53760ECCE635009BE4BA /* HTTPMultipartUpload.h */, + F92C53770ECCE635009BE4BA /* HTTPMultipartUpload.m */, + F4DAB1DB19F1027100A5A838 /* launch_reporter.cc */, + F4DAB1DC19F1027100A5A838 /* launch_reporter.h */, + F92C53780ECCE635009BE4BA /* MachIPC.h */, + F92C53790ECCE635009BE4BA /* MachIPC.mm */, + 4D61A25D14F43CFC002D5862 /* bootstrap_compat.cc */, + 4D61A25E14F43CFC002D5862 /* bootstrap_compat.h */, + F92C537A0ECCE635009BE4BA /* macho_id.cc */, + F92C537B0ECCE635009BE4BA /* macho_id.h */, + F92C537C0ECCE635009BE4BA /* macho_utilities.cc */, + F92C537D0ECCE635009BE4BA /* macho_utilities.h */, + F92C537E0ECCE635009BE4BA /* macho_walker.cc */, + F92C537F0ECCE635009BE4BA /* macho_walker.h */, + F92C53820ECCE635009BE4BA /* string_utilities.cc */, + F92C53830ECCE635009BE4BA /* string_utilities.h */, + ); + name = mac; + sourceTree = "<group>"; + }; + F92C538D0ECCE6F2009BE4BA /* client */ = { + isa = PBXGroup; + children = ( + 16C7C915147D45AE00776EAD /* apple */, + F92C53990ECCE78E009BE4BA /* mac */, + F92C538E0ECCE70A009BE4BA /* minidump_file_writer-inl.h */, + F92C538F0ECCE70A009BE4BA /* minidump_file_writer.cc */, + F92C53900ECCE70A009BE4BA /* minidump_file_writer.h */, + F93DE2D70F82A70E00608B94 /* minidump_file_writer_unittest.cc */, + ); + name = client; + sourceTree = "<group>"; + }; + F92C53990ECCE78E009BE4BA /* mac */ = { + isa = PBXGroup; + children = ( + F9C77DDF0F7DD7CF0045F7DB /* tests */, + F9C44DAB0EF0726F003AEBAA /* testapp */, + F92C56A60ECE04B6009BE4BA /* sender */, + F92C55CD0ECD0053009BE4BA /* Framework */, + F92C53B50ECCE799009BE4BA /* crash_generation */, + F92C53590ECCE3BB009BE4BA /* handler */, + ); + name = mac; + sourceTree = "<group>"; + }; + F92C53B50ECCE799009BE4BA /* crash_generation */ = { + isa = PBXGroup; + children = ( + 163201D41443019E00C4DBF5 /* ConfigFile.h */, + 163201D51443019E00C4DBF5 /* ConfigFile.mm */, + D2F9A4C4121336C7002747C1 /* client_info.h */, + D2F9A4C5121336C7002747C1 /* crash_generation_client.h */, + D2F9A4C6121336C7002747C1 /* crash_generation_client.cc */, + D2F9A4C7121336C7002747C1 /* crash_generation_server.h */, + D2F9A4C8121336C7002747C1 /* crash_generation_server.cc */, + F9286B380F7EB25800A4DCC8 /* Inspector.h */, + F9286B390F7EB25800A4DCC8 /* InspectorMain.mm */, + F92C53B70ECCE7B3009BE4BA /* Inspector.mm */, + ); + name = crash_generation; + sourceTree = "<group>"; + }; + F92C55CD0ECD0053009BE4BA /* Framework */ = { + isa = PBXGroup; + children = ( + F945859D0F78241E009A47BF /* Info.plist */, + F92C56310ECD0DF1009BE4BA /* OnDemandServer.h */, + F92C56320ECD0DF1009BE4BA /* OnDemandServer.mm */, + F92C55CE0ECD0064009BE4BA /* Breakpad.h */, + F92C55CF0ECD0064009BE4BA /* Breakpad.mm */, + ); + name = Framework; + sourceTree = "<group>"; + }; + F92C56A60ECE04B6009BE4BA /* sender */ = { + isa = PBXGroup; + children = ( + 16E02DB4147410D4008C604D /* uploader.mm */, + 163202431443201300C4DBF5 /* uploader.h */, + F9B6309F100FF96B00D0F4AC /* goArrow.png */, + F92C56A70ECE04C5009BE4BA /* crash_report_sender.h */, + F92C56A80ECE04C5009BE4BA /* crash_report_sender.m */, + F945849C0F280E3C009A47BF /* Localizable.strings */, + 33880C7E0F9E097100817F82 /* InfoPlist.strings */, + 3329D4EC0FA16D820007BBC5 /* Breakpad.xib */, + 4084699C0F5D9CF900FDCA37 /* crash_report_sender.icns */, + F92C56A20ECE04A7009BE4BA /* crash_report_sender-Info.plist */, + ); + name = sender; + sourceTree = "<group>"; + }; + F95BB87D101F949F00AA053B /* Products */ = { + isa = PBXGroup; + children = ( + F95BB885101F949F00AA053B /* crash_report */, + ); + name = Products; + sourceTree = "<group>"; + }; + F95BB88A101F94AC00AA053B /* Products */ = { + isa = PBXGroup; + children = ( + F95BB892101F94AC00AA053B /* dump_syms */, + 8B31F7A111EF9A8700FCF3E4 /* macho_dump */, + 8B31F7A311EF9A8700FCF3E4 /* libgtestmockall.a */, + 8B31F7A511EF9A8700FCF3E4 /* byte_cursor_unittest */, + 8B31F7A711EF9A8700FCF3E4 /* macho_reader_unittest */, + 8B31F7A911EF9A8700FCF3E4 /* stabs_reader_unittest */, + 8B31F7AB11EF9A8700FCF3E4 /* bytereader_unittest */, + 8B31F7AD11EF9A8700FCF3E4 /* dwarf2reader_cfi_unittest */, + 8B31F7AF11EF9A8700FCF3E4 /* dwarf2diehandler_unittest */, + 8B31F7B111EF9A8700FCF3E4 /* dwarf_cu_to_module_unittest */, + 8B31F7B311EF9A8700FCF3E4 /* dwarf_line_to_module_unittest */, + 8B31F7B511EF9A8700FCF3E4 /* dwarf_cfi_to_module_unittest */, + 8B31F7B711EF9A8700FCF3E4 /* stabs_to_module_unittest */, + 8B31F7B911EF9A8700FCF3E4 /* module_unittest */, + 8B31F7BB11EF9A8700FCF3E4 /* test_assembler_unittest */, + ); + name = Products; + sourceTree = "<group>"; + }; + F95BB895101F94C000AA053B /* Products */ = { + isa = PBXGroup; + children = ( + F95BB89F101F94C000AA053B /* symupload */, + F95BB8A1101F94C000AA053B /* minidump_upload */, + ); + name = Products; + sourceTree = "<group>"; + }; + F95BB8A3101F94C300AA053B /* Tools */ = { + isa = PBXGroup; + children = ( + F95BB894101F94C000AA053B /* symupload.xcodeproj */, + F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */, + F95BB87C101F949F00AA053B /* crash_report.xcodeproj */, + ); + name = Tools; + sourceTree = "<group>"; + }; + F9C44DAB0EF0726F003AEBAA /* testapp */ = { + isa = PBXGroup; + children = ( + F9C44DBF0EF0778F003AEBAA /* Controller.h */, + F9C44DC00EF0778F003AEBAA /* TestClass.h */, + F9C44DB80EF072A0003AEBAA /* InfoPlist.strings */, + F9C44DBA0EF072A0003AEBAA /* MainMenu.xib */, + F9C44DAC0EF07288003AEBAA /* Controller.m */, + F9C44DAD0EF07288003AEBAA /* crashduringload */, + F9C44DAE0EF07288003AEBAA /* crashInMain */, + F9C44DAF0EF07288003AEBAA /* Info.plist */, + F9C44DB00EF07288003AEBAA /* main.m */, + F9C44DB10EF07288003AEBAA /* TestClass.mm */, + ); + name = testapp; + sourceTree = "<group>"; + }; + F9C77DDF0F7DD7CF0045F7DB /* tests */ = { + isa = PBXGroup; + children = ( + 1EEEB6251720830600F7E689 /* simple_string_dictionary_unittest.cc */, + D23F4B9A12A8688800686C8D /* minidump_generator_test_helper.cc */, + D23F4B2C12A7E13200686C8D /* minidump_generator_test.cc */, + D2F9A4CE121336F7002747C1 /* crash_generation_server_test.cc */, + D2F9A3D41212F87C002747C1 /* exception_handler_test.cc */, + F91AF5CF0FD60393009D8BE2 /* BreakpadFramework_Test.mm */, + ); + name = tests; + sourceTree = "<group>"; + }; + F9C77E0F0F7DDF650045F7DB /* testing */ = { + isa = PBXGroup; + children = ( + F9C77E110F7DDF810045F7DB /* GTMSenTestCase.h */, + F9C77E120F7DDF810045F7DB /* GTMSenTestCase.m */, + ); + name = testing; + sourceTree = "<group>"; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 8DC2EF500486A6940098B216 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + F92C55D00ECD0064009BE4BA /* Breakpad.h in Headers */, + F92C56330ECD0DF1009BE4BA /* OnDemandServer.h in Headers */, + D2F9A4C9121336C7002747C1 /* client_info.h in Headers */, + D2F9A4CA121336C7002747C1 /* crash_generation_client.h in Headers */, + D2F9A4CC121336C7002747C1 /* crash_generation_server.h in Headers */, + 163201D61443019E00C4DBF5 /* ConfigFile.h in Headers */, + 16C7C918147D45AE00776EAD /* BreakpadDefines.h in Headers */, + 162F64F3161C577500CD68D5 /* arch_utilities.h in Headers */, + F4DAB1DE19F1027100A5A838 /* launch_reporter.h in Headers */, + 1EEEB6241720829E00F7E689 /* simple_string_dictionary.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D2F9A41112131EF0002747C1 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F92C56380ECD10B3009BE4BA /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 8DC2EF4F0486A6940098B216 /* Breakpad */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1DEB91AD08733DA50010E9CD /* Build configuration list for PBXNativeTarget "Breakpad" */; + buildPhases = ( + F97A0E850ED4EC15008784D3 /* Change install name of breakpadUtilities */, + 8DC2EF500486A6940098B216 /* Headers */, + 8DC2EF520486A6940098B216 /* Resources */, + 8DC2EF540486A6940098B216 /* Sources */, + 8DC2EF560486A6940098B216 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + F92C56860ECD15EF009BE4BA /* PBXTargetDependency */, + F92C56880ECD15F1009BE4BA /* PBXTargetDependency */, + F9C44E970EF09F4B003AEBAA /* PBXTargetDependency */, + ); + name = Breakpad; + productInstallPath = "$(HOME)/Library/Frameworks"; + productName = Breakpad; + productReference = 8DC2EF5B0486A6940098B216 /* Breakpad.framework */; + productType = "com.apple.product-type.framework"; + }; + D23F4BAA12A868A500686C8D /* minidump_generator_test_helper */ = { + isa = PBXNativeTarget; + buildConfigurationList = D23F4BB012A868C400686C8D /* Build configuration list for PBXNativeTarget "minidump_generator_test_helper" */; + buildPhases = ( + D23F4BA812A868A500686C8D /* Sources */, + D23F4BA912A868A500686C8D /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = minidump_generator_test_helper; + productName = minidump_generator_test_helper; + productReference = D23F4BAB12A868A500686C8D /* minidump_generator_test_helper */; + productType = "com.apple.product-type.tool"; + }; + D2F9A41412131EF0002747C1 /* gtest */ = { + isa = PBXNativeTarget; + buildConfigurationList = D2F9A42D12131F0E002747C1 /* Build configuration list for PBXNativeTarget "gtest" */; + buildPhases = ( + D2F9A41112131EF0002747C1 /* Headers */, + D2F9A41212131EF0002747C1 /* Sources */, + D2F9A41312131EF0002747C1 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = gtest; + productName = gtest; + productReference = D2F9A41512131EF0002747C1 /* libgtest.a */; + productType = "com.apple.product-type.library.static"; + }; + D2F9A52A121383A1002747C1 /* crash_generation_server_test */ = { + isa = PBXNativeTarget; + buildConfigurationList = D2F9A542121383A1002747C1 /* Build configuration list for PBXNativeTarget "crash_generation_server_test" */; + buildPhases = ( + D2F9A52D121383A1002747C1 /* Sources */, + D2F9A53E121383A1002747C1 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + D2F9A52B121383A1002747C1 /* PBXTargetDependency */, + ); + name = crash_generation_server_test; + productName = handler_test; + productReference = D2F9A546121383A1002747C1 /* crash_generation_server_test */; + productType = "com.apple.product-type.tool"; + }; + F92C53530ECCE349009BE4BA /* Inspector */ = { + isa = PBXNativeTarget; + buildConfigurationList = F92C53580ECCE36D009BE4BA /* Build configuration list for PBXNativeTarget "Inspector" */; + buildPhases = ( + F94584840F27FB40009A47BF /* Change install name of breakpadUtilities */, + F92C53510ECCE349009BE4BA /* Sources */, + F92C53520ECCE349009BE4BA /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + F92C564E0ECD10E5009BE4BA /* PBXTargetDependency */, + ); + name = Inspector; + productName = Inspector; + productReference = F92C53540ECCE349009BE4BA /* Inspector */; + productType = "com.apple.product-type.tool"; + }; + F92C563B0ECD10B3009BE4BA /* breakpadUtilities */ = { + isa = PBXNativeTarget; + buildConfigurationList = F92C56670ECD11A3009BE4BA /* Build configuration list for PBXNativeTarget "breakpadUtilities" */; + buildPhases = ( + F92C56380ECD10B3009BE4BA /* Headers */, + F92C56390ECD10B3009BE4BA /* Sources */, + F92C563A0ECD10B3009BE4BA /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = breakpadUtilities; + productName = breakpadUtilities; + productReference = F92C563C0ECD10B3009BE4BA /* breakpadUtilities.dylib */; + productType = "com.apple.product-type.library.dynamic"; + }; + F92C569F0ECE04A7009BE4BA /* crash_report_sender */ = { + isa = PBXNativeTarget; + buildConfigurationList = F92C56A50ECE04A8009BE4BA /* Build configuration list for PBXNativeTarget "crash_report_sender" */; + buildPhases = ( + F92C569C0ECE04A7009BE4BA /* Resources */, + F92C569D0ECE04A7009BE4BA /* Sources */, + F92C569E0ECE04A7009BE4BA /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = crash_report_sender; + productName = crash_report_sender; + productReference = F92C56A00ECE04A7009BE4BA /* crash_report_sender.app */; + productType = "com.apple.product-type.application"; + }; + F93803BD0F80820F004D428B /* generator_test */ = { + isa = PBXNativeTarget; + buildConfigurationList = F93803C40F80822E004D428B /* Build configuration list for PBXNativeTarget "generator_test" */; + buildPhases = ( + F93803BB0F80820F004D428B /* Sources */, + F93803BC0F80820F004D428B /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + D23F4B3012A7E16200686C8D /* PBXTargetDependency */, + D23F4BBA12A8694C00686C8D /* PBXTargetDependency */, + ); + name = generator_test; + productName = generator_test; + productReference = F93803BE0F80820F004D428B /* generator_test */; + productType = "com.apple.product-type.tool"; + }; + F93DE2D00F82A67300608B94 /* minidump_file_writer_unittest */ = { + isa = PBXNativeTarget; + buildConfigurationList = F93DE2D60F82A67700608B94 /* Build configuration list for PBXNativeTarget "minidump_file_writer_unittest" */; + buildPhases = ( + F93DE2CE0F82A67300608B94 /* Sources */, + F93DE2CF0F82A67300608B94 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = minidump_file_writer_unittest; + productName = minidump_file_writer_unittest; + productReference = F93DE2D10F82A67300608B94 /* minidump_file_writer_unittest */; + productType = "com.apple.product-type.tool"; + }; + F93DE32B0F82C55600608B94 /* handler_test */ = { + isa = PBXNativeTarget; + buildConfigurationList = F93DE3320F82C5D800608B94 /* Build configuration list for PBXNativeTarget "handler_test" */; + buildPhases = ( + F93DE3290F82C55600608B94 /* Sources */, + F93DE32A0F82C55600608B94 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + D2F9A44312131F80002747C1 /* PBXTargetDependency */, + ); + name = handler_test; + productName = handler_test; + productReference = F93DE32C0F82C55600608B94 /* handler_test */; + productType = "com.apple.product-type.tool"; + }; + F9C44DA40EF060A8003AEBAA /* BreakpadTest */ = { + isa = PBXNativeTarget; + buildConfigurationList = F9C44DAA0EF060A9003AEBAA /* Build configuration list for PBXNativeTarget "BreakpadTest" */; + buildPhases = ( + F9C44DA10EF060A8003AEBAA /* Resources */, + F9C44DA20EF060A8003AEBAA /* Sources */, + F9C44DA30EF060A8003AEBAA /* Frameworks */, + F9C44E410EF08B17003AEBAA /* Copy Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + F9C44E1A0EF0790F003AEBAA /* PBXTargetDependency */, + ); + name = BreakpadTest; + productName = BreakpadTest; + productReference = F9C44DA50EF060A8003AEBAA /* BreakpadTest.app */; + productType = "com.apple.product-type.application"; + }; + F9C77DD90F7DD5CF0045F7DB /* UnitTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = F9C77DDE0F7DD5D00045F7DB /* Build configuration list for PBXNativeTarget "UnitTests" */; + buildPhases = ( + F9C77DD50F7DD5CF0045F7DB /* Resources */, + F9C77DD60F7DD5CF0045F7DB /* Sources */, + F9C77DD70F7DD5CF0045F7DB /* Frameworks */, + F9C77DD80F7DD5CF0045F7DB /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + F93DE2FC0F82C3C600608B94 /* PBXTargetDependency */, + F93DE3700F82CC1300608B94 /* PBXTargetDependency */, + F91AF6380FD60A74009D8BE2 /* PBXTargetDependency */, + D2F9A5DF12142A6A002747C1 /* PBXTargetDependency */, + ); + name = UnitTests; + productName = UnitTests; + productReference = F9C77DDA0F7DD5CF0045F7DB /* UnitTests.octest */; + productType = "com.apple.product-type.bundle"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 0867D690FE84028FC02AAC07 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 1DEB91B108733DA50010E9CD /* Build configuration list for PBXProject "Breakpad" */; + compatibilityVersion = "Xcode 3.1"; + developmentRegion = English; + hasScannedForEncodings = 1; + knownRegions = ( + English, + da, + de, + es, + fr, + it, + ja, + nl, + no, + sl, + sv, + tr, + ); + mainGroup = 0867D691FE84028FC02AAC07 /* Breakpad */; + productRefGroup = 034768DFFF38A50411DB9C8B /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = F95BB87D101F949F00AA053B /* Products */; + ProjectRef = F95BB87C101F949F00AA053B /* crash_report.xcodeproj */; + }, + { + ProductGroup = F95BB88A101F94AC00AA053B /* Products */; + ProjectRef = F95BB889101F94AC00AA053B /* dump_syms.xcodeproj */; + }, + { + ProductGroup = F95BB895101F94C000AA053B /* Products */; + ProjectRef = F95BB894101F94C000AA053B /* symupload.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 8DC2EF4F0486A6940098B216 /* Breakpad */, + F92C53530ECCE349009BE4BA /* Inspector */, + F92C563B0ECD10B3009BE4BA /* breakpadUtilities */, + F92C569F0ECE04A7009BE4BA /* crash_report_sender */, + F9C44DA40EF060A8003AEBAA /* BreakpadTest */, + F94585840F782326009A47BF /* All */, + F9C77DD90F7DD5CF0045F7DB /* UnitTests */, + F93803BD0F80820F004D428B /* generator_test */, + F93DE2D00F82A67300608B94 /* minidump_file_writer_unittest */, + F93DE32B0F82C55600608B94 /* handler_test */, + D2F9A41412131EF0002747C1 /* gtest */, + D2F9A52A121383A1002747C1 /* crash_generation_server_test */, + D23F4BAA12A868A500686C8D /* minidump_generator_test_helper */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 8B31F7A111EF9A8700FCF3E4 /* macho_dump */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = macho_dump; + remoteRef = 8B31F7A011EF9A8700FCF3E4 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 8B31F7A311EF9A8700FCF3E4 /* libgtestmockall.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libgtestmockall.a; + remoteRef = 8B31F7A211EF9A8700FCF3E4 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 8B31F7A511EF9A8700FCF3E4 /* byte_cursor_unittest */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = byte_cursor_unittest; + remoteRef = 8B31F7A411EF9A8700FCF3E4 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 8B31F7A711EF9A8700FCF3E4 /* macho_reader_unittest */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = macho_reader_unittest; + remoteRef = 8B31F7A611EF9A8700FCF3E4 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 8B31F7A911EF9A8700FCF3E4 /* stabs_reader_unittest */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = stabs_reader_unittest; + remoteRef = 8B31F7A811EF9A8700FCF3E4 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 8B31F7AB11EF9A8700FCF3E4 /* bytereader_unittest */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = bytereader_unittest; + remoteRef = 8B31F7AA11EF9A8700FCF3E4 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 8B31F7AD11EF9A8700FCF3E4 /* dwarf2reader_cfi_unittest */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = dwarf2reader_cfi_unittest; + remoteRef = 8B31F7AC11EF9A8700FCF3E4 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 8B31F7AF11EF9A8700FCF3E4 /* dwarf2diehandler_unittest */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = dwarf2diehandler_unittest; + remoteRef = 8B31F7AE11EF9A8700FCF3E4 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 8B31F7B111EF9A8700FCF3E4 /* dwarf_cu_to_module_unittest */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = dwarf_cu_to_module_unittest; + remoteRef = 8B31F7B011EF9A8700FCF3E4 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 8B31F7B311EF9A8700FCF3E4 /* dwarf_line_to_module_unittest */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = dwarf_line_to_module_unittest; + remoteRef = 8B31F7B211EF9A8700FCF3E4 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 8B31F7B511EF9A8700FCF3E4 /* dwarf_cfi_to_module_unittest */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = dwarf_cfi_to_module_unittest; + remoteRef = 8B31F7B411EF9A8700FCF3E4 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 8B31F7B711EF9A8700FCF3E4 /* stabs_to_module_unittest */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = stabs_to_module_unittest; + remoteRef = 8B31F7B611EF9A8700FCF3E4 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 8B31F7B911EF9A8700FCF3E4 /* module_unittest */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = module_unittest; + remoteRef = 8B31F7B811EF9A8700FCF3E4 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 8B31F7BB11EF9A8700FCF3E4 /* test_assembler_unittest */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = test_assembler_unittest; + remoteRef = 8B31F7BA11EF9A8700FCF3E4 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + F95BB885101F949F00AA053B /* crash_report */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = crash_report; + remoteRef = F95BB884101F949F00AA053B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + F95BB892101F94AC00AA053B /* dump_syms */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = dump_syms; + remoteRef = F95BB891101F94AC00AA053B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + F95BB89F101F94C000AA053B /* symupload */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = symupload; + remoteRef = F95BB89E101F94C000AA053B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + F95BB8A1101F94C000AA053B /* minidump_upload */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = minidump_upload; + remoteRef = F95BB8A0101F94C000AA053B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 8DC2EF520486A6940098B216 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F9C44E980EF09F56003AEBAA /* crash_report_sender.app in Resources */, + F92C568A0ECD15F9009BE4BA /* Inspector in Resources */, + F92C56650ECD1185009BE4BA /* breakpadUtilities.dylib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F92C569C0ECE04A7009BE4BA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F945849E0F280E3C009A47BF /* Localizable.strings in Resources */, + 4084699D0F5D9CF900FDCA37 /* crash_report_sender.icns in Resources */, + 33880C800F9E097100817F82 /* InfoPlist.strings in Resources */, + 3329D4ED0FA16D820007BBC5 /* Breakpad.xib in Resources */, + F9B630A0100FF96B00D0F4AC /* goArrow.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F9C44DA10EF060A8003AEBAA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F9C44DB30EF07288003AEBAA /* crashduringload in Resources */, + F9C44DB40EF07288003AEBAA /* crashInMain in Resources */, + F9C44DBC0EF072A0003AEBAA /* InfoPlist.strings in Resources */, + F9C44DBD0EF072A0003AEBAA /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F9C77DD50F7DD5CF0045F7DB /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + F94584840F27FB40009A47BF /* Change install name of breakpadUtilities */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Change install name of breakpadUtilities"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "install_name_tool -id \"@executable_path/../Resources/breakpadUtilities.dylib\" \"${BUILT_PRODUCTS_DIR}/breakpadUtilities.dylib\"\n"; + }; + F97A0E850ED4EC15008784D3 /* Change install name of breakpadUtilities */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Change install name of breakpadUtilities"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "#!/bin/bash\ninstall_name_tool -id \"@executable_path/../Frameworks/Breakpad.framework/Resources/breakpadUtilities.dylib\" \"${BUILT_PRODUCTS_DIR}/breakpadUtilities.dylib\"\n"; + }; + F9C77DD80F7DD5CF0045F7DB /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n\necho running minidump generator tests...\n\"${BUILT_PRODUCTS_DIR}/generator_test\"\necho Running exception handler tests...\n\"${BUILT_PRODUCTS_DIR}/handler_test\"\necho Running crash generation server tests...\n\"${BUILT_PRODUCTS_DIR}/crash_generation_server_test\"\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 8DC2EF540486A6940098B216 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F92C565F0ECD116B009BE4BA /* protected_memory_allocator.cc in Sources */, + F92C56630ECD1179009BE4BA /* exception_handler.cc in Sources */, + F92C55D10ECD0064009BE4BA /* Breakpad.mm in Sources */, + F4DAB1DD19F1027100A5A838 /* launch_reporter.cc in Sources */, + F92C56340ECD0DF1009BE4BA /* OnDemandServer.mm in Sources */, + D2F9A4CB121336C7002747C1 /* crash_generation_client.cc in Sources */, + D2F9A4CD121336C7002747C1 /* crash_generation_server.cc in Sources */, + 163201D71443019E00C4DBF5 /* ConfigFile.mm in Sources */, + 162F64F2161C577500CD68D5 /* arch_utilities.cc in Sources */, + 1EEEB6231720829E00F7E689 /* simple_string_dictionary.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D23F4BA812A868A500686C8D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D23F4BB112A868CB00686C8D /* minidump_generator_test_helper.cc in Sources */, + D23F4BB812A868F700686C8D /* MachIPC.mm in Sources */, + D246417012BAA40E005170D0 /* exception_handler.cc in Sources */, + D246417112BAA41C005170D0 /* crash_generation_client.cc in Sources */, + D246417512BAA438005170D0 /* minidump_generator.cc in Sources */, + D246417612BAA43F005170D0 /* dynamic_images.cc in Sources */, + D246417712BAA444005170D0 /* breakpad_nlist_64.cc in Sources */, + D246418812BAA4E3005170D0 /* string_utilities.cc in Sources */, + D246418C12BAA508005170D0 /* minidump_file_writer.cc in Sources */, + D246419012BAA52A005170D0 /* string_conversion.cc in Sources */, + D246419112BAA52F005170D0 /* convert_UTF.c in Sources */, + D246419512BAA54C005170D0 /* file_id.cc in Sources */, + D246419612BAA55A005170D0 /* macho_id.cc in Sources */, + D24641A012BAA67F005170D0 /* macho_walker.cc in Sources */, + D24641AF12BAA82D005170D0 /* macho_utilities.cc in Sources */, + 4D72CA2513DFAE1C006CABE3 /* md5.cc in Sources */, + 4D61A26C14F43D42002D5862 /* bootstrap_compat.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D2F9A41212131EF0002747C1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D2F9A43D12131F55002747C1 /* gmock-all.cc in Sources */, + D2F9A44012131F65002747C1 /* gtest_main.cc in Sources */, + D2F9A44112131F65002747C1 /* gtest-all.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D2F9A52D121383A1002747C1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D2F9A553121383DC002747C1 /* crash_generation_server_test.cc in Sources */, + D2F9A52E121383A1002747C1 /* crash_generation_client.cc in Sources */, + D2F9A52F121383A1002747C1 /* crash_generation_server.cc in Sources */, + D2F9A530121383A1002747C1 /* MachIPC.mm in Sources */, + D2F9A531121383A1002747C1 /* breakpad_nlist_64.cc in Sources */, + D2F9A532121383A1002747C1 /* dynamic_images.cc in Sources */, + D2F9A533121383A1002747C1 /* exception_handler.cc in Sources */, + D2F9A534121383A1002747C1 /* minidump_generator.cc in Sources */, + D2F9A535121383A1002747C1 /* minidump_file_writer.cc in Sources */, + D2F9A536121383A1002747C1 /* convert_UTF.c in Sources */, + D2F9A537121383A1002747C1 /* string_conversion.cc in Sources */, + D2F9A538121383A1002747C1 /* file_id.cc in Sources */, + D2F9A539121383A1002747C1 /* macho_id.cc in Sources */, + D2F9A53A121383A1002747C1 /* macho_utilities.cc in Sources */, + D2F9A53B121383A1002747C1 /* macho_walker.cc in Sources */, + D2F9A53C121383A1002747C1 /* string_utilities.cc in Sources */, + D24641EC12BAC6FB005170D0 /* logging.cc in Sources */, + D24641ED12BAC6FB005170D0 /* minidump.cc in Sources */, + D24641EE12BAC6FB005170D0 /* pathname_stripper.cc in Sources */, + D24641EF12BAC6FB005170D0 /* basic_code_modules.cc in Sources */, + 4D72CA3913DFAE92006CABE3 /* md5.cc in Sources */, + 4D61A26F14F43D48002D5862 /* bootstrap_compat.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F92C53510ECCE349009BE4BA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F92C53B80ECCE7B3009BE4BA /* Inspector.mm in Sources */, + F9286B3A0F7EB25800A4DCC8 /* InspectorMain.mm in Sources */, + 163201E31443029300C4DBF5 /* ConfigFile.mm in Sources */, + 4D61A26B14F43D3C002D5862 /* bootstrap_compat.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F92C56390ECD10B3009BE4BA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F4F916B619F10FFC00B83BE4 /* launch_reporter.cc in Sources */, + 162F64F4161C579B00CD68D5 /* arch_utilities.cc in Sources */, + 162F64F5161C579B00CD68D5 /* arch_utilities.h in Sources */, + D2A5DD301188633800081F03 /* breakpad_nlist_64.cc in Sources */, + F92C563F0ECD10CA009BE4BA /* convert_UTF.c in Sources */, + F92C56400ECD10CA009BE4BA /* dynamic_images.cc in Sources */, + F92C56410ECD10CA009BE4BA /* file_id.cc in Sources */, + F92C56420ECD10CA009BE4BA /* macho_id.cc in Sources */, + F92C56430ECD10CA009BE4BA /* macho_utilities.cc in Sources */, + F92C56440ECD10CA009BE4BA /* macho_walker.cc in Sources */, + F92C56450ECD10CA009BE4BA /* MachIPC.mm in Sources */, + 4D72CA0E13DFAD5C006CABE3 /* md5.cc in Sources */, + F92C56460ECD10CA009BE4BA /* minidump_file_writer.cc in Sources */, + F92C56470ECD10CA009BE4BA /* minidump_generator.cc in Sources */, + F92C56490ECD10CA009BE4BA /* string_utilities.cc in Sources */, + F92C564A0ECD10CA009BE4BA /* string_conversion.cc in Sources */, + 4D61A25F14F43CFC002D5862 /* bootstrap_compat.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F92C569D0ECE04A7009BE4BA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F9C44EA20EF09F93003AEBAA /* HTTPMultipartUpload.m in Sources */, + F92C56A90ECE04C5009BE4BA /* crash_report_sender.m in Sources */, + F9C44EE90EF0A3C1003AEBAA /* GTMLogger.m in Sources */, + 16E02DB8147410F0008C604D /* uploader.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F93803BB0F80820F004D428B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D2C1DBE412AFC270006917BD /* logging.cc in Sources */, + D2C1DBE512AFC270006917BD /* minidump.cc in Sources */, + D2C1DBE612AFC270006917BD /* pathname_stripper.cc in Sources */, + D2C1DBE712AFC270006917BD /* basic_code_modules.cc in Sources */, + D2F9A4DF12133AD9002747C1 /* crash_generation_client.cc in Sources */, + D2F9A4E012133AD9002747C1 /* crash_generation_server.cc in Sources */, + D24BBD291211EDB100F3D417 /* MachIPC.mm in Sources */, + D2A5DD401188640400081F03 /* breakpad_nlist_64.cc in Sources */, + F93803CD0F8083B7004D428B /* dynamic_images.cc in Sources */, + F93803CE0F8083B7004D428B /* exception_handler.cc in Sources */, + F93803CF0F8083B7004D428B /* minidump_generator.cc in Sources */, + F93803D00F8083B7004D428B /* minidump_file_writer.cc in Sources */, + F93803D10F8083B7004D428B /* convert_UTF.c in Sources */, + F93803D20F8083B7004D428B /* string_conversion.cc in Sources */, + F93803D30F8083B7004D428B /* file_id.cc in Sources */, + F93803D40F8083B7004D428B /* macho_id.cc in Sources */, + F93803D50F8083B7004D428B /* macho_utilities.cc in Sources */, + F93803D60F8083B7004D428B /* macho_walker.cc in Sources */, + F93803D70F8083B7004D428B /* string_utilities.cc in Sources */, + D23F4B2E12A7E13200686C8D /* minidump_generator_test.cc in Sources */, + 4D72CA2F13DFAE65006CABE3 /* md5.cc in Sources */, + 4D61A26D14F43D43002D5862 /* bootstrap_compat.cc in Sources */, + 1EEEB62B1720868C00F7E689 /* simple_string_dictionary.cc in Sources */, + 1EEEB62A1720859200F7E689 /* simple_string_dictionary_unittest.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F93DE2CE0F82A67300608B94 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F93DE2D90F82A73500608B94 /* minidump_file_writer.cc in Sources */, + F93DE2DA0F82A73500608B94 /* convert_UTF.c in Sources */, + F93DE2DB0F82A73500608B94 /* string_conversion.cc in Sources */, + F93DE2D80F82A70E00608B94 /* minidump_file_writer_unittest.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F93DE3290F82C55600608B94 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D244536A12426F00009BBCE0 /* logging.cc in Sources */, + D244536B12426F00009BBCE0 /* minidump.cc in Sources */, + D244536C12426F00009BBCE0 /* pathname_stripper.cc in Sources */, + D244536D12426F00009BBCE0 /* basic_code_modules.cc in Sources */, + D2F9A4E112133AE2002747C1 /* crash_generation_client.cc in Sources */, + D2F9A4E212133AE2002747C1 /* crash_generation_server.cc in Sources */, + D24BBD321212CACF00F3D417 /* MachIPC.mm in Sources */, + D2A5DD411188642E00081F03 /* breakpad_nlist_64.cc in Sources */, + F93DE3350F82C66B00608B94 /* dynamic_images.cc in Sources */, + F93DE3360F82C66B00608B94 /* exception_handler.cc in Sources */, + F93DE3370F82C66B00608B94 /* minidump_generator.cc in Sources */, + F93DE3380F82C66B00608B94 /* minidump_file_writer.cc in Sources */, + F93DE3390F82C66B00608B94 /* convert_UTF.c in Sources */, + F93DE33A0F82C66B00608B94 /* string_conversion.cc in Sources */, + F93DE33B0F82C66B00608B94 /* file_id.cc in Sources */, + F93DE33C0F82C66B00608B94 /* macho_id.cc in Sources */, + F93DE33D0F82C66B00608B94 /* macho_utilities.cc in Sources */, + F93DE33E0F82C66B00608B94 /* macho_walker.cc in Sources */, + F93DE33F0F82C66B00608B94 /* string_utilities.cc in Sources */, + D2F9A3D51212F87C002747C1 /* exception_handler_test.cc in Sources */, + D244540B12439BA0009BBCE0 /* memory_unittest.cc in Sources */, + 4D72CA3813DFAE91006CABE3 /* md5.cc in Sources */, + 4D61A26E14F43D45002D5862 /* bootstrap_compat.cc in Sources */, + 1EEEB6271720831E00F7E689 /* BreakpadFramework_Test.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F9C44DA20EF060A8003AEBAA /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F9C44DB20EF07288003AEBAA /* Controller.m in Sources */, + F9C44DB60EF07288003AEBAA /* main.m in Sources */, + F9C44DB70EF07288003AEBAA /* TestClass.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F9C77DD60F7DD5CF0045F7DB /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F9C77E130F7DDF810045F7DB /* GTMSenTestCase.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 8B31023911F0CF0600FCF3E4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = all_unittests; + targetProxy = 8B31023811F0CF0600FCF3E4 /* PBXContainerItemProxy */; + }; + 8B31051711F1010E00FCF3E4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F93803BD0F80820F004D428B /* generator_test */; + targetProxy = 8B31051611F1010E00FCF3E4 /* PBXContainerItemProxy */; + }; + 8B31051911F1010E00FCF3E4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F93DE2D00F82A67300608B94 /* minidump_file_writer_unittest */; + targetProxy = 8B31051811F1010E00FCF3E4 /* PBXContainerItemProxy */; + }; + 8B31051B11F1010E00FCF3E4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F93DE32B0F82C55600608B94 /* handler_test */; + targetProxy = 8B31051A11F1010E00FCF3E4 /* PBXContainerItemProxy */; + }; + 8B31051D11F1010E00FCF3E4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = macho_dump; + targetProxy = 8B31051C11F1010E00FCF3E4 /* PBXContainerItemProxy */; + }; + 8B31051F11F1010E00FCF3E4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = minidump_upload; + targetProxy = 8B31051E11F1010E00FCF3E4 /* PBXContainerItemProxy */; + }; + D23F4B3012A7E16200686C8D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D2F9A41412131EF0002747C1 /* gtest */; + targetProxy = D23F4B2F12A7E16200686C8D /* PBXContainerItemProxy */; + }; + D23F4BBA12A8694C00686C8D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D23F4BAA12A868A500686C8D /* minidump_generator_test_helper */; + targetProxy = D23F4BB912A8694C00686C8D /* PBXContainerItemProxy */; + }; + D2F9A44312131F80002747C1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D2F9A41412131EF0002747C1 /* gtest */; + targetProxy = D2F9A44212131F80002747C1 /* PBXContainerItemProxy */; + }; + D2F9A52B121383A1002747C1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D2F9A41412131EF0002747C1 /* gtest */; + targetProxy = D2F9A52C121383A1002747C1 /* PBXContainerItemProxy */; + }; + D2F9A5DF12142A6A002747C1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D2F9A52A121383A1002747C1 /* crash_generation_server_test */; + targetProxy = D2F9A5DE12142A6A002747C1 /* PBXContainerItemProxy */; + }; + F91AF6380FD60A74009D8BE2 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8DC2EF4F0486A6940098B216 /* Breakpad */; + targetProxy = F91AF6370FD60A74009D8BE2 /* PBXContainerItemProxy */; + }; + F92C564E0ECD10E5009BE4BA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F92C563B0ECD10B3009BE4BA /* breakpadUtilities */; + targetProxy = F92C564D0ECD10E5009BE4BA /* PBXContainerItemProxy */; + }; + F92C56860ECD15EF009BE4BA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F92C563B0ECD10B3009BE4BA /* breakpadUtilities */; + targetProxy = F92C56850ECD15EF009BE4BA /* PBXContainerItemProxy */; + }; + F92C56880ECD15F1009BE4BA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F92C53530ECCE349009BE4BA /* Inspector */; + targetProxy = F92C56870ECD15F1009BE4BA /* PBXContainerItemProxy */; + }; + F93DE2FC0F82C3C600608B94 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F93803BD0F80820F004D428B /* generator_test */; + targetProxy = F93DE2FB0F82C3C600608B94 /* PBXContainerItemProxy */; + }; + F93DE3700F82CC1300608B94 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F93DE32B0F82C55600608B94 /* handler_test */; + targetProxy = F93DE36F0F82CC1300608B94 /* PBXContainerItemProxy */; + }; + F93DE3A70F830D1D00608B94 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F9C77DD90F7DD5CF0045F7DB /* UnitTests */; + targetProxy = F93DE3A60F830D1D00608B94 /* PBXContainerItemProxy */; + }; + F94585880F78232B009A47BF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8DC2EF4F0486A6940098B216 /* Breakpad */; + targetProxy = F94585870F78232B009A47BF /* PBXContainerItemProxy */; + }; + F945858A0F78232E009A47BF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F92C53530ECCE349009BE4BA /* Inspector */; + targetProxy = F94585890F78232E009A47BF /* PBXContainerItemProxy */; + }; + F945858C0F782330009A47BF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F92C563B0ECD10B3009BE4BA /* breakpadUtilities */; + targetProxy = F945858B0F782330009A47BF /* PBXContainerItemProxy */; + }; + F945858E0F782333009A47BF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F92C569F0ECE04A7009BE4BA /* crash_report_sender */; + targetProxy = F945858D0F782333009A47BF /* PBXContainerItemProxy */; + }; + F94585900F782336009A47BF /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F9C44DA40EF060A8003AEBAA /* BreakpadTest */; + targetProxy = F945858F0F782336009A47BF /* PBXContainerItemProxy */; + }; + F95BB8B3101F94D300AA053B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = dump_syms; + targetProxy = F95BB8B2101F94D300AA053B /* PBXContainerItemProxy */; + }; + F95BB8B5101F94D300AA053B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = symupload; + targetProxy = F95BB8B4101F94D300AA053B /* PBXContainerItemProxy */; + }; + F95BB8B7101F94D300AA053B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = crash_report; + targetProxy = F95BB8B6101F94D300AA053B /* PBXContainerItemProxy */; + }; + F9C44E1A0EF0790F003AEBAA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8DC2EF4F0486A6940098B216 /* Breakpad */; + targetProxy = F9C44E190EF0790F003AEBAA /* PBXContainerItemProxy */; + }; + F9C44E970EF09F4B003AEBAA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = F92C569F0ECE04A7009BE4BA /* crash_report_sender */; + targetProxy = F9C44E960EF09F4B003AEBAA /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33880C7E0F9E097100817F82 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 33880C7F0F9E097100817F82 /* English */, + DE43468711C7295D004F095F /* da */, + DE43468611C72958004F095F /* de */, + DE43468811C7295F004F095F /* es */, + DE43468911C72964004F095F /* fr */, + DE43468A11C72967004F095F /* it */, + DE43468B11C7296B004F095F /* ja */, + DE43468C11C7296D004F095F /* nl */, + DE43468D11C7296F004F095F /* no */, + DE43468E11C72971004F095F /* sl */, + DE43468F11C72973004F095F /* sv */, + DE43469011C72976004F095F /* tr */, + ); + name = InfoPlist.strings; + sourceTree = "<group>"; + }; + F945849C0F280E3C009A47BF /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + F945849D0F280E3C009A47BF /* English */, + DE43467411C72855004F095F /* da */, + DE43467511C72857004F095F /* de */, + DE43467611C7285B004F095F /* es */, + DE43467711C72862004F095F /* fr */, + DE43467811C72869004F095F /* it */, + DE43467E11C728DC004F095F /* ja */, + DE43467911C7286D004F095F /* nl */, + DE43467A11C72873004F095F /* no */, + DE43467B11C72877004F095F /* sl */, + DE43467C11C7287A004F095F /* sv */, + DE43467F11C728E1004F095F /* tr */, + ); + name = Localizable.strings; + sourceTree = "<group>"; + }; + F9C44DB80EF072A0003AEBAA /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + F9C44DB90EF072A0003AEBAA /* English */, + ); + name = InfoPlist.strings; + sourceTree = "<group>"; + }; + F9C44DBA0EF072A0003AEBAA /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + F9C44DBB0EF072A0003AEBAA /* English */, + ); + name = MainMenu.xib; + sourceTree = "<group>"; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 1DEB91AE08733DA50010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = Framework/Breakpad_Prefix.pch; + HEADER_SEARCH_PATHS = ../..; + INFOPLIST_FILE = Framework/Info.plist; + INSTALL_PATH = "@executable_path/../Frameworks"; + PRODUCT_NAME = Breakpad; + WRAPPER_EXTENSION = framework; + }; + name = Debug; + }; + 1DEB91AF08733DA50010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = Framework/Breakpad_Prefix.pch; + HEADER_SEARCH_PATHS = ../..; + INFOPLIST_FILE = Framework/Info.plist; + INSTALL_PATH = "@executable_path/../Frameworks"; + PRODUCT_NAME = Breakpad; + WRAPPER_EXTENSION = framework; + }; + name = Release; + }; + 1DEB91B208733DA50010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8B31027711F0D3AF00FCF3E4 /* BreakpadDebug.xcconfig */; + buildSettings = { + GCC_TREAT_WARNINGS_AS_ERRORS = NO; + SDKROOT = macosx10.5; + }; + name = Debug; + }; + 1DEB91B308733DA50010E9CD /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8B31027811F0D3AF00FCF3E4 /* BreakpadRelease.xcconfig */; + buildSettings = { + GCC_TREAT_WARNINGS_AS_ERRORS = NO; + }; + name = Release; + }; + D23F4BAD12A868A600686C8D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_MODEL_TUNING = G5; + GCC_OPTIMIZATION_LEVEL = 0; + HEADER_SEARCH_PATHS = ../..; + INSTALL_PATH = /usr/local/bin; + PREBINDING = NO; + PRODUCT_NAME = minidump_generator_test_helper; + }; + name = Debug; + }; + D23F4BAE12A868A600686C8D /* Debug With Code Coverage */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_MODEL_TUNING = G5; + HEADER_SEARCH_PATHS = ../..; + INSTALL_PATH = /usr/local/bin; + PREBINDING = NO; + PRODUCT_NAME = minidump_generator_test_helper; + }; + name = "Debug With Code Coverage"; + }; + D23F4BAF12A868A600686C8D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_MODEL_TUNING = G5; + HEADER_SEARCH_PATHS = ../..; + INSTALL_PATH = /usr/local/bin; + PREBINDING = NO; + PRODUCT_NAME = minidump_generator_test_helper; + ZERO_LINK = NO; + }; + name = Release; + }; + D2F9A41612131EF0002747C1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + ../../testing, + ../../testing/include, + ../../testing/gtest, + ../../testing/gtest/include, + ); + PREBINDING = NO; + PRODUCT_NAME = gtest; + }; + name = Debug; + }; + D2F9A41712131EF0002747C1 /* Debug With Code Coverage */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + ../../testing, + ../../testing/include, + ../../testing/gtest, + ../../testing/gtest/include, + ); + PREBINDING = NO; + PRODUCT_NAME = gtest; + }; + name = "Debug With Code Coverage"; + }; + D2F9A41812131EF0002747C1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + ../../testing, + ../../testing/include, + ../../testing/gtest, + ../../testing/gtest/include, + ); + PREBINDING = NO; + PRODUCT_NAME = gtest; + ZERO_LINK = NO; + }; + name = Release; + }; + D2F9A543121383A1002747C1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + DEBUG_INFORMATION_FORMAT = dwarf; + GCC_INLINES_ARE_PRIVATE_EXTERN = NO; + GCC_PREPROCESSOR_DEFINITIONS = "BP_LOGGING_INCLUDE=\\\"client/mac/tests/testlogging.h\\\""; + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + HEADER_SEARCH_PATHS = ( + ../.., + ../../testing, + ../../testing/include, + ../../testing/gtest, + ../../testing/gtest/include, + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/build/Debug\"", + ); + PRODUCT_NAME = crash_generation_server_test; + }; + name = Debug; + }; + D2F9A544121383A1002747C1 /* Debug With Code Coverage */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + ../.., + ../../testing, + ../../testing/include, + ../../testing/gtest, + ../../testing/gtest/include, + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\\\"$(SRCROOT)/build/Debug\\\"", + ); + PRODUCT_NAME = crash_generation_server_test; + }; + name = "Debug With Code Coverage"; + }; + D2F9A545121383A1002747C1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + ../.., + ../../testing, + ../../testing/include, + ../../testing/gtest, + ../../testing/gtest/include, + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\\\"$(SRCROOT)/build/Debug\\\"", + ); + PRODUCT_NAME = crash_generation_server_test; + }; + name = Release; + }; + F92C53560ECCE34A009BE4BA /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ../..; + PRODUCT_NAME = Inspector; + }; + name = Debug; + }; + F92C53570ECCE34A009BE4BA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ../..; + PRODUCT_NAME = Inspector; + }; + name = Release; + }; + F92C563D0ECD10B3009BE4BA /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + HEADER_SEARCH_PATHS = ../..; + LD_DYLIB_INSTALL_NAME = "@executable_path/../Resources/$(EXECUTABLE_PATH)"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-headerpad_max_install_names", + ); + PRODUCT_NAME = breakpadUtilities; + }; + name = Debug; + }; + F92C563E0ECD10B3009BE4BA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + HEADER_SEARCH_PATHS = ../..; + LD_DYLIB_INSTALL_NAME = "@executable_path/../Resources/$(EXECUTABLE_PATH)"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-headerpad_max_install_names", + ); + PRODUCT_NAME = breakpadUtilities; + }; + name = Release; + }; + F92C56A30ECE04A8009BE4BA /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ../..; + INFOPLIST_FILE = "sender/crash_report_sender-Info.plist"; + PRODUCT_NAME = crash_report_sender; + }; + name = Debug; + }; + F92C56A40ECE04A8009BE4BA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ../..; + INFOPLIST_FILE = "sender/crash_report_sender-Info.plist"; + PRODUCT_NAME = crash_report_sender; + }; + name = Release; + }; + F93803C00F808210004D428B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_PREPROCESSOR_DEFINITIONS = "BP_LOGGING_INCLUDE=\\\"client/mac/tests/testlogging.h\\\""; + HEADER_SEARCH_PATHS = ( + ../.., + ../../.., + ../../testing, + ../../testing/include, + ../../testing/gtest, + ../../testing/gtest/include, + ); + PRODUCT_NAME = generator_test; + }; + name = Debug; + }; + F93803C10F808210004D428B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + ../.., + ../../.., + ../../testing, + ../../testing/include, + ../../testing/gtest, + ../../testing/gtest/include, + ); + PRODUCT_NAME = generator_test; + }; + name = Release; + }; + F93DE2D30F82A67400608B94 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ../..; + PRODUCT_NAME = minidump_file_writer_unittest; + }; + name = Debug; + }; + F93DE2D40F82A67400608B94 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ../..; + PRODUCT_NAME = minidump_file_writer_unittest; + }; + name = Release; + }; + F93DE32E0F82C55700608B94 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + DEBUG_INFORMATION_FORMAT = dwarf; + GCC_INLINES_ARE_PRIVATE_EXTERN = NO; + GCC_PREPROCESSOR_DEFINITIONS = "BP_LOGGING_INCLUDE=\\\"client/mac/tests/testlogging.h\\\""; + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + HEADER_SEARCH_PATHS = ( + ../../.., + ../.., + ../../testing, + ../../testing/include, + ../../testing/gtest, + ../../testing/gtest/include, + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/build/Debug\"", + ); + PRODUCT_NAME = handler_test; + }; + name = Debug; + }; + F93DE32F0F82C55700608B94 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + ../../.., + ../.., + ../../testing, + ../../testing/include, + ../../testing/gtest, + ../../testing/gtest/include, + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/build/Debug\"", + ); + PRODUCT_NAME = handler_test; + }; + name = Release; + }; + F93DE3B90F830E7000608B94 /* Debug With Code Coverage */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8B31027711F0D3AF00FCF3E4 /* BreakpadDebug.xcconfig */; + buildSettings = { + GCC_TREAT_WARNINGS_AS_ERRORS = NO; + }; + name = "Debug With Code Coverage"; + }; + F93DE3BA0F830E7000608B94 /* Debug With Code Coverage */ = { + isa = XCBuildConfiguration; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = Framework/Breakpad_Prefix.pch; + HEADER_SEARCH_PATHS = ../..; + INFOPLIST_FILE = Framework/Info.plist; + INSTALL_PATH = "@executable_path/../Frameworks"; + PRODUCT_NAME = Breakpad; + WRAPPER_EXTENSION = framework; + }; + name = "Debug With Code Coverage"; + }; + F93DE3BB0F830E7000608B94 /* Debug With Code Coverage */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ../..; + PRODUCT_NAME = Inspector; + }; + name = "Debug With Code Coverage"; + }; + F93DE3BC0F830E7000608B94 /* Debug With Code Coverage */ = { + isa = XCBuildConfiguration; + buildSettings = { + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + HEADER_SEARCH_PATHS = ../..; + LD_DYLIB_INSTALL_NAME = "@executable_path/../Resources/$(EXECUTABLE_PATH)"; + OTHER_LDFLAGS = ( + "$(inherited)", + "-headerpad_max_install_names", + ); + PRODUCT_NAME = breakpadUtilities; + }; + name = "Debug With Code Coverage"; + }; + F93DE3BD0F830E7000608B94 /* Debug With Code Coverage */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ../..; + INFOPLIST_FILE = "sender/crash_report_sender-Info.plist"; + PRODUCT_NAME = crash_report_sender; + }; + name = "Debug With Code Coverage"; + }; + F93DE3BE0F830E7000608B94 /* Debug With Code Coverage */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/build/$(CONFIGURATION)"; + INFOPLIST_FILE = testapp/Info.plist; + PRODUCT_NAME = BreakpadTest; + }; + name = "Debug With Code Coverage"; + }; + F93DE3BF0F830E7000608B94 /* Debug With Code Coverage */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = All; + }; + name = "Debug With Code Coverage"; + }; + F93DE3C00F830E7000608B94 /* Debug With Code Coverage */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\"$(DEVELOPER_FRAMEWORKS_DIR)\"", + ); + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + HEADER_SEARCH_PATHS = ../..; + INFOPLIST_FILE = "UnitTests-Info.plist"; + PRODUCT_NAME = UnitTests; + WRAPPER_EXTENSION = octest; + }; + name = "Debug With Code Coverage"; + }; + F93DE3C10F830E7000608B94 /* Debug With Code Coverage */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + ../.., + ../../.., + ../../testing, + ../../testing/include, + ../../testing/gtest, + ../../testing/gtest/include, + ); + PRODUCT_NAME = generator_test; + }; + name = "Debug With Code Coverage"; + }; + F93DE3C20F830E7000608B94 /* Debug With Code Coverage */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ../..; + PRODUCT_NAME = minidump_file_writer_unittest; + }; + name = "Debug With Code Coverage"; + }; + F93DE3C30F830E7000608B94 /* Debug With Code Coverage */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + ../../.., + ../.., + ../../testing, + ../../testing/include, + ../../testing/gtest, + ../../testing/gtest/include, + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/build/Debug\"", + ); + PRODUCT_NAME = handler_test; + }; + name = "Debug With Code Coverage"; + }; + F94585850F782326009A47BF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = All; + }; + name = Debug; + }; + F94585860F782326009A47BF /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = All; + }; + name = Release; + }; + F9C44DA80EF060A8003AEBAA /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/build/$(CONFIGURATION)"; + INFOPLIST_FILE = testapp/Info.plist; + PRODUCT_NAME = BreakpadTest; + }; + name = Debug; + }; + F9C44DA90EF060A8003AEBAA /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/build/$(CONFIGURATION)"; + INFOPLIST_FILE = testapp/Info.plist; + PRODUCT_NAME = BreakpadTest; + }; + name = Release; + }; + F9C77DDC0F7DD5D00045F7DB /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\"$(DEVELOPER_FRAMEWORKS_DIR)\"", + ); + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + INFOPLIST_FILE = "UnitTests-Info.plist"; + PRODUCT_NAME = UnitTests; + WRAPPER_EXTENSION = octest; + }; + name = Debug; + }; + F9C77DDD0F7DD5D00045F7DB /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\"$(DEVELOPER_FRAMEWORKS_DIR)\"", + ); + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + INFOPLIST_FILE = "UnitTests-Info.plist"; + PRODUCT_NAME = UnitTests; + WRAPPER_EXTENSION = octest; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DEB91AD08733DA50010E9CD /* Build configuration list for PBXNativeTarget "Breakpad" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB91AE08733DA50010E9CD /* Debug */, + F93DE3BA0F830E7000608B94 /* Debug With Code Coverage */, + 1DEB91AF08733DA50010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1DEB91B108733DA50010E9CD /* Build configuration list for PBXProject "Breakpad" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB91B208733DA50010E9CD /* Debug */, + F93DE3B90F830E7000608B94 /* Debug With Code Coverage */, + 1DEB91B308733DA50010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D23F4BB012A868C400686C8D /* Build configuration list for PBXNativeTarget "minidump_generator_test_helper" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D23F4BAD12A868A600686C8D /* Debug */, + D23F4BAE12A868A600686C8D /* Debug With Code Coverage */, + D23F4BAF12A868A600686C8D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D2F9A42D12131F0E002747C1 /* Build configuration list for PBXNativeTarget "gtest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D2F9A41612131EF0002747C1 /* Debug */, + D2F9A41712131EF0002747C1 /* Debug With Code Coverage */, + D2F9A41812131EF0002747C1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D2F9A542121383A1002747C1 /* Build configuration list for PBXNativeTarget "crash_generation_server_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D2F9A543121383A1002747C1 /* Debug */, + D2F9A544121383A1002747C1 /* Debug With Code Coverage */, + D2F9A545121383A1002747C1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F92C53580ECCE36D009BE4BA /* Build configuration list for PBXNativeTarget "Inspector" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F92C53560ECCE34A009BE4BA /* Debug */, + F93DE3BB0F830E7000608B94 /* Debug With Code Coverage */, + F92C53570ECCE34A009BE4BA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F92C56670ECD11A3009BE4BA /* Build configuration list for PBXNativeTarget "breakpadUtilities" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F92C563D0ECD10B3009BE4BA /* Debug */, + F93DE3BC0F830E7000608B94 /* Debug With Code Coverage */, + F92C563E0ECD10B3009BE4BA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F92C56A50ECE04A8009BE4BA /* Build configuration list for PBXNativeTarget "crash_report_sender" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F92C56A30ECE04A8009BE4BA /* Debug */, + F93DE3BD0F830E7000608B94 /* Debug With Code Coverage */, + F92C56A40ECE04A8009BE4BA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F93803C40F80822E004D428B /* Build configuration list for PBXNativeTarget "generator_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F93803C00F808210004D428B /* Debug */, + F93DE3C10F830E7000608B94 /* Debug With Code Coverage */, + F93803C10F808210004D428B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F93DE2D60F82A67700608B94 /* Build configuration list for PBXNativeTarget "minidump_file_writer_unittest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F93DE2D30F82A67400608B94 /* Debug */, + F93DE3C20F830E7000608B94 /* Debug With Code Coverage */, + F93DE2D40F82A67400608B94 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F93DE3320F82C5D800608B94 /* Build configuration list for PBXNativeTarget "handler_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F93DE32E0F82C55700608B94 /* Debug */, + F93DE3C30F830E7000608B94 /* Debug With Code Coverage */, + F93DE32F0F82C55700608B94 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F94585930F78235C009A47BF /* Build configuration list for PBXAggregateTarget "All" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F94585850F782326009A47BF /* Debug */, + F93DE3BF0F830E7000608B94 /* Debug With Code Coverage */, + F94585860F782326009A47BF /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F9C44DAA0EF060A9003AEBAA /* Build configuration list for PBXNativeTarget "BreakpadTest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F9C44DA80EF060A8003AEBAA /* Debug */, + F93DE3BE0F830E7000608B94 /* Debug With Code Coverage */, + F9C44DA90EF060A8003AEBAA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F9C77DDE0F7DD5D00045F7DB /* Build configuration list for PBXNativeTarget "UnitTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F9C77DDC0F7DD5D00045F7DB /* Debug */, + F93DE3C00F830E7000608B94 /* Debug With Code Coverage */, + F9C77DDD0F7DD5D00045F7DB /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 0867D690FE84028FC02AAC07 /* Project object */; +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/Framework/Breakpad.h b/toolkit/crashreporter/google-breakpad/src/client/mac/Framework/Breakpad.h new file mode 100644 index 000000000..dc7e45d1c --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/Framework/Breakpad.h @@ -0,0 +1,285 @@ +// Copyright (c) 2006, 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. +// +// Framework to provide a simple C API to crash reporting for +// applications. By default, if any machine-level exception (e.g., +// EXC_BAD_ACCESS) occurs, it will be handled by the BreakpadRef +// object as follows: +// +// 1. Create a minidump file (see Breakpad for details) +// 2. Prompt the user (using CFUserNotification) +// 3. Invoke a command line reporting tool to send the minidump to a +// server +// +// By specifying parameters to the BreakpadCreate function, you can +// modify the default behavior to suit your needs and wants and +// desires. + +// A service name associated with the original bootstrap parent port, saved in +// OnDemandServer and restored in Inspector. +#define BREAKPAD_BOOTSTRAP_PARENT_PORT "com.Breakpad.BootstrapParent" + +typedef void *BreakpadRef; + +#ifdef __cplusplus +extern "C" { +#endif + +#include <CoreFoundation/CoreFoundation.h> +#include <Foundation/Foundation.h> + +#include "BreakpadDefines.h" + +// Optional user-defined function to dec to decide if we should handle +// this crash or forward it along. +// Return true if you want Breakpad to handle it. +// Return false if you want Breakpad to skip it +// The exception handler always returns false, as if SEND_AND_EXIT were false +// (which means the next exception handler will take the exception) +typedef bool (*BreakpadFilterCallback)(int exception_type, + int exception_code, + mach_port_t crashing_thread, + void *context); + +// Create a new BreakpadRef object and install it as an exception +// handler. The |parameters| will typically be the contents of your +// bundle's Info.plist. +// +// You can also specify these additional keys for customizable behavior: +// Key: Value: +// BREAKPAD_PRODUCT Product name (e.g., "MyAwesomeProduct") +// This one is used as the key to identify +// the product when uploading. Falls back to +// CFBundleName if not specified. +// REQUIRED +// +// BREAKPAD_PRODUCT_DISPLAY This is the display name, e.g. a pretty +// name for the product when the crash_sender +// pops up UI for the user. Falls back first to +// CFBundleDisplayName and then to +// BREAKPAD_PRODUCT if not specified. +// +// BREAKPAD_VERSION Product version (e.g., 1.2.3), used +// as metadata for crash report. Falls back to +// CFBundleVersion if not specified. +// REQUIRED +// +// BREAKPAD_VENDOR Vendor name, used in UI (e.g. "A report has +// been created that you can send to <vendor>") +// +// BREAKPAD_URL URL destination for reporting +// REQUIRED +// +// BREAKPAD_REPORT_INTERVAL # of seconds between sending +// reports. If an additional report is +// generated within this time, it will +// be ignored. Default: 3600sec. +// Specify 0 to send all reports. +// +// BREAKPAD_SKIP_CONFIRM If true, the reporter will send the report +// without any user intervention. +// Defaults to NO +// +// BREAKPAD_CONFIRM_TIMEOUT Number of seconds before the upload +// confirmation dialog will be automatically +// dismissed (cancelling the upload). +// Default: 300 seconds (min of 60). +// Specify 0 to prevent timeout. +// +// BREAKPAD_SEND_AND_EXIT If true, the handler will exit after sending. +// This will prevent any other handler (e.g., +// CrashReporter) from getting the crash. +// Defaults TO YES +// +// BREAKPAD_DUMP_DIRECTORY The directory to store crash-dumps +// in. By default, we use +// ~/Library/Breakpad/<BREAKPAD_PRODUCT> +// The path you specify here is tilde-expanded. +// +// BREAKPAD_INSPECTOR_LOCATION The full path to the Inspector executable. +// Defaults to <Framework resources>/Inspector +// +// BREAKPAD_REPORTER_EXE_LOCATION The full path to the Reporter/sender +// executable. +// Default: +// <Framework Resources>/crash_report_sender.app +// +// BREAKPAD_LOGFILES Indicates an array of log file paths that +// should be uploaded at crash time. +// +// BREAKPAD_REQUEST_COMMENTS If true, the message dialog will have a +// text box for the user to enter comments. +// Default: NO +// +// BREAKPAD_REQUEST_EMAIL If true and BREAKPAD_REQUEST_COMMENTS is also +// true, the message dialog will have a text +// box for the user to enter their email address. +// Default: NO +// +// BREAKPAD_SERVER_TYPE A parameter that tells Breakpad how to +// rewrite the upload parameters for a specific +// server type. The currently valid values are +// 'socorro' or 'google'. If you want to add +// other types, see the function in +// crash_report_sender.m that maps parameters to +// URL parameters. Defaults to 'google'. +// +// BREAKPAD_SERVER_PARAMETER_DICT A plist dictionary of static +// parameters that are uploaded to the +// server. The parameters are sent as +// is to the crash server. Their +// content isn't added to the minidump +// but pass as URL parameters when +// uploading theminidump to the crash +// server. +// +// BREAKPAD_IN_PROCESS A boolean NSNumber value. If YES, Breakpad +// will write the dump file in-process and then +// launch the reporter executable as a child +// process. +//============================================================================= +// The BREAKPAD_PRODUCT, BREAKPAD_VERSION and BREAKPAD_URL are +// required to have non-NULL values. By default, the BREAKPAD_PRODUCT +// will be the CFBundleName and the BREAKPAD_VERSION will be the +// CFBundleVersion when these keys are present in the bundle's +// Info.plist, which is usually passed in to BreakpadCreate() as an +// NSDictionary (you could also pass in another dictionary that had +// the same keys configured). If the BREAKPAD_PRODUCT or +// BREAKPAD_VERSION are ultimately undefined, BreakpadCreate() will +// fail. You have been warned. +// +// If you are running in a debugger, Breakpad will not install, unless the +// BREAKPAD_IGNORE_DEBUGGER envionment variable is set and/or non-zero. +// +// The BREAKPAD_SKIP_CONFIRM and BREAKPAD_SEND_AND_EXIT default +// values are NO and YES. However, they can be controlled by setting their +// values in a user or global plist. +// +// It's easiest to use Breakpad via the Framework, but if you're compiling the +// code in directly, BREAKPAD_INSPECTOR_LOCATION and +// BREAKPAD_REPORTER_EXE_LOCATION allow you to specify custom paths +// to the helper executables. +// +//============================================================================= +// The following are NOT user-supplied but are documented here for +// completeness. They are calculated by Breakpad during initialization & +// crash-dump generation, or entered in by the user. +// +// BREAKPAD_PROCESS_START_TIME The time, in seconds since the Epoch, the +// process started +// +// BREAKPAD_PROCESS_CRASH_TIME The time, in seconds since the Epoch, the +// process crashed. +// +// BREAKPAD_PROCESS_UP_TIME The total time in milliseconds the process +// has been running. This parameter is not +// set until the crash-dump-generation phase. +// +// BREAKPAD_LOGFILE_KEY_PREFIX Used to find out which parameters in the +// parameter dictionary correspond to log +// file paths. +// +// BREAKPAD_SERVER_PARAMETER_PREFIX This prefix is used by Breakpad +// internally, because Breakpad uses +// the same dictionary internally to +// track both its internal +// configuration parameters and +// parameters meant to be uploaded +// to the server. This string is +// used internally by Breakpad to +// prefix user-supplied parameter +// names so those can be sent to the +// server without leaking Breakpad's +// internal values. +// +// BREAKPAD_ON_DEMAND Used internally to indicate to the +// Reporter that we're sending on-demand, +// not as result of a crash. +// +// BREAKPAD_COMMENTS The text the user provided as comments. +// Only used in crash_report_sender. + +// Returns a new BreakpadRef object on success, NULL otherwise. +BreakpadRef BreakpadCreate(NSDictionary *parameters); + +// Uninstall and release the data associated with |ref|. +void BreakpadRelease(BreakpadRef ref); + +// Clients may set an optional callback which gets called when a crash +// occurs. The callback function should return |true| if we should +// handle the crash, generate a crash report, etc. or |false| if we +// should ignore it and forward the crash (normally to CrashReporter). +// Context is a pointer to arbitrary data to make the callback with. +void BreakpadSetFilterCallback(BreakpadRef ref, + BreakpadFilterCallback callback, + void *context); + +// User defined key and value string storage. Generally this is used +// to configure Breakpad's internal operation, such as whether the +// crash_sender should prompt the user, or the filesystem location for +// the minidump file. See Breakpad.h for some parameters that can be +// set. Anything longer than 255 bytes will be truncated. Note that +// the string is converted to UTF8 before truncation, so any multibyte +// character that straddles the 255(256 - 1 for terminator) byte limit +// will be mangled. +// +// A maximum number of 64 key/value pairs are supported. An assert() +// will fire if more than this number are set. Unfortunately, right +// now, the same dictionary is used for both Breakpad's parameters AND +// the Upload parameters. +// +// TODO (nealsid): Investigate how necessary this is if we don't +// automatically upload parameters to the server anymore. +// TODO (nealsid): separate server parameter dictionary from the +// dictionary used to configure Breakpad, and document limits for each +// independently. +void BreakpadSetKeyValue(BreakpadRef ref, NSString *key, NSString *value); +NSString *BreakpadKeyValue(BreakpadRef ref, NSString *key); +void BreakpadRemoveKeyValue(BreakpadRef ref, NSString *key); + +// You can use this method to specify parameters that will be uploaded +// to the crash server. They will be automatically encoded as +// necessary. Note that as mentioned above there are limits on both +// the number of keys and their length. +void BreakpadAddUploadParameter(BreakpadRef ref, NSString *key, + NSString *value); + +// This method will remove a previously-added parameter from the +// upload parameter set. +void BreakpadRemoveUploadParameter(BreakpadRef ref, NSString *key); + +// Add a log file for Breakpad to read and send upon crash dump +void BreakpadAddLogFile(BreakpadRef ref, NSString *logPathname); + +// Generate a minidump and send +void BreakpadGenerateAndSendReport(BreakpadRef ref); + +#ifdef __cplusplus +} +#endif diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/Framework/Breakpad.mm b/toolkit/crashreporter/google-breakpad/src/client/mac/Framework/Breakpad.mm new file mode 100644 index 000000000..1d2e519bb --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/Framework/Breakpad.mm @@ -0,0 +1,1043 @@ +// Copyright (c) 2006, 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/mac/Framework/Breakpad.h" + +#include <assert.h> +#import <Foundation/Foundation.h> +#include <pthread.h> +#include <sys/stat.h> +#include <sys/sysctl.h> + +#import "client/mac/crash_generation/Inspector.h" +#import "client/mac/handler/exception_handler.h" +#import "client/mac/Framework/Breakpad.h" +#import "client/mac/Framework/OnDemandServer.h" +#import "client/mac/handler/protected_memory_allocator.h" +#include "common/mac/launch_reporter.h" +#import "common/mac/MachIPC.h" +#import "common/simple_string_dictionary.h" + +#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::MachPortSender; +using google_breakpad::MachReceiveMessage; +using google_breakpad::MachSendMessage; +using google_breakpad::ReceivePort; +using google_breakpad::SimpleStringDictionary; + +//============================================================================= +// 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); + + void GenerateAndSendReport(); + + void SetFilterCallback(BreakpadFilterCallback callback, void *context) { + filter_callback_ = callback; + filter_callback_context_ = context; + } + + private: + Breakpad() + : handler_(NULL), + config_params_(NULL), + send_and_exit_(true), + filter_callback_(NULL), + filter_callback_context_(NULL) { + inspector_path_[0] = 0; + } + + bool Initialize(NSDictionary *parameters); + bool InitializeInProcess(NSDictionary *parameters); + bool InitializeOutOfProcess(NSDictionary *parameters); + + bool ExtractParameters(NSDictionary *parameters); + + // Dispatches to HandleException() + static bool ExceptionHandlerDirectCallback(void *context, + int exception_type, + int exception_code, + int exception_subcode, + mach_port_t crashing_thread); + + bool HandleException(int exception_type, + int exception_code, + int exception_subcode, + mach_port_t crashing_thread); + + // Dispatches to HandleMinidump(). + // This gets called instead of ExceptionHandlerDirectCallback when running + // with the BREAKPAD_IN_PROCESS option. + static bool HandleMinidumpCallback(const char *dump_dir, + const char *minidump_id, + void *context, + bool succeeded); + + // This is only used when BREAKPAD_IN_PROCESS is YES. + bool HandleMinidump(const char *dump_dir, const char *minidump_id); + + // 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) + + char inspector_path_[PATH_MAX]; // Path to inspector tool + + SimpleStringDictionary *config_params_; // Create parameters (STRONG) + + OnDemandServer inspector_; + + bool send_and_exit_; // Exit after sending, if true + + BreakpadFilterCallback filter_callback_; + void *filter_callback_context_; +}; + +#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::ExceptionHandlerDirectCallback(void *context, + int exception_type, + int exception_code, + int exception_subcode, + mach_port_t crashing_thread) { + 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) + return false; + + return breakpad->HandleException( exception_type, + exception_code, + exception_subcode, + crashing_thread); +} + +//============================================================================= +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); +} + +//============================================================================= +#pragma mark - + +#include <dlfcn.h> + +//============================================================================= +// Returns the pathname to the Resources directory for this version of +// Breakpad which we are now running. +// +// Don't make the function static, since _dyld_lookup_and_bind_fully needs a +// simple non-static C name +// +extern "C" { +NSString * GetResourcePath(); +NSString * GetResourcePath() { + NSString *resourcePath = nil; + + // If there are multiple breakpads installed then calling bundleWithIdentifier + // will not work properly, so only use that as a backup plan. + // We want to find the bundle containing the code where this function lives + // and work from there + // + + // Get the pathname to the code which contains this function + Dl_info info; + if (dladdr((const void*)GetResourcePath, &info) != 0) { + NSFileManager *filemgr = [NSFileManager defaultManager]; + NSString *filePath = + [filemgr stringWithFileSystemRepresentation:info.dli_fname + length:strlen(info.dli_fname)]; + NSString *bundlePath = [filePath stringByDeletingLastPathComponent]; + // The "Resources" directory should be in the same directory as the + // executable code, since that's how the Breakpad framework is built. + resourcePath = [bundlePath stringByAppendingPathComponent:@"Resources/"]; + } else { + // fallback plan + NSBundle *bundle = + [NSBundle bundleWithIdentifier:@"com.Google.BreakpadFramework"]; + resourcePath = [bundle resourcePath]; + } + + return resourcePath; +} +} // extern "C" + +//============================================================================= +bool Breakpad::Initialize(NSDictionary *parameters) { + // Initialize + config_params_ = NULL; + handler_ = NULL; + + // Check for debugger + if (IsDebuggerActive()) { + return true; + } + + // Gather any user specified parameters + if (!ExtractParameters(parameters)) { + return false; + } + + if ([[parameters objectForKey:@BREAKPAD_IN_PROCESS] boolValue]) + return InitializeInProcess(parameters); + else + return InitializeOutOfProcess(parameters); +} + +//============================================================================= +bool Breakpad::InitializeInProcess(NSDictionary* parameters) { + handler_ = + new (gBreakpadAllocator->Allocate( + sizeof(google_breakpad::ExceptionHandler))) + google_breakpad::ExceptionHandler( + config_params_->GetValueForKey(BREAKPAD_DUMP_DIRECTORY), + 0, &HandleMinidumpCallback, this, true, 0); + return true; +} + +//============================================================================= +bool Breakpad::InitializeOutOfProcess(NSDictionary* parameters) { + // Get path to Inspector executable. + NSString *inspectorPathString = KeyValue(@BREAKPAD_INSPECTOR_LOCATION); + + // Standardize path (resolve symlinkes, etc.) and escape spaces + inspectorPathString = [inspectorPathString stringByStandardizingPath]; + inspectorPathString = [[inspectorPathString componentsSeparatedByString:@" "] + componentsJoinedByString:@"\\ "]; + + // Create an on-demand server object representing the Inspector. + // In case of a crash, we simply need to call the LaunchOnDemand() + // method on it, then send a mach message to its service port. + // It will then launch and perform a process inspection of our crashed state. + // See the HandleException() method for the details. +#define RECEIVE_PORT_NAME "com.Breakpad.Inspector" + + name_t portName; + snprintf(portName, sizeof(name_t), "%s%d", RECEIVE_PORT_NAME, getpid()); + + // Save the location of the Inspector + strlcpy(inspector_path_, [inspectorPathString fileSystemRepresentation], + sizeof(inspector_path_)); + + // Append a single command-line argument to the Inspector path + // representing the bootstrap name of the launch-on-demand receive port. + // When the Inspector is launched, it can use this to lookup the port + // by calling bootstrap_check_in(). + strlcat(inspector_path_, " ", sizeof(inspector_path_)); + strlcat(inspector_path_, portName, sizeof(inspector_path_)); + + kern_return_t kr = inspector_.Initialize(inspector_path_, + portName, + true); // shutdown on exit + + if (kr != KERN_SUCCESS) { + return false; + } + + // Create the handler (allocating it in our special protected pool) + handler_ = + new (gBreakpadAllocator->Allocate( + sizeof(google_breakpad::ExceptionHandler))) + google_breakpad::ExceptionHandler( + Breakpad::ExceptionHandlerDirectCallback, this, true); + return true; +} + +//============================================================================= +Breakpad::~Breakpad() { + // Note that we don't use operator delete() on these pointers, + // since they were allocated by ProtectedMemoryAllocator objects. + // + if (config_params_) { + config_params_->~SimpleStringDictionary(); + } + + if (handler_) + handler_->~ExceptionHandler(); +} + +//============================================================================= +bool Breakpad::ExtractParameters(NSDictionary *parameters) { + NSUserDefaults *stdDefaults = [NSUserDefaults standardUserDefaults]; + NSString *skipConfirm = [stdDefaults stringForKey:@BREAKPAD_SKIP_CONFIRM]; + NSString *sendAndExit = [stdDefaults stringForKey:@BREAKPAD_SEND_AND_EXIT]; + + 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 *interval = [parameters objectForKey:@BREAKPAD_REPORT_INTERVAL]; + NSString *inspectorPathString = + [parameters objectForKey:@BREAKPAD_INSPECTOR_LOCATION]; + NSString *reporterPathString = + [parameters objectForKey:@BREAKPAD_REPORTER_EXE_LOCATION]; + NSString *timeout = [parameters objectForKey:@BREAKPAD_CONFIRM_TIMEOUT]; + NSArray *logFilePaths = [parameters objectForKey:@BREAKPAD_LOGFILES]; + NSString *logFileTailSize = + [parameters objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE]; + NSString *requestUserText = + [parameters objectForKey:@BREAKPAD_REQUEST_COMMENTS]; + NSString *requestEmail = [parameters objectForKey:@BREAKPAD_REQUEST_EMAIL]; + NSString *vendor = + [parameters objectForKey:@BREAKPAD_VENDOR]; + NSString *dumpSubdirectory = + [parameters objectForKey:@BREAKPAD_DUMP_DIRECTORY]; + + NSDictionary *serverParameters = + [parameters objectForKey:@BREAKPAD_SERVER_PARAMETER_DICT]; + + // These may have been set above as user prefs, which take priority. + if (!skipConfirm) { + skipConfirm = [parameters objectForKey:@BREAKPAD_SKIP_CONFIRM]; + } + if (!sendAndExit) { + sendAndExit = [parameters objectForKey:@BREAKPAD_SEND_AND_EXIT]; + } + + if (!product) + product = [parameters objectForKey:@"CFBundleName"]; + + if (!display) { + display = [parameters objectForKey:@"CFBundleDisplayName"]; + if (!display) { + display = product; + } + } + + if (!version) + version = [parameters objectForKey:@"CFBundleVersion"]; + + if (!interval) + interval = @"3600"; + + if (!timeout) + timeout = @"300"; + + if (!logFileTailSize) + logFileTailSize = @"200000"; + + if (!vendor) { + vendor = @"Vendor not specified"; + } + + // Normalize the values. + if (skipConfirm) { + skipConfirm = [skipConfirm uppercaseString]; + + if ([skipConfirm isEqualToString:@"YES"] || + [skipConfirm isEqualToString:@"TRUE"] || + [skipConfirm isEqualToString:@"1"]) + skipConfirm = @"YES"; + else + skipConfirm = @"NO"; + } else { + skipConfirm = @"NO"; + } + + send_and_exit_ = true; + if (sendAndExit) { + sendAndExit = [sendAndExit uppercaseString]; + + if ([sendAndExit isEqualToString:@"NO"] || + [sendAndExit isEqualToString:@"FALSE"] || + [sendAndExit isEqualToString:@"0"]) + send_and_exit_ = false; + } + + if (requestUserText) { + requestUserText = [requestUserText uppercaseString]; + + if ([requestUserText isEqualToString:@"YES"] || + [requestUserText isEqualToString:@"TRUE"] || + [requestUserText isEqualToString:@"1"]) + requestUserText = @"YES"; + else + requestUserText = @"NO"; + } else { + requestUserText = @"NO"; + } + + // Find the helper applications if not specified in user config. + NSString *resourcePath = nil; + if (!inspectorPathString || !reporterPathString) { + resourcePath = GetResourcePath(); + if (!resourcePath) { + return false; + } + } + + // Find Inspector. + if (!inspectorPathString) { + inspectorPathString = + [resourcePath stringByAppendingPathComponent:@"Inspector"]; + } + + // Verify that there is an Inspector tool. + if (![[NSFileManager defaultManager] fileExistsAtPath:inspectorPathString]) { + return false; + } + + // Find Reporter. + if (!reporterPathString) { + reporterPathString = + [resourcePath + stringByAppendingPathComponent:@"crash_report_sender.app"]; + reporterPathString = + [[NSBundle bundleWithPath:reporterPathString] executablePath]; + } + + // Verify that there is a Reporter application. + if (![[NSFileManager defaultManager] + fileExistsAtPath:reporterPathString]) { + return false; + } + + if (!dumpSubdirectory) { + 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(SimpleStringDictionary)) ) + SimpleStringDictionary(); + + SimpleStringDictionary &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_REPORT_INTERVAL, [interval UTF8String]); + dictionary.SetKeyValue(BREAKPAD_SKIP_CONFIRM, [skipConfirm UTF8String]); + dictionary.SetKeyValue(BREAKPAD_CONFIRM_TIMEOUT, [timeout UTF8String]); + dictionary.SetKeyValue(BREAKPAD_INSPECTOR_LOCATION, + [inspectorPathString fileSystemRepresentation]); + dictionary.SetKeyValue(BREAKPAD_REPORTER_EXE_LOCATION, + [reporterPathString fileSystemRepresentation]); + dictionary.SetKeyValue(BREAKPAD_LOGFILE_UPLOAD_SIZE, + [logFileTailSize UTF8String]); + dictionary.SetKeyValue(BREAKPAD_REQUEST_COMMENTS, + [requestUserText UTF8String]); + dictionary.SetKeyValue(BREAKPAD_REQUEST_EMAIL, [requestEmail 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 (logFilePaths) { + char logFileKey[255]; + for(unsigned int i = 0; i < [logFilePaths count]; i++) { + sprintf(logFileKey,"%s%d", BREAKPAD_LOGFILE_KEY_PREFIX, i); + dictionary.SetKeyValue(logFileKey, + [[logFilePaths objectAtIndex:i] + fileSystemRepresentation]); + } + } + + 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 char *value = config_params_->GetValueForKey([key UTF8String]); + return value ? [NSString stringWithUTF8String:value] : nil; +} + +//============================================================================= +void Breakpad::RemoveKeyValue(NSString *key) { + if (!config_params_ || !key) return; + + config_params_->RemoveKey([key UTF8String]); +} + +//============================================================================= +void Breakpad::GenerateAndSendReport() { + config_params_->SetKeyValue(BREAKPAD_ON_DEMAND, "YES"); + HandleException(0, 0, 0, mach_thread_self()); + config_params_->SetKeyValue(BREAKPAD_ON_DEMAND, "NO"); +} + +//============================================================================= +bool Breakpad::HandleException(int exception_type, + int exception_code, + int exception_subcode, + mach_port_t crashing_thread) { + if (filter_callback_) { + bool should_handle = filter_callback_(exception_type, + exception_code, + crashing_thread, + filter_callback_context_); + if (!should_handle) return false; + } + + // We need to reset the memory protections to be read/write, + // since LaunchOnDemand() requires changing state. + gBreakpadAllocator->Unprotect(); + // Configure the server to launch when we message the service port. + // The reason we do this here, rather than at startup, is that we + // can leak a bootstrap service entry if this method is called and + // there never ends up being a crash. + inspector_.LaunchOnDemand(); + gBreakpadAllocator->Protect(); + + // The Inspector should send a message to this port to verify it + // received our information and has finished the inspection. + ReceivePort acknowledge_port; + + // Send initial information to the Inspector. + MachSendMessage message(kMsgType_InspectorInitialInfo); + message.AddDescriptor(mach_task_self()); // our task + message.AddDescriptor(crashing_thread); // crashing thread + message.AddDescriptor(mach_thread_self()); // exception-handling thread + message.AddDescriptor(acknowledge_port.GetPort());// message receive port + + InspectorInfo info; + info.exception_type = exception_type; + info.exception_code = exception_code; + info.exception_subcode = exception_subcode; + info.parameter_count = config_params_->GetCount(); + message.SetData(&info, sizeof(info)); + + MachPortSender sender(inspector_.GetServicePort()); + + kern_return_t result = sender.SendMessage(message, 2000); + + if (result == KERN_SUCCESS) { + // Now, send a series of key-value pairs to the Inspector. + const SimpleStringDictionary::Entry *entry = NULL; + SimpleStringDictionary::Iterator iter(*config_params_); + + while ( (entry = iter.Next()) ) { + KeyValueMessageData keyvalue_data(*entry); + + MachSendMessage keyvalue_message(kMsgType_InspectorKeyValuePair); + keyvalue_message.SetData(&keyvalue_data, sizeof(keyvalue_data)); + + result = sender.SendMessage(keyvalue_message, 2000); + + if (result != KERN_SUCCESS) { + break; + } + } + + if (result == KERN_SUCCESS) { + // Wait for acknowledgement that the inspection has finished. + MachReceiveMessage acknowledge_messsage; + result = acknowledge_port.WaitForMessage(&acknowledge_messsage, 5000); + } + } + +#if VERBOSE + PRINT_MACH_RESULT(result, "Breakpad: SendMessage "); + printf("Breakpad: Inspector service port = %#x\n", + inspector_.GetServicePort()); +#endif + + // If we don't want any forwarding, return true here to indicate that we've + // processed things as much as we want. + if (send_and_exit_) return true; + + return false; +} + +//============================================================================= +bool Breakpad::HandleMinidump(const char *dump_dir, const char *minidump_id) { + google_breakpad::ConfigFile config_file; + config_file.WriteFile(dump_dir, config_params_, dump_dir, minidump_id); + google_breakpad::LaunchReporter( + config_params_->GetValueForKey(BREAKPAD_REPORTER_EXE_LOCATION), + config_file.GetFilePath()); + return true; +} + +//============================================================================= +//============================================================================= + +#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(SimpleStringDictionary)); + + // Create a mutex for use in accessing the SimpleStringDictionary + 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"); + } +} + +//============================================================================= +void BreakpadGenerateAndSendReport(BreakpadRef ref) { + try { + Breakpad *breakpad = (Breakpad *)ref; + + if (breakpad && gKeyValueAllocator) { + ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator); + + gBreakpadAllocator->Unprotect(); + breakpad->GenerateAndSendReport(); + gBreakpadAllocator->Protect(); + } + } catch(...) { // don't let exceptions leave this C API + fprintf(stderr, "BreakpadGenerateAndSendReport() : error\n"); + } +} + +//============================================================================= +void BreakpadSetFilterCallback(BreakpadRef ref, + BreakpadFilterCallback callback, + void *context) { + + try { + Breakpad *breakpad = (Breakpad *)ref; + + if (breakpad && gBreakpadAllocator) { + // share the dictionary mutex here (we really don't need a mutex) + ProtectedMemoryLocker locker(&gDictionaryMutex, gBreakpadAllocator); + + breakpad->SetFilterCallback(callback, context); + } + } catch(...) { // don't let exceptions leave this C API + fprintf(stderr, "BreakpadSetFilterCallback() : error\n"); + } +} + +//============================================================================ +void BreakpadAddLogFile(BreakpadRef ref, NSString *logPathname) { + int logFileCounter = 0; + + NSString *logFileKey = [NSString stringWithFormat:@"%@%d", + @BREAKPAD_LOGFILE_KEY_PREFIX, + logFileCounter]; + + NSString *existingLogFilename = nil; + existingLogFilename = BreakpadKeyValue(ref, logFileKey); + // Find the first log file key that we can use by testing for existence + while (existingLogFilename) { + if ([existingLogFilename isEqualToString:logPathname]) { + return; + } + logFileCounter++; + logFileKey = [NSString stringWithFormat:@"%@%d", + @BREAKPAD_LOGFILE_KEY_PREFIX, + logFileCounter]; + existingLogFilename = BreakpadKeyValue(ref, logFileKey); + } + + BreakpadSetKeyValue(ref, logFileKey, logPathname); +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/Framework/Breakpad_Prefix.pch b/toolkit/crashreporter/google-breakpad/src/client/mac/Framework/Breakpad_Prefix.pch new file mode 100644 index 000000000..729866635 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/Framework/Breakpad_Prefix.pch @@ -0,0 +1,8 @@ +// +// Prefix header for all source files of the 'Breakpad' target in the +// 'Breakpad' project. +// + +#ifdef __OBJC__ + #import <Cocoa/Cocoa.h> +#endif diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/Framework/Info.plist b/toolkit/crashreporter/google-breakpad/src/client/mac/Framework/Info.plist new file mode 100644 index 000000000..e43baddc0 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/Framework/Info.plist @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleName</key> + <string>${PRODUCT_NAME}</string> + <key>CFBundleIconFile</key> + <string></string> + <key>CFBundleIdentifier</key> + <string>com.googlecode.google-breakpad</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>FMWK</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1.0</string> + <key>NSPrincipalClass</key> + <string></string> +</dict> +</plist> diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/Framework/OnDemandServer.h b/toolkit/crashreporter/google-breakpad/src/client/mac/Framework/OnDemandServer.h new file mode 100644 index 000000000..b8aabbe47 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/Framework/OnDemandServer.h @@ -0,0 +1,145 @@ +// Copyright (c) 2007, 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 <mach/mach.h> +#include <servers/bootstrap.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <unistd.h> + +//============================================================================== +// class OnDemandServer : +// A basic on-demand server launcher supporting a single named service port +// +// Example Usage : +// +// kern_return_t result; +// OnDemandServer *server = OnDemandServer::Create("/tmp/myserver", +// "com.MyCompany.MyServiceName", +// true, +// &result); +// +// if (server) { +// server->LaunchOnDemand(); +// mach_port_t service_port = GetServicePort(); +// +// // Send a mach message to service_port and "myserver" will be launched +// } +// +// +// ---- Now in the server code ---- +// +// // "myserver" should get the service port and read the message which +// // launched it: +// mach_port_t service_rcv_port_; +// kern_return_t kr = bootstrap_check_in(bootstrap_port, +// "com.MyCompany.MyServiceName", +// &service_rcv_port_); +// // mach_msg() read service_rcv_port_ .... +// +// .... +// +// // Later "myserver" may want to unregister the service if it doesn't +// // want its bootstrap service to stick around after it exits. +// +// // DO NOT use mach_port_deallocate() here -- it will fail and the +// // following bootstrap_register() will also fail leaving our service +// // name hanging around forever (until reboot) +// kern_return_t kr = mach_port_destroy(mach_task_self(), service_rcv_port_); +// +// kr = bootstrap_register(bootstrap_port, +// "com.MyCompany.MyServiceName", +// MACH_PORT_NULL); + +class OnDemandServer { + public: + // must call Initialize() to be useful + OnDemandServer() + : server_port_(MACH_PORT_NULL), + service_port_(MACH_PORT_NULL), + unregister_on_cleanup_(true) { + } + + // Creates the bootstrap server and service + kern_return_t Initialize(const char *server_command, + const char *service_name, + bool unregister_on_cleanup); + + // Returns an OnDemandServer object if successful, or NULL if there's + // an error. The error result will be returned in out_result. + // + // server_command : the full path name including optional command-line + // arguments to the executable representing the server + // + // service_name : represents service name + // something like "com.company.ServiceName" + // + // unregister_on_cleanup : if true, unregisters the service name + // when the OnDemandServer is deleted -- unregistering will + // ONLY be possible if LaunchOnDemand() has NOT been called. + // If false, then the service will continue to be registered + // even after the current process quits. + // + // out_result : if non-NULL, returns the result + // this value will be KERN_SUCCESS if Create() returns non-NULL + // + static OnDemandServer *Create(const char *server_command, + const char *service_name, + bool unregister_on_cleanup, + kern_return_t *out_result); + + // Cleans up and if LaunchOnDemand() has not yet been called then + // the bootstrap service will be unregistered. + ~OnDemandServer(); + + // This must be called if we intend to commit to launching the server + // by sending a mach message to our service port. Do not call it otherwise + // or it will be difficult (impossible?) to unregister the service name. + void LaunchOnDemand(); + + // This is the port we need to send a mach message to after calling + // LaunchOnDemand(). Sending a message causing an immediate launch + // of the server + mach_port_t GetServicePort() { return service_port_; }; + + private: + // Disallow copy constructor + OnDemandServer(const OnDemandServer&); + + // Cleans up and if LaunchOnDemand() has not yet been called then + // the bootstrap service will be unregistered. + void Unregister(); + + name_t service_name_; + + mach_port_t server_port_; + mach_port_t service_port_; + bool unregister_on_cleanup_; +}; diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/Framework/OnDemandServer.mm b/toolkit/crashreporter/google-breakpad/src/client/mac/Framework/OnDemandServer.mm new file mode 100644 index 000000000..dbe601bb8 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/Framework/OnDemandServer.mm @@ -0,0 +1,189 @@ +// Copyright (c) 2007, 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 "OnDemandServer.h" + +#import "Breakpad.h" +#include "common/mac/bootstrap_compat.h" + +#if DEBUG + #define PRINT_MACH_RESULT(result_, message_) \ + printf(message_"%s (%d)\n", mach_error_string(result_), result_ ); +#if defined(MAC_OS_X_VERSION_10_5) && \ + MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + #define PRINT_BOOTSTRAP_RESULT(result_, message_) \ + printf(message_"%s (%d)\n", bootstrap_strerror(result_), result_ ); +#else + #define PRINT_BOOTSTRAP_RESULT(result_, message_) \ + PRINT_MACH_RESULT(result_, message_) +#endif +#else + #define PRINT_MACH_RESULT(result_, message_) + #define PRINT_BOOTSTRAP_RESULT(result_, message_) +#endif + +//============================================================================== +OnDemandServer *OnDemandServer::Create(const char *server_command, + const char *service_name, + bool unregister_on_cleanup, + kern_return_t *out_result) { + OnDemandServer *server = new OnDemandServer(); + + if (!server) return NULL; + + kern_return_t result = server->Initialize(server_command, + service_name, + unregister_on_cleanup); + + if (out_result) { + *out_result = result; + } + + if (result == KERN_SUCCESS) { + return server; + } + + delete server; + return NULL; +}; + +//============================================================================== +kern_return_t OnDemandServer::Initialize(const char *server_command, + const char *service_name, + bool unregister_on_cleanup) { + unregister_on_cleanup_ = unregister_on_cleanup; + + mach_port_t self_task = mach_task_self(); + + mach_port_t bootstrap_port; + kern_return_t kr = task_get_bootstrap_port(self_task, &bootstrap_port); + if (kr != KERN_SUCCESS) { + PRINT_MACH_RESULT(kr, "task_get_bootstrap_port(): "); + return kr; + } + + mach_port_t bootstrap_subset_port; + kr = bootstrap_subset(bootstrap_port, self_task, &bootstrap_subset_port); + if (kr != BOOTSTRAP_SUCCESS) { + PRINT_BOOTSTRAP_RESULT(kr, "bootstrap_subset(): "); + return kr; + } + + // The inspector will be invoked with its bootstrap port set to the subset, + // but the sender will need access to the original bootstrap port. Although + // the original port is the subset's parent, bootstrap_parent can't be used + // because it requires extra privileges. Stash the original bootstrap port + // in the subset by registering it under a known name. The inspector will + // recover this port and set it as its own bootstrap port in Inspector.mm + // Inspector::ResetBootstrapPort. + kr = breakpad::BootstrapRegister( + bootstrap_subset_port, + const_cast<char*>(BREAKPAD_BOOTSTRAP_PARENT_PORT), + bootstrap_port); + if (kr != BOOTSTRAP_SUCCESS) { + PRINT_BOOTSTRAP_RESULT(kr, "bootstrap_register(): "); + return kr; + } + + kr = bootstrap_create_server(bootstrap_subset_port, + const_cast<char*>(server_command), + geteuid(), // server uid + true, + &server_port_); + if (kr != BOOTSTRAP_SUCCESS) { + PRINT_BOOTSTRAP_RESULT(kr, "bootstrap_create_server(): "); + return kr; + } + + strlcpy(service_name_, service_name, sizeof(service_name_)); + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + // Create a service called service_name, and return send rights to + // that port in service_port_. + kr = bootstrap_create_service(server_port_, + const_cast<char*>(service_name), + &service_port_); +#pragma clang diagnostic pop + if (kr != BOOTSTRAP_SUCCESS) { + PRINT_BOOTSTRAP_RESULT(kr, "bootstrap_create_service(): "); + + // perhaps the service has already been created - try to look it up + kr = bootstrap_look_up(bootstrap_port, (char*)service_name, &service_port_); + + if (kr != BOOTSTRAP_SUCCESS) { + PRINT_BOOTSTRAP_RESULT(kr, "bootstrap_look_up(): "); + Unregister(); // clean up server port + return kr; + } + } + + return KERN_SUCCESS; +} + +//============================================================================== +OnDemandServer::~OnDemandServer() { + if (unregister_on_cleanup_) { + Unregister(); + } +} + +//============================================================================== +void OnDemandServer::LaunchOnDemand() { + // We need to do this, since the launched server is another process + // and holding on to this port delays launching until the current process + // exits! + mach_port_deallocate(mach_task_self(), server_port_); + server_port_ = MACH_PORT_DEAD; + + // Now, the service is still registered and all we need to do is send + // a mach message to the service port in order to launch the server. +} + +//============================================================================== +void OnDemandServer::Unregister() { + if (service_port_ != MACH_PORT_NULL) { + mach_port_deallocate(mach_task_self(), service_port_); + service_port_ = MACH_PORT_NULL; + } + + if (server_port_ != MACH_PORT_NULL) { + // unregister the service + kern_return_t kr = breakpad::BootstrapRegister(server_port_, + service_name_, + MACH_PORT_NULL); + + if (kr != KERN_SUCCESS) { + PRINT_MACH_RESULT(kr, "Breakpad UNREGISTER : bootstrap_register() : "); + } + + mach_port_deallocate(mach_task_self(), server_port_); + server_port_ = MACH_PORT_NULL; + } +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/UnitTests-Info.plist b/toolkit/crashreporter/google-breakpad/src/client/mac/UnitTests-Info.plist new file mode 100644 index 000000000..65013556d --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/UnitTests-Info.plist @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleIdentifier</key> + <string>com.yourcompany.${PRODUCT_NAME:identifier}</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>BNDL</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1.0</string> +</dict> +</plist> diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/ConfigFile.h b/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/ConfigFile.h new file mode 100644 index 000000000..5662e8b09 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/ConfigFile.h @@ -0,0 +1,83 @@ +// 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. +// +// Utility class that can persist a SimpleStringDictionary to disk. + +#import <Foundation/Foundation.h> + +#include "common/simple_string_dictionary.h" + +namespace google_breakpad { + +BOOL EnsureDirectoryPathExists(NSString *dirPath); + +//============================================================================= +class ConfigFile { + public: + ConfigFile() { + config_file_ = -1; + config_file_path_[0] = 0; + has_created_file_ = false; + }; + + ~ConfigFile() { + }; + + void WriteFile(const char* directory, + const SimpleStringDictionary *configurationParameters, + const char *dump_dir, + const char *minidump_id); + + const char *GetFilePath() { return config_file_path_; } + + void Unlink() { + if (config_file_ != -1) + unlink(config_file_path_); + + config_file_ = -1; + } + + private: + BOOL WriteData(const void *data, size_t length); + + BOOL AppendConfigData(const char *key, + const void *data, + size_t length); + + BOOL AppendConfigString(const char *key, + const char *value); + + BOOL AppendCrashTimeParameters(const char *processStartTimeString); + + int config_file_; // descriptor for config file + char config_file_path_[PATH_MAX]; // Path to configuration file + bool has_created_file_; +}; + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/ConfigFile.mm b/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/ConfigFile.mm new file mode 100644 index 000000000..acab7de84 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/ConfigFile.mm @@ -0,0 +1,167 @@ +// 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. +// +// Utility class that can persist a SimpleStringDictionary to disk. + +#import "client/mac/crash_generation/ConfigFile.h" + +#import <Foundation/Foundation.h> +#include <stdio.h> +#include <sys/time.h> + +#import "client/apple/Framework/BreakpadDefines.h" +#import "common/mac/GTMDefines.h" + + +namespace google_breakpad { + +//============================================================================= +BOOL EnsureDirectoryPathExists(NSString *dirPath) { + NSFileManager *mgr = [NSFileManager defaultManager]; + + NSDictionary *attrs = + [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:0750] + forKey:NSFilePosixPermissions]; + + return [mgr createDirectoryAtPath:dirPath + withIntermediateDirectories:YES + attributes:attrs + error:nil]; +} + +//============================================================================= +BOOL ConfigFile::WriteData(const void *data, size_t length) { + size_t result = write(config_file_, data, length); + + return result == length; +} + +//============================================================================= +BOOL ConfigFile::AppendConfigData(const char *key, + const void *data, size_t length) { + assert(config_file_ != -1); + + if (!key) { + return NO; + } + + if (!data) { + return NO; + } + + // Write the key, \n, length of data (ascii integer), \n, data + char buffer[16]; + char nl = '\n'; + BOOL result = WriteData(key, strlen(key)); + + snprintf(buffer, sizeof(buffer) - 1, "\n%lu\n", length); + result &= WriteData(buffer, strlen(buffer)); + result &= WriteData(data, length); + result &= WriteData(&nl, 1); + return result; +} + +//============================================================================= +BOOL ConfigFile::AppendConfigString(const char *key, + const char *value) { + return AppendConfigData(key, value, strlen(value)); +} + +//============================================================================= +BOOL ConfigFile::AppendCrashTimeParameters(const char *processStartTimeString) { + // Set process uptime parameter + struct timeval tv; + gettimeofday(&tv, NULL); + + char processUptimeString[32], processCrashtimeString[32]; + // Set up time if we've received the start time. + if (processStartTimeString) { + time_t processStartTime = strtol(processStartTimeString, NULL, 10); + time_t processUptime = tv.tv_sec - processStartTime; + // Store the uptime in milliseconds. + sprintf(processUptimeString, "%llu", + static_cast<unsigned long long int>(processUptime) * 1000); + if (!AppendConfigString(BREAKPAD_PROCESS_UP_TIME, processUptimeString)) + return false; + } + + sprintf(processCrashtimeString, "%zd", tv.tv_sec); + return AppendConfigString(BREAKPAD_PROCESS_CRASH_TIME, + processCrashtimeString); +} + +//============================================================================= +void ConfigFile::WriteFile(const char* directory, + const SimpleStringDictionary *configurationParameters, + const char *dump_dir, + const char *minidump_id) { + + assert(config_file_ == -1); + + // Open and write out configuration file preamble + if (directory) { + snprintf(config_file_path_, sizeof(config_file_path_), "%s/Config-XXXXXX", + directory); + } else { + strlcpy(config_file_path_, "/tmp/Config-XXXXXX", + sizeof(config_file_path_)); + } + config_file_ = mkstemp(config_file_path_); + + if (config_file_ == -1) { + return; + } + + has_created_file_ = true; + + // Add the minidump dir + AppendConfigString(kReporterMinidumpDirectoryKey, dump_dir); + AppendConfigString(kReporterMinidumpIDKey, minidump_id); + + // Write out the configuration parameters + BOOL result = YES; + const SimpleStringDictionary &dictionary = *configurationParameters; + + const SimpleStringDictionary::Entry *entry = NULL; + SimpleStringDictionary::Iterator iter(dictionary); + + while ((entry = iter.Next())) { + result = AppendConfigString(entry->key, entry->value); + + if (!result) + break; + } + AppendCrashTimeParameters( + configurationParameters->GetValueForKey(BREAKPAD_PROCESS_START_TIME)); + + close(config_file_); + config_file_ = -1; +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/Inspector.h b/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/Inspector.h new file mode 100644 index 000000000..671235513 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/Inspector.h @@ -0,0 +1,162 @@ +// Copyright (c) 2007, 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. +// +// Interface file between the Breakpad.framework and +// the Inspector process. + +#include "common/simple_string_dictionary.h" + +#import <Foundation/Foundation.h> +#include <mach/mach.h> + +#import "client/mac/crash_generation/ConfigFile.h" +#import "client/mac/handler/minidump_generator.h" + + +// Types of mach messsages (message IDs) +enum { + kMsgType_InspectorInitialInfo = 0, // data is InspectorInfo + kMsgType_InspectorKeyValuePair = 1, // data is KeyValueMessageData + kMsgType_InspectorAcknowledgement = 2 // no data sent +}; + +// Initial information sent from the crashed process by +// Breakpad.framework to the Inspector process +// The mach message with this struct as data will also include +// several descriptors for sending mach port rights to the crashed +// task, etc. +struct InspectorInfo { + int exception_type; + int exception_code; + int exception_subcode; + unsigned int parameter_count; // key-value pairs +}; + +// Key/value message data to be sent to the Inspector +struct KeyValueMessageData { + public: + KeyValueMessageData() {} + explicit KeyValueMessageData( + const google_breakpad::SimpleStringDictionary::Entry &inEntry) { + strlcpy(key, inEntry.key, sizeof(key) ); + strlcpy(value, inEntry.value, sizeof(value) ); + } + + char key[google_breakpad::SimpleStringDictionary::key_size]; + char value[google_breakpad::SimpleStringDictionary::value_size]; +}; + +using std::string; +using google_breakpad::MinidumpGenerator; + +namespace google_breakpad { + +//============================================================================= +class MinidumpLocation { + public: + MinidumpLocation(NSString *minidumpDir) { + // Ensure that the path exists. Fallback to /tmp if unable to locate path. + assert(minidumpDir); + if (!EnsureDirectoryPathExists(minidumpDir)) { + minidumpDir = @"/tmp"; + } + + strlcpy(minidump_dir_path_, [minidumpDir fileSystemRepresentation], + sizeof(minidump_dir_path_)); + + // now generate a unique ID + string dump_path(minidump_dir_path_); + string next_minidump_id; + + string next_minidump_path_ = + (MinidumpGenerator::UniqueNameInDirectory(dump_path, &next_minidump_id)); + + strlcpy(minidump_id_, next_minidump_id.c_str(), sizeof(minidump_id_)); + }; + + const char *GetPath() { return minidump_dir_path_; } + const char *GetID() { return minidump_id_; } + + private: + char minidump_dir_path_[PATH_MAX]; // Path to minidump directory + char minidump_id_[128]; +}; + +//============================================================================= +class Inspector { + public: + Inspector() {}; + + // given a bootstrap service name, receives mach messages + // from a crashed process, then inspects it, creates a minidump file + // and asks the user if he wants to upload it to a server. + void Inspect(const char *receive_port_name); + + private: + // The Inspector is invoked with its bootstrap port set to the bootstrap + // subset established in OnDemandServer.mm OnDemandServer::Initialize. + // For proper communication with the system, the sender (which will inherit + // the Inspector's bootstrap port) needs the per-session bootstrap namespace + // available directly in its bootstrap port. OnDemandServer stashed this + // port into the subset namespace under a special name. ResetBootstrapPort + // recovers this port and switches this task to use it as its own bootstrap + // (ensuring that children like the sender will inherit it), and saves the + // subset in bootstrap_subset_port_ for use by ServiceCheckIn and + // ServiceCheckOut. + kern_return_t ResetBootstrapPort(); + + kern_return_t ServiceCheckIn(const char *receive_port_name); + kern_return_t ServiceCheckOut(const char *receive_port_name); + + kern_return_t ReadMessages(); + + bool InspectTask(); + kern_return_t SendAcknowledgement(); + + // The bootstrap port in which the inspector is registered and into which it + // must check in. + mach_port_t bootstrap_subset_port_; + + mach_port_t service_rcv_port_; + + int exception_type_; + int exception_code_; + int exception_subcode_; + mach_port_t remote_task_; + mach_port_t crashing_thread_; + mach_port_t handler_thread_; + mach_port_t ack_port_; + + SimpleStringDictionary config_params_; + + ConfigFile config_file_; +}; + + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/Inspector.mm b/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/Inspector.mm new file mode 100644 index 000000000..dc6f48086 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/Inspector.mm @@ -0,0 +1,362 @@ +// Copyright (c) 2007, 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. +// +// Utility that can inspect another process and write a crash dump + +#include <cstdio> +#include <iostream> +#include <servers/bootstrap.h> +#include <stdio.h> +#include <string.h> +#include <string> + +#import "client/mac/crash_generation/Inspector.h" + +#import "client/mac/Framework/Breakpad.h" +#import "client/mac/handler/minidump_generator.h" + +#import "common/mac/MachIPC.h" +#include "common/mac/bootstrap_compat.h" +#include "common/mac/launch_reporter.h" + +#import "GTMDefines.h" + +#import <Foundation/Foundation.h> + +namespace google_breakpad { + +//============================================================================= +void Inspector::Inspect(const char *receive_port_name) { + kern_return_t result = ResetBootstrapPort(); + if (result != KERN_SUCCESS) { + return; + } + + result = ServiceCheckIn(receive_port_name); + + if (result == KERN_SUCCESS) { + result = ReadMessages(); + + if (result == KERN_SUCCESS) { + // Inspect the task and write a minidump file. + bool wrote_minidump = InspectTask(); + + // Send acknowledgement to the crashed process that the inspection + // has finished. It will then be able to cleanly exit. + // The return value is ignored because failure isn't fatal. If the process + // didn't get the message there's nothing we can do, and we still want to + // send the report. + SendAcknowledgement(); + + if (wrote_minidump) { + // Ask the user if he wants to upload the crash report to a server, + // and do so if he agrees. + LaunchReporter( + config_params_.GetValueForKey(BREAKPAD_REPORTER_EXE_LOCATION), + config_file_.GetFilePath()); + } else { + fprintf(stderr, "Inspection of crashed process failed\n"); + } + + // Now that we're done reading messages, cleanup the service, but only + // if there was an actual exception + // Otherwise, it means the dump was generated on demand and the process + // lives on, and we might be needed again in the future. + if (exception_code_) { + ServiceCheckOut(receive_port_name); + } + } else { + PRINT_MACH_RESULT(result, "Inspector: WaitForMessage()"); + } + } +} + +//============================================================================= +kern_return_t Inspector::ResetBootstrapPort() { + // A reasonable default, in case anything fails. + bootstrap_subset_port_ = bootstrap_port; + + mach_port_t self_task = mach_task_self(); + + kern_return_t kr = task_get_bootstrap_port(self_task, + &bootstrap_subset_port_); + if (kr != KERN_SUCCESS) { + NSLog(@"ResetBootstrapPort: task_get_bootstrap_port failed: %s (%d)", + mach_error_string(kr), kr); + return kr; + } + + mach_port_t bootstrap_parent_port; + kr = bootstrap_look_up(bootstrap_subset_port_, + const_cast<char*>(BREAKPAD_BOOTSTRAP_PARENT_PORT), + &bootstrap_parent_port); + if (kr != BOOTSTRAP_SUCCESS) { + NSLog(@"ResetBootstrapPort: bootstrap_look_up failed: %s (%d)", +#if defined(MAC_OS_X_VERSION_10_5) && \ + MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + bootstrap_strerror(kr), +#else + mach_error_string(kr), +#endif + kr); + return kr; + } + + kr = task_set_bootstrap_port(self_task, bootstrap_parent_port); + if (kr != KERN_SUCCESS) { + NSLog(@"ResetBootstrapPort: task_set_bootstrap_port failed: %s (%d)", + mach_error_string(kr), kr); + return kr; + } + + // Some things access the bootstrap port through this global variable + // instead of calling task_get_bootstrap_port. + bootstrap_port = bootstrap_parent_port; + + return KERN_SUCCESS; +} + +//============================================================================= +kern_return_t Inspector::ServiceCheckIn(const char *receive_port_name) { + // We need to get the mach port representing this service, so we can + // get information from the crashed process. + kern_return_t kr = bootstrap_check_in(bootstrap_subset_port_, + (char*)receive_port_name, + &service_rcv_port_); + + if (kr != KERN_SUCCESS) { +#if VERBOSE + PRINT_MACH_RESULT(kr, "Inspector: bootstrap_check_in()"); +#endif + } + + return kr; +} + +//============================================================================= +kern_return_t Inspector::ServiceCheckOut(const char *receive_port_name) { + // We're done receiving mach messages from the crashed process, + // so clean up a bit. + kern_return_t kr; + + // DO NOT use mach_port_deallocate() here -- it will fail and the + // following bootstrap_register() will also fail leaving our service + // name hanging around forever (until reboot) + kr = mach_port_destroy(mach_task_self(), service_rcv_port_); + + if (kr != KERN_SUCCESS) { + PRINT_MACH_RESULT(kr, + "Inspector: UNREGISTERING: service_rcv_port mach_port_deallocate()"); + return kr; + } + + // Unregister the service associated with the receive port. + kr = breakpad::BootstrapRegister(bootstrap_subset_port_, + (char*)receive_port_name, + MACH_PORT_NULL); + + if (kr != KERN_SUCCESS) { + PRINT_MACH_RESULT(kr, "Inspector: UNREGISTERING: bootstrap_register()"); + } + + return kr; +} + +//============================================================================= +kern_return_t Inspector::ReadMessages() { + // Wait for an initial message from the crashed process containing basic + // information about the crash. + ReceivePort receive_port(service_rcv_port_); + + MachReceiveMessage message; + kern_return_t result = receive_port.WaitForMessage(&message, 1000); + + if (result == KERN_SUCCESS) { + InspectorInfo &info = (InspectorInfo &)*message.GetData(); + exception_type_ = info.exception_type; + exception_code_ = info.exception_code; + exception_subcode_ = info.exception_subcode; + +#if VERBOSE + printf("message ID = %d\n", message.GetMessageID()); +#endif + + remote_task_ = message.GetTranslatedPort(0); + crashing_thread_ = message.GetTranslatedPort(1); + handler_thread_ = message.GetTranslatedPort(2); + ack_port_ = message.GetTranslatedPort(3); + +#if VERBOSE + printf("exception_type = %d\n", exception_type_); + printf("exception_code = %d\n", exception_code_); + printf("exception_subcode = %d\n", exception_subcode_); + printf("remote_task = %d\n", remote_task_); + printf("crashing_thread = %d\n", crashing_thread_); + printf("handler_thread = %d\n", handler_thread_); + printf("ack_port_ = %d\n", ack_port_); + printf("parameter count = %d\n", info.parameter_count); +#endif + + // In certain situations where multiple crash requests come + // through quickly, we can end up with the mach IPC messages not + // coming through correctly. Since we don't know what parameters + // we've missed, we can't do much besides abort the crash dump + // situation in this case. + unsigned int parameters_read = 0; + // The initial message contains the number of key value pairs that + // we are expected to read. + // Read each key/value pair, one mach message per key/value pair. + for (unsigned int i = 0; i < info.parameter_count; ++i) { + MachReceiveMessage parameter_message; + result = receive_port.WaitForMessage(¶meter_message, 1000); + + if(result == KERN_SUCCESS) { + KeyValueMessageData &key_value_data = + (KeyValueMessageData&)*parameter_message.GetData(); + // If we get a blank key, make sure we don't increment the + // parameter count; in some cases (notably on-demand generation + // many times in a short period of time) caused the Mach IPC + // messages to not come through correctly. + if (strlen(key_value_data.key) == 0) { + continue; + } + parameters_read++; + + config_params_.SetKeyValue(key_value_data.key, key_value_data.value); + } else { + PRINT_MACH_RESULT(result, "Inspector: key/value message"); + break; + } + } + if (parameters_read != info.parameter_count) { + return KERN_FAILURE; + } + } + + return result; +} + +//============================================================================= +bool Inspector::InspectTask() { + // keep the task quiet while we're looking at it + task_suspend(remote_task_); + + NSString *minidumpDir; + + const char *minidumpDirectory = + config_params_.GetValueForKey(BREAKPAD_DUMP_DIRECTORY); + + // If the client app has not specified a minidump directory, + // use a default of Library/<kDefaultLibrarySubdirectory>/<Product Name> + if (!minidumpDirectory || 0 == strlen(minidumpDirectory)) { + NSArray *libraryDirectories = + NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, + NSUserDomainMask, + YES); + + NSString *applicationSupportDirectory = + [libraryDirectories objectAtIndex:0]; + NSString *library_subdirectory = [NSString + stringWithUTF8String:kDefaultLibrarySubdirectory]; + NSString *breakpad_product = [NSString + stringWithUTF8String:config_params_.GetValueForKey(BREAKPAD_PRODUCT)]; + + NSArray *path_components = [NSArray + arrayWithObjects:applicationSupportDirectory, + library_subdirectory, + breakpad_product, + nil]; + + minidumpDir = [NSString pathWithComponents:path_components]; + } else { + minidumpDir = [[NSString stringWithUTF8String:minidumpDirectory] + stringByExpandingTildeInPath]; + } + + MinidumpLocation minidumpLocation(minidumpDir); + + // Obscure bug alert: + // Don't use [NSString stringWithFormat] to build up the path here since it + // assumes system encoding and in RTL locales will prepend an LTR override + // character for paths beginning with '/' which fileSystemRepresentation does + // not remove. Filed as rdar://6889706 . + NSString *path_ns = [NSString + stringWithUTF8String:minidumpLocation.GetPath()]; + NSString *pathid_ns = [NSString + stringWithUTF8String:minidumpLocation.GetID()]; + NSString *minidumpPath = [path_ns stringByAppendingPathComponent:pathid_ns]; + minidumpPath = [minidumpPath + stringByAppendingPathExtension:@"dmp"]; + + config_file_.WriteFile( 0, + &config_params_, + minidumpLocation.GetPath(), + minidumpLocation.GetID()); + + + MinidumpGenerator generator(remote_task_, handler_thread_); + + if (exception_type_ && exception_code_) { + generator.SetExceptionInformation(exception_type_, + exception_code_, + exception_subcode_, + crashing_thread_); + } + + + bool result = generator.Write([minidumpPath fileSystemRepresentation]); + + // let the task continue + task_resume(remote_task_); + + return result; +} + +//============================================================================= +// The crashed task needs to be told that the inspection has finished. +// It will wait on a mach port (with timeout) until we send acknowledgement. +kern_return_t Inspector::SendAcknowledgement() { + if (ack_port_ != MACH_PORT_DEAD) { + MachPortSender sender(ack_port_); + MachSendMessage ack_message(kMsgType_InspectorAcknowledgement); + + kern_return_t result = sender.SendMessage(ack_message, 2000); + +#if VERBOSE + PRINT_MACH_RESULT(result, "Inspector: sent acknowledgement"); +#endif + + return result; + } + + return KERN_INVALID_NAME; +} + +} // namespace google_breakpad + diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/InspectorMain.mm b/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/InspectorMain.mm new file mode 100644 index 000000000..137c6a1e1 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/InspectorMain.mm @@ -0,0 +1,65 @@ +// Copyright (c) 2007, 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. +// +// Main driver for Inspector + +#import "client/mac/crash_generation/Inspector.h" +#import <Cocoa/Cocoa.h> + +namespace google_breakpad { + +//============================================================================= +extern "C" { + +int main(int argc, char *const argv[]) { +#if DEBUG + // Since we're launched on-demand, this is necessary to see debugging + // output in the console window. + freopen("/dev/console", "w", stdout); + freopen("/dev/console", "w", stderr); +#endif + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + if (argc != 2) { + exit(0); + } + // Our first command-line argument contains the name of the service + // that we're providing. + google_breakpad::Inspector inspector; + inspector.Inspect(argv[1]); + + [pool release]; + + return 0; +} + +} // extern "C" + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/client_info.h b/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/client_info.h new file mode 100644 index 000000000..a3a95dcac --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/client_info.h @@ -0,0 +1,47 @@ +// Copyright (c) 2010 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_MAC_CRASH_GENERATION_CLIENT_INFO_H_ +#define CLIENT_MAC_CRASH_GENERATION_CLIENT_INFO_H_ + +namespace google_breakpad { + +class ClientInfo { + public: + explicit ClientInfo(pid_t pid) : pid_(pid) {} + + pid_t pid() const { return pid_; } + + private: + pid_t pid_; +}; + +} // namespace google_breakpad + +#endif // CLIENT_MAC_CRASH_GENERATION_CLIENT_INFO_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/crash_generation_client.cc b/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/crash_generation_client.cc new file mode 100644 index 000000000..f6bf14f58 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/crash_generation_client.cc @@ -0,0 +1,72 @@ +// Copyright (c) 2010 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/mac/crash_generation/crash_generation_client.h" + +#include "client/mac/crash_generation/crash_generation_server.h" +#include "common/mac/MachIPC.h" + +namespace google_breakpad { + +bool CrashGenerationClient::RequestDumpForException( + int exception_type, + int exception_code, + int exception_subcode, + mach_port_t crashing_thread) { + // The server will send a message to this port indicating that it + // has finished its work. + ReceivePort acknowledge_port; + + MachSendMessage message(kDumpRequestMessage); + message.AddDescriptor(mach_task_self()); // this task + message.AddDescriptor(crashing_thread); // crashing thread + message.AddDescriptor(MACH_PORT_NULL); // handler thread + message.AddDescriptor(acknowledge_port.GetPort()); // message receive port + + ExceptionInfo info; + info.exception_type = exception_type; + info.exception_code = exception_code; + info.exception_subcode = exception_subcode; + message.SetData(&info, sizeof(info)); + + const mach_msg_timeout_t kSendTimeoutMs = 2 * 1000; + kern_return_t result = sender_.SendMessage(message, kSendTimeoutMs); + if (result != KERN_SUCCESS) + return false; + + // Give the server slightly longer to reply since it has to + // inspect this task and write the minidump. + const mach_msg_timeout_t kReceiveTimeoutMs = 5 * 1000; + MachReceiveMessage acknowledge_message; + result = acknowledge_port.WaitForMessage(&acknowledge_message, + kReceiveTimeoutMs); + return result == KERN_SUCCESS; +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/crash_generation_client.h b/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/crash_generation_client.h new file mode 100644 index 000000000..527f577a5 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/crash_generation_client.h @@ -0,0 +1,65 @@ +// Copyright (c) 2010 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 GOOGLE_BREAKPAD_CLIENT_MAC_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_ +#define GOOGLE_BREAKPAD_CLIENT_MAC_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_ + +#include "common/mac/MachIPC.h" + +namespace google_breakpad { + +class CrashGenerationClient { + public: + explicit CrashGenerationClient(const char* mach_port_name) + : sender_(mach_port_name) { + } + + // Request the crash server to generate a dump. + // + // Return true if the dump was successful; false otherwise. + bool RequestDumpForException(int exception_type, + int exception_code, + int exception_subcode, + mach_port_t crashing_thread); + + bool RequestDump() { + return RequestDumpForException(0, 0, 0, MACH_PORT_NULL); + } + + private: + MachPortSender sender_; + + // Prevent copy construction and assignment. + CrashGenerationClient(const CrashGenerationClient&); + CrashGenerationClient& operator=(const CrashGenerationClient&); +}; + +} // namespace google_breakpad + +#endif // GOOGLE_BREAKPAD_CLIENT_MAC_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/crash_generation_server.cc b/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/crash_generation_server.cc new file mode 100644 index 000000000..451e8d9c2 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/crash_generation_server.cc @@ -0,0 +1,166 @@ +// Copyright (c) 2010 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/mac/crash_generation/crash_generation_server.h" + +#include <pthread.h> + +#include "client/mac/crash_generation/client_info.h" +#include "client/mac/handler/minidump_generator.h" +#include "common/mac/scoped_task_suspend-inl.h" + +namespace google_breakpad { + +CrashGenerationServer::CrashGenerationServer( + const char *mach_port_name, + FilterCallback filter, + void *filter_context, + OnClientDumpRequestCallback dump_callback, + void *dump_context, + OnClientExitingCallback exit_callback, + void *exit_context, + bool generate_dumps, + const std::string &dump_path) + : filter_(filter), + filter_context_(filter_context), + dump_callback_(dump_callback), + dump_context_(dump_context), + exit_callback_(exit_callback), + exit_context_(exit_context), + generate_dumps_(generate_dumps), + dump_dir_(dump_path.empty() ? "/tmp" : dump_path), + started_(false), + receive_port_(mach_port_name), + mach_port_name_(mach_port_name) { +} + +CrashGenerationServer::~CrashGenerationServer() { + if (started_) + Stop(); +} + +bool CrashGenerationServer::Start() { + int thread_create_result = pthread_create(&server_thread_, NULL, + &WaitForMessages, this); + started_ = thread_create_result == 0; + return started_; +} + +bool CrashGenerationServer::Stop() { + if (!started_) + return false; + + // Send a quit message to the background thread, and then join it. + MachPortSender sender(mach_port_name_.c_str()); + MachSendMessage quit_message(kQuitMessage); + const mach_msg_timeout_t kSendTimeoutMs = 2 * 1000; + kern_return_t result = sender.SendMessage(quit_message, kSendTimeoutMs); + if (result == KERN_SUCCESS) { + int thread_join_result = pthread_join(server_thread_, NULL); + started_ = thread_join_result != 0; + } + + return !started_; +} + +// static +void *CrashGenerationServer::WaitForMessages(void *server) { + CrashGenerationServer *self = + reinterpret_cast<CrashGenerationServer*>(server); + while (self->WaitForOneMessage()) {} + return NULL; +} + +bool CrashGenerationServer::WaitForOneMessage() { + MachReceiveMessage message; + kern_return_t result = receive_port_.WaitForMessage(&message, + MACH_MSG_TIMEOUT_NONE); + if (result == KERN_SUCCESS) { + switch (message.GetMessageID()) { + case kDumpRequestMessage: { + ExceptionInfo &info = (ExceptionInfo &)*message.GetData(); + + mach_port_t remote_task = message.GetTranslatedPort(0); + mach_port_t crashing_thread = message.GetTranslatedPort(1); + mach_port_t handler_thread = message.GetTranslatedPort(2); + mach_port_t ack_port = message.GetTranslatedPort(3); + pid_t remote_pid = -1; + pid_for_task(remote_task, &remote_pid); + ClientInfo client(remote_pid); + + bool result; + std::string dump_path; + if (generate_dumps_ && (!filter_ || filter_(filter_context_))) { + ScopedTaskSuspend suspend(remote_task); + + MinidumpGenerator generator(remote_task, handler_thread); + dump_path = generator.UniqueNameInDirectory(dump_dir_, NULL); + + if (info.exception_type && info.exception_code) { + generator.SetExceptionInformation(info.exception_type, + info.exception_code, + info.exception_subcode, + crashing_thread); + } + result = generator.Write(dump_path.c_str()); + } else { + result = true; + } + + if (result && dump_callback_) { + dump_callback_(dump_context_, client, dump_path); + } + + // TODO(ted): support a way for the client to send additional data, + // perhaps with a callback so users of the server can read the data + // themselves? + + if (ack_port != MACH_PORT_DEAD && ack_port != MACH_PORT_NULL) { + MachPortSender sender(ack_port); + MachSendMessage ack_message(kAcknowledgementMessage); + const mach_msg_timeout_t kSendTimeoutMs = 2 * 1000; + + sender.SendMessage(ack_message, kSendTimeoutMs); + } + + if (exit_callback_) { + exit_callback_(exit_context_, client); + } + break; + } + case kQuitMessage: + return false; + } + } else { // result != KERN_SUCCESS + return false; + } + return true; +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/crash_generation_server.h b/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/crash_generation_server.h new file mode 100644 index 000000000..85bd5b5e3 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/crash_generation_server.h @@ -0,0 +1,150 @@ +// Copyright (c) 2010 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 GOOGLE_BREAKPAD_CLIENT_MAC_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_ +#define GOOGLE_BREAKPAD_CLIENT_MAC_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_ + +#include <stdint.h> + +#include <string> + +#include "common/mac/MachIPC.h" + +namespace google_breakpad { + +class ClientInfo; + +// Messages the server can read via its mach port +enum { + kDumpRequestMessage = 1, + kAcknowledgementMessage = 2, + kQuitMessage = 3 +}; + +// Exception details sent by the client when requesting a dump. +struct ExceptionInfo { + int32_t exception_type; + int32_t exception_code; + int32_t exception_subcode; +}; + +class CrashGenerationServer { + public: + // WARNING: callbacks may be invoked on a different thread + // than that which creates the CrashGenerationServer. They must + // be thread safe. + typedef void (*OnClientDumpRequestCallback)(void *context, + const ClientInfo &client_info, + const std::string &file_path); + + typedef void (*OnClientExitingCallback)(void *context, + const ClientInfo &client_info); + // If a FilterCallback returns false, the dump will not be written. + typedef bool (*FilterCallback)(void *context); + + // Create an instance with the given parameters. + // + // mach_port_name: Named server port to listen on. + // filter: Callback for a client to cancel writing a dump. + // filter_context: Context for the filter callback. + // dump_callback: Callback for a client crash dump request. + // dump_context: Context for client crash dump request callback. + // exit_callback: Callback for client process exit. + // exit_context: Context for client exit callback. + // generate_dumps: Whether to automatically generate dumps. + // Client code of this class might want to generate dumps explicitly + // in the crash dump request callback. In that case, false can be + // passed for this parameter. + // dump_path: Path for generating dumps; required only if true is + // passed for generateDumps parameter; NULL can be passed otherwise. + CrashGenerationServer(const char *mach_port_name, + FilterCallback filter, + void *filter_context, + OnClientDumpRequestCallback dump_callback, + void *dump_context, + OnClientExitingCallback exit_callback, + void *exit_context, + bool generate_dumps, + const std::string &dump_path); + + ~CrashGenerationServer(); + + // Perform initialization steps needed to start listening to clients. + // + // Return true if initialization is successful; false otherwise. + bool Start(); + + // Stop the server. + bool Stop(); + + private: + // Return a unique filename at which a minidump can be written. + bool MakeMinidumpFilename(std::string &outFilename); + + // Loop reading client messages and responding to them until + // a quit message is received. + static void *WaitForMessages(void *server); + + // Wait for a single client message and respond to it. Returns false + // if a quit message was received or if an error occurred. + bool WaitForOneMessage(); + + FilterCallback filter_; + void *filter_context_; + + OnClientDumpRequestCallback dump_callback_; + void *dump_context_; + + OnClientExitingCallback exit_callback_; + void *exit_context_; + + bool generate_dumps_; + + std::string dump_dir_; + + bool started_; + + // The mach port that receives requests to dump from child processes. + ReceivePort receive_port_; + + // The name of the mach port. Stored so the Stop method can message + // the background thread to shut it down. + std::string mach_port_name_; + + // The thread that waits on the receive port. + pthread_t server_thread_; + + // Disable copy constructor and operator=. + CrashGenerationServer(const CrashGenerationServer&); + CrashGenerationServer& operator=(const CrashGenerationServer&); +}; + +} // namespace google_breakpad + +#endif // GOOGLE_BREAKPAD_CLIENT_MAC_CRASH_GENERATION_CRASH_GENERATION_SERVER_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/moz.build b/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/moz.build new file mode 100644 index 000000000..79afa4504 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/crash_generation/moz.build @@ -0,0 +1,19 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES += [ + 'crash_generation_client.cc', + 'crash_generation_server.cc', +] + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '../../..', +] + +if CONFIG['CLANG_CXX']: + CXXFLAGS += ['-Wno-shadow'] diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/breakpad_nlist_64.cc b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/breakpad_nlist_64.cc new file mode 100644 index 000000000..3492b823d --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/breakpad_nlist_64.cc @@ -0,0 +1,402 @@ +/* + * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS 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. + */ + + +/* + * This file was copied from libc/gen/nlist.c from Darwin's source code + * The version of nlist used as a base is from 10.5.2, libc-498 + * http://www.opensource.apple.com/darwinsource/10.5.2/Libc-498/gen/nlist.c + * + * The full tarball is at: + * http://www.opensource.apple.com/darwinsource/tarballs/apsl/Libc-498.tar.gz + * + * I've modified it to be compatible with 64-bit images. +*/ + +#include "breakpad_nlist_64.h" + +#include <CoreFoundation/CoreFoundation.h> +#include <fcntl.h> +#include <mach-o/nlist.h> +#include <mach-o/loader.h> +#include <mach-o/fat.h> +#include <mach/mach.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <TargetConditionals.h> +#include <unistd.h> + +/* Stuff lifted from <a.out.h> and <sys/exec.h> since they are gone */ +/* + * Header prepended to each a.out file. + */ +struct exec { + unsigned short a_machtype; /* machine type */ + unsigned short a_magic; /* magic number */ + unsigned long a_text; /* size of text segment */ + unsigned long a_data; /* size of initialized data */ + unsigned long a_bss; /* size of uninitialized data */ + unsigned long a_syms; /* size of symbol table */ + unsigned long a_entry; /* entry point */ + unsigned long a_trsize; /* size of text relocation */ + unsigned long a_drsize; /* size of data relocation */ +}; + +#define OMAGIC 0407 /* old impure format */ +#define NMAGIC 0410 /* read-only text */ +#define ZMAGIC 0413 /* demand load format */ + +#define N_BADMAG(x) \ + (((x).a_magic)!=OMAGIC && ((x).a_magic)!=NMAGIC && ((x).a_magic)!=ZMAGIC) +#define N_TXTOFF(x) \ + ((x).a_magic==ZMAGIC ? 0 : sizeof (struct exec)) +#define N_SYMOFF(x) \ + (N_TXTOFF(x) + (x).a_text+(x).a_data + (x).a_trsize+(x).a_drsize) + +// Traits structs for specializing function templates to handle +// 32-bit/64-bit Mach-O files. +template<typename T> +struct MachBits {}; + +typedef struct nlist nlist32; +typedef struct nlist_64 nlist64; + +template<> +struct MachBits<nlist32> { + typedef mach_header mach_header_type; + typedef uint32_t word_type; + static const uint32_t magic = MH_MAGIC; +}; + +template<> +struct MachBits<nlist64> { + typedef mach_header_64 mach_header_type; + typedef uint64_t word_type; + static const uint32_t magic = MH_MAGIC_64; +}; + +template<typename nlist_type> +int +__breakpad_fdnlist(int fd, nlist_type *list, const char **symbolNames, + cpu_type_t cpu_type); + +/* + * nlist - retreive attributes from name list (string table version) + */ + +template <typename nlist_type> +int breakpad_nlist_common(const char *name, + nlist_type *list, + const char **symbolNames, + cpu_type_t cpu_type) { + int fd = open(name, O_RDONLY, 0); + if (fd < 0) + return -1; + int n = __breakpad_fdnlist(fd, list, symbolNames, cpu_type); + close(fd); + return n; +} + +int breakpad_nlist(const char *name, + struct nlist *list, + const char **symbolNames, + cpu_type_t cpu_type) { + return breakpad_nlist_common(name, list, symbolNames, cpu_type); +} + +int breakpad_nlist(const char *name, + struct nlist_64 *list, + const char **symbolNames, + cpu_type_t cpu_type) { + return breakpad_nlist_common(name, list, symbolNames, cpu_type); +} + +/* Note: __fdnlist() is called from kvm_nlist in libkvm's kvm.c */ + +template<typename nlist_type> +int __breakpad_fdnlist(int fd, nlist_type *list, const char **symbolNames, + cpu_type_t cpu_type) { + typedef typename MachBits<nlist_type>::mach_header_type mach_header_type; + typedef typename MachBits<nlist_type>::word_type word_type; + + const uint32_t magic = MachBits<nlist_type>::magic; + + int maxlen = 500; + int nreq = 0; + for (nlist_type* q = list; + symbolNames[q-list] && symbolNames[q-list][0]; + q++, nreq++) { + + q->n_type = 0; + q->n_value = 0; + q->n_desc = 0; + q->n_sect = 0; + q->n_un.n_strx = 0; + } + + struct exec buf; + if (read(fd, (char *)&buf, sizeof(buf)) != sizeof(buf) || + (N_BADMAG(buf) && *((uint32_t *)&buf) != magic && + CFSwapInt32BigToHost(*((uint32_t *)&buf)) != FAT_MAGIC && + /* The following is the big-endian ppc64 check */ + (*((uint32_t*)&buf)) != FAT_MAGIC)) { + return -1; + } + + /* Deal with fat file if necessary */ + unsigned arch_offset = 0; + if (CFSwapInt32BigToHost(*((uint32_t *)&buf)) == FAT_MAGIC || + /* The following is the big-endian ppc64 check */ + *((unsigned int *)&buf) == FAT_MAGIC) { + /* Read in the fat header */ + struct fat_header fh; + if (lseek(fd, 0, SEEK_SET) == -1) { + return -1; + } + if (read(fd, (char *)&fh, sizeof(fh)) != sizeof(fh)) { + return -1; + } + + /* Convert fat_narchs to host byte order */ + fh.nfat_arch = CFSwapInt32BigToHost(fh.nfat_arch); + + /* Read in the fat archs */ + struct fat_arch *fat_archs = + (struct fat_arch *)malloc(fh.nfat_arch * sizeof(struct fat_arch)); + if (fat_archs == NULL) { + return -1; + } + if (read(fd, (char *)fat_archs, + sizeof(struct fat_arch) * fh.nfat_arch) != + (ssize_t)(sizeof(struct fat_arch) * fh.nfat_arch)) { + free(fat_archs); + return -1; + } + + /* + * Convert archs to host byte ordering (a constraint of + * cpusubtype_getbestarch() + */ + for (unsigned i = 0; i < fh.nfat_arch; i++) { + fat_archs[i].cputype = + CFSwapInt32BigToHost(fat_archs[i].cputype); + fat_archs[i].cpusubtype = + CFSwapInt32BigToHost(fat_archs[i].cpusubtype); + fat_archs[i].offset = + CFSwapInt32BigToHost(fat_archs[i].offset); + fat_archs[i].size = + CFSwapInt32BigToHost(fat_archs[i].size); + fat_archs[i].align = + CFSwapInt32BigToHost(fat_archs[i].align); + } + + struct fat_arch *fap = NULL; + for (unsigned i = 0; i < fh.nfat_arch; i++) { + if (fat_archs[i].cputype == cpu_type) { + fap = &fat_archs[i]; + break; + } + } + + if (!fap) { + free(fat_archs); + return -1; + } + arch_offset = fap->offset; + free(fat_archs); + + /* Read in the beginning of the architecture-specific file */ + if (lseek(fd, arch_offset, SEEK_SET) == -1) { + return -1; + } + if (read(fd, (char *)&buf, sizeof(buf)) != sizeof(buf)) { + return -1; + } + } + + off_t sa; /* symbol address */ + off_t ss; /* start of strings */ + register_t n; + if (*((unsigned int *)&buf) == magic) { + if (lseek(fd, arch_offset, SEEK_SET) == -1) { + return -1; + } + mach_header_type mh; + if (read(fd, (char *)&mh, sizeof(mh)) != sizeof(mh)) { + return -1; + } + + struct load_command *load_commands = + (struct load_command *)malloc(mh.sizeofcmds); + if (load_commands == NULL) { + return -1; + } + if (read(fd, (char *)load_commands, mh.sizeofcmds) != + (ssize_t)mh.sizeofcmds) { + free(load_commands); + return -1; + } + struct symtab_command *stp = NULL; + struct load_command *lcp = load_commands; + // iterate through all load commands, looking for + // LC_SYMTAB load command + for (uint32_t i = 0; i < mh.ncmds; i++) { + if (lcp->cmdsize % sizeof(word_type) != 0 || + lcp->cmdsize <= 0 || + (char *)lcp + lcp->cmdsize > + (char *)load_commands + mh.sizeofcmds) { + free(load_commands); + return -1; + } + if (lcp->cmd == LC_SYMTAB) { + if (lcp->cmdsize != + sizeof(struct symtab_command)) { + free(load_commands); + return -1; + } + stp = (struct symtab_command *)lcp; + break; + } + lcp = (struct load_command *) + ((char *)lcp + lcp->cmdsize); + } + if (stp == NULL) { + free(load_commands); + return -1; + } + // sa points to the beginning of the symbol table + sa = stp->symoff + arch_offset; + // ss points to the beginning of the string table + ss = stp->stroff + arch_offset; + // n is the number of bytes in the symbol table + // each symbol table entry is an nlist structure + n = stp->nsyms * sizeof(nlist_type); + free(load_commands); + } else { + sa = N_SYMOFF(buf) + arch_offset; + ss = sa + buf.a_syms + arch_offset; + n = buf.a_syms; + } + + if (lseek(fd, sa, SEEK_SET) == -1) { + return -1; + } + + // the algorithm here is to read the nlist entries in m-sized + // chunks into q. q is then iterated over. for each entry in q, + // use the string table index(q->n_un.n_strx) to read the symbol + // name, then scan the nlist entries passed in by the user(via p), + // and look for a match + while (n) { + nlist_type space[BUFSIZ/sizeof (nlist_type)]; + register_t m = sizeof (space); + + if (n < m) + m = n; + if (read(fd, (char *)space, m) != m) + break; + n -= m; + off_t savpos = lseek(fd, 0, SEEK_CUR); + if (savpos == -1) { + return -1; + } + for (nlist_type* q = space; (m -= sizeof(nlist_type)) >= 0; q++) { + char nambuf[BUFSIZ]; + + if (q->n_un.n_strx == 0 || q->n_type & N_STAB) + continue; + + // seek to the location in the binary where the symbol + // name is stored & read it into memory + if (lseek(fd, ss+q->n_un.n_strx, SEEK_SET) == -1) { + return -1; + } + if (read(fd, nambuf, maxlen+1) == -1) { + return -1; + } + const char *s2 = nambuf; + for (nlist_type *p = list; + symbolNames[p-list] && symbolNames[p-list][0]; + p++) { + // get the symbol name the user has passed in that + // corresponds to the nlist entry that we're looking at + const char *s1 = symbolNames[p - list]; + while (*s1) { + if (*s1++ != *s2++) + goto cont; + } + if (*s2) + goto cont; + + p->n_value = q->n_value; + p->n_type = q->n_type; + p->n_desc = q->n_desc; + p->n_sect = q->n_sect; + p->n_un.n_strx = q->n_un.n_strx; + if (--nreq == 0) + return nreq; + + break; + cont: ; + } + } + if (lseek(fd, savpos, SEEK_SET) == -1) { + return -1; + } + } + return nreq; +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/breakpad_nlist_64.h b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/breakpad_nlist_64.h new file mode 100644 index 000000000..1d2c63913 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/breakpad_nlist_64.h @@ -0,0 +1,47 @@ +// Copyright (c) 2008, 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. + +// breakpad_nlist.h +// +// This file is meant to provide a header for clients of the modified +// nlist function implemented to work on 64-bit. + +#ifndef CLIENT_MAC_HANDLER_BREAKPAD_NLIST_H__ + +#include <mach/machine.h> + +int breakpad_nlist(const char *name, + struct nlist *list, + const char **symbolNames, + cpu_type_t cpu_type); +int breakpad_nlist(const char *name, + struct nlist_64 *list, + const char **symbolNames, + cpu_type_t cpu_type); + +#endif /* CLIENT_MAC_HANDLER_BREAKPAD_NLIST_H__ */ diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/dynamic_images.cc b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/dynamic_images.cc new file mode 100644 index 000000000..cdba6df4a --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/dynamic_images.cc @@ -0,0 +1,573 @@ +// Copyright (c) 2007, 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/mac/handler/dynamic_images.h" + +extern "C" { // needed to compile on Leopard + #include <mach-o/nlist.h> + #include <stdlib.h> + #include <stdio.h> +} + +#include <assert.h> +#include <AvailabilityMacros.h> +#include <dlfcn.h> +#include <mach/task_info.h> +#include <sys/sysctl.h> +#include <TargetConditionals.h> +#include <unistd.h> + +#include <algorithm> +#include <string> +#include <vector> + +#include "breakpad_nlist_64.h" + +#if !TARGET_OS_IPHONE +#include <CoreServices/CoreServices.h> + +#ifndef MAC_OS_X_VERSION_10_6 +#define MAC_OS_X_VERSION_10_6 1060 +#endif + +#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6 + +// Fallback declarations for TASK_DYLD_INFO and friends, introduced in +// <mach/task_info.h> in the Mac OS X 10.6 SDK. +#define TASK_DYLD_INFO 17 +struct task_dyld_info { + mach_vm_address_t all_image_info_addr; + mach_vm_size_t all_image_info_size; +}; +typedef struct task_dyld_info task_dyld_info_data_t; +typedef struct task_dyld_info *task_dyld_info_t; +#define TASK_DYLD_INFO_COUNT (sizeof(task_dyld_info_data_t) / sizeof(natural_t)) + +#endif + +#endif // !TARGET_OS_IPHONE + +namespace google_breakpad { + +using std::string; +using std::vector; + +//============================================================================== +// Returns the size of the memory region containing |address| and the +// number of bytes from |address| to the end of the region. +// We potentially, will extend the size of the original +// region by the size of the following region if it's contiguous with the +// first in order to handle cases when we're reading strings and they +// straddle two vm regions. +// +static mach_vm_size_t GetMemoryRegionSize(task_port_t target_task, + const uint64_t address, + mach_vm_size_t *size_to_end) { + mach_vm_address_t region_base = (mach_vm_address_t)address; + mach_vm_size_t region_size; + natural_t nesting_level = 0; + vm_region_submap_info_64 submap_info; + mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT_64; + + // Get information about the vm region containing |address| + vm_region_recurse_info_t region_info; + region_info = reinterpret_cast<vm_region_recurse_info_t>(&submap_info); + + kern_return_t result = + mach_vm_region_recurse(target_task, + ®ion_base, + ®ion_size, + &nesting_level, + region_info, + &info_count); + + if (result == KERN_SUCCESS) { + // Get distance from |address| to the end of this region + *size_to_end = region_base + region_size -(mach_vm_address_t)address; + + // If we want to handle strings as long as 4096 characters we may need + // to check if there's a vm region immediately following the first one. + // If so, we need to extend |*size_to_end| to go all the way to the end + // of the second region. + if (*size_to_end < 4096) { + // Second region starts where the first one ends + mach_vm_address_t region_base2 = + (mach_vm_address_t)(region_base + region_size); + mach_vm_size_t region_size2; + + // Get information about the following vm region + result = + mach_vm_region_recurse(target_task, + ®ion_base2, + ®ion_size2, + &nesting_level, + region_info, + &info_count); + + // Extend region_size to go all the way to the end of the 2nd region + if (result == KERN_SUCCESS + && region_base2 == region_base + region_size) { + region_size += region_size2; + } + } + + *size_to_end = region_base + region_size -(mach_vm_address_t)address; + } else { + region_size = 0; + *size_to_end = 0; + } + + return region_size; +} + +#define kMaxStringLength 8192 +//============================================================================== +// Reads a NULL-terminated string from another task. +// +// Warning! This will not read any strings longer than kMaxStringLength-1 +// +static string ReadTaskString(task_port_t target_task, + const uint64_t address) { + // The problem is we don't know how much to read until we know how long + // the string is. And we don't know how long the string is, until we've read + // the memory! So, we'll try to read kMaxStringLength bytes + // (or as many bytes as we can until we reach the end of the vm region). + mach_vm_size_t size_to_end; + GetMemoryRegionSize(target_task, address, &size_to_end); + + if (size_to_end > 0) { + mach_vm_size_t size_to_read = + size_to_end > kMaxStringLength ? kMaxStringLength : size_to_end; + + vector<uint8_t> bytes; + if (ReadTaskMemory(target_task, address, (size_t)size_to_read, bytes) != + KERN_SUCCESS) + return string(); + + return string(reinterpret_cast<const char*>(&bytes[0])); + } + + return string(); +} + +//============================================================================== +// Reads an address range from another task. The bytes read will be returned +// in bytes, which will be resized as necessary. +kern_return_t ReadTaskMemory(task_port_t target_task, + const uint64_t address, + size_t length, + vector<uint8_t> &bytes) { + int systemPageSize = getpagesize(); + + // use the negative of the page size for the mask to find the page address + mach_vm_address_t page_address = address & (-systemPageSize); + + mach_vm_address_t last_page_address = + (address + length + (systemPageSize - 1)) & (-systemPageSize); + + mach_vm_size_t page_size = last_page_address - page_address; + uint8_t* local_start; + uint32_t local_length; + + kern_return_t r = mach_vm_read(target_task, + page_address, + page_size, + reinterpret_cast<vm_offset_t*>(&local_start), + &local_length); + + if (r != KERN_SUCCESS) + return r; + + bytes.resize(length); + memcpy(&bytes[0], + &local_start[(mach_vm_address_t)address - page_address], + length); + mach_vm_deallocate(mach_task_self(), (uintptr_t)local_start, local_length); + return KERN_SUCCESS; +} + +#pragma mark - + +//============================================================================== +// Traits structs for specializing function templates to handle +// 32-bit/64-bit Mach-O files. +struct MachO32 { + typedef mach_header mach_header_type; + typedef segment_command mach_segment_command_type; + typedef dyld_image_info32 dyld_image_info; + typedef dyld_all_image_infos32 dyld_all_image_infos; + typedef struct nlist nlist_type; + static const uint32_t magic = MH_MAGIC; + static const uint32_t segment_load_command = LC_SEGMENT; +}; + +struct MachO64 { + typedef mach_header_64 mach_header_type; + typedef segment_command_64 mach_segment_command_type; + typedef dyld_image_info64 dyld_image_info; + typedef dyld_all_image_infos64 dyld_all_image_infos; + typedef struct nlist_64 nlist_type; + static const uint32_t magic = MH_MAGIC_64; + static const uint32_t segment_load_command = LC_SEGMENT_64; +}; + +template<typename MachBits> +bool FindTextSection(DynamicImage& image) { + typedef typename MachBits::mach_header_type mach_header_type; + typedef typename MachBits::mach_segment_command_type + mach_segment_command_type; + + const mach_header_type* header = + reinterpret_cast<const mach_header_type*>(&image.header_[0]); + + if(header->magic != MachBits::magic) { + return false; + } + + const struct load_command *cmd = + reinterpret_cast<const struct load_command *>(header + 1); + + bool found_text_section = false; + bool found_dylib_id_command = false; + for (unsigned int i = 0; cmd && (i < header->ncmds); ++i) { + if (!found_text_section) { + if (cmd->cmd == MachBits::segment_load_command) { + const mach_segment_command_type *seg = + reinterpret_cast<const mach_segment_command_type *>(cmd); + + if (!strcmp(seg->segname, "__TEXT")) { + image.vmaddr_ = static_cast<mach_vm_address_t>(seg->vmaddr); + image.vmsize_ = static_cast<mach_vm_size_t>(seg->vmsize); + image.slide_ = 0; + + if (seg->fileoff == 0 && seg->filesize != 0) { + image.slide_ = + (uintptr_t)image.GetLoadAddress() - (uintptr_t)seg->vmaddr; + } + found_text_section = true; + } + } + } + + if (!found_dylib_id_command) { + if (cmd->cmd == LC_ID_DYLIB) { + const struct dylib_command *dc = + reinterpret_cast<const struct dylib_command *>(cmd); + + image.version_ = dc->dylib.current_version; + found_dylib_id_command = true; + } + } + + if (found_dylib_id_command && found_text_section) { + return true; + } + + cmd = reinterpret_cast<const struct load_command *> + (reinterpret_cast<const char *>(cmd) + cmd->cmdsize); + } + + return false; +} + +//============================================================================== +// Initializes vmaddr_, vmsize_, and slide_ +void DynamicImage::CalculateMemoryAndVersionInfo() { + // unless we can process the header, ensure that calls to + // IsValid() will return false + vmaddr_ = 0; + vmsize_ = 0; + slide_ = 0; + version_ = 0; + + // The function template above does all the real work. + if (Is64Bit()) + FindTextSection<MachO64>(*this); + else + FindTextSection<MachO32>(*this); +} + +//============================================================================== +// The helper function template abstracts the 32/64-bit differences. +template<typename MachBits> +uint32_t GetFileTypeFromHeader(DynamicImage& image) { + typedef typename MachBits::mach_header_type mach_header_type; + + const mach_header_type* header = + reinterpret_cast<const mach_header_type*>(&image.header_[0]); + return header->filetype; +} + +uint32_t DynamicImage::GetFileType() { + if (Is64Bit()) + return GetFileTypeFromHeader<MachO64>(*this); + + return GetFileTypeFromHeader<MachO32>(*this); +} + +#pragma mark - + +//============================================================================== +// Loads information about dynamically loaded code in the given task. +DynamicImages::DynamicImages(mach_port_t task) + : task_(task), + cpu_type_(DetermineTaskCPUType(task)), + image_list_() { + ReadImageInfoForTask(); +} + +template<typename MachBits> +static uint64_t LookupSymbol(const char* symbol_name, + const char* filename, + cpu_type_t cpu_type) { + typedef typename MachBits::nlist_type nlist_type; + + nlist_type symbol_info[8] = {}; + const char *symbolNames[2] = { symbol_name, "\0" }; + nlist_type &list = symbol_info[0]; + int invalidEntriesCount = breakpad_nlist(filename, + &list, + symbolNames, + cpu_type); + + if(invalidEntriesCount != 0) { + return 0; + } + + assert(list.n_value); + return list.n_value; +} + +#if TARGET_OS_IPHONE || MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6 +static bool HasTaskDyldInfo() { + return true; +} +#else +static SInt32 GetOSVersionInternal() { + SInt32 os_version = 0; + Gestalt(gestaltSystemVersion, &os_version); + return os_version; +} + +static SInt32 GetOSVersion() { + static SInt32 os_version = GetOSVersionInternal(); + return os_version; +} + +static bool HasTaskDyldInfo() { + return GetOSVersion() >= 0x1060; +} +#endif // TARGET_OS_IPHONE || MAC_OS_X_VERSION_MIN_REQUIRED >= 10_6 + +uint64_t DynamicImages::GetDyldAllImageInfosPointer() { + if (HasTaskDyldInfo()) { + task_dyld_info_data_t task_dyld_info; + mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; + if (task_info(task_, TASK_DYLD_INFO, (task_info_t)&task_dyld_info, + &count) != KERN_SUCCESS) { + return 0; + } + + return (uint64_t)task_dyld_info.all_image_info_addr; + } else { + const char *imageSymbolName = "_dyld_all_image_infos"; + const char *dyldPath = "/usr/lib/dyld"; + + if (Is64Bit()) + return LookupSymbol<MachO64>(imageSymbolName, dyldPath, cpu_type_); + return LookupSymbol<MachO32>(imageSymbolName, dyldPath, cpu_type_); + } +} + +//============================================================================== +// This code was written using dyld_debug.c (from Darwin) as a guide. + +template<typename MachBits> +void ReadImageInfo(DynamicImages& images, + uint64_t image_list_address) { + typedef typename MachBits::dyld_image_info dyld_image_info; + typedef typename MachBits::dyld_all_image_infos dyld_all_image_infos; + typedef typename MachBits::mach_header_type mach_header_type; + + // Read the structure inside of dyld that contains information about + // loaded images. We're reading from the desired task's address space. + + // Here we make the assumption that dyld loaded at the same address in + // the crashed process vs. this one. This is an assumption made in + // "dyld_debug.c" and is said to be nearly always valid. + vector<uint8_t> dyld_all_info_bytes; + if (ReadTaskMemory(images.task_, + image_list_address, + sizeof(dyld_all_image_infos), + dyld_all_info_bytes) != KERN_SUCCESS) + return; + + dyld_all_image_infos *dyldInfo = + reinterpret_cast<dyld_all_image_infos*>(&dyld_all_info_bytes[0]); + + // number of loaded images + int count = dyldInfo->infoArrayCount; + + // Read an array of dyld_image_info structures each containing + // information about a loaded image. + vector<uint8_t> dyld_info_array_bytes; + if (ReadTaskMemory(images.task_, + dyldInfo->infoArray, + count * sizeof(dyld_image_info), + dyld_info_array_bytes) != KERN_SUCCESS) + return; + + dyld_image_info *infoArray = + reinterpret_cast<dyld_image_info*>(&dyld_info_array_bytes[0]); + images.image_list_.reserve(count); + + for (int i = 0; i < count; ++i) { + dyld_image_info &info = infoArray[i]; + + // First read just the mach_header from the image in the task. + vector<uint8_t> mach_header_bytes; + if (ReadTaskMemory(images.task_, + info.load_address_, + sizeof(mach_header_type), + mach_header_bytes) != KERN_SUCCESS) + continue; // bail on this dynamic image + + mach_header_type *header = + reinterpret_cast<mach_header_type*>(&mach_header_bytes[0]); + + // Now determine the total amount necessary to read the header + // plus all of the load commands. + size_t header_size = + sizeof(mach_header_type) + header->sizeofcmds; + + if (ReadTaskMemory(images.task_, + info.load_address_, + header_size, + mach_header_bytes) != KERN_SUCCESS) + continue; + + // Read the file name from the task's memory space. + string file_path; + if (info.file_path_) { + // Although we're reading kMaxStringLength bytes, it's copied in the + // the DynamicImage constructor below with the correct string length, + // so it's not really wasting memory. + file_path = ReadTaskString(images.task_, info.file_path_); + } + + // Create an object representing this image and add it to our list. + DynamicImage *new_image; + new_image = new DynamicImage(&mach_header_bytes[0], + header_size, + info.load_address_, + file_path, + static_cast<uintptr_t>(info.file_mod_date_), + images.task_, + images.cpu_type_); + + if (new_image->IsValid()) { + images.image_list_.push_back(DynamicImageRef(new_image)); + } else { + delete new_image; + } + } + + // sorts based on loading address + sort(images.image_list_.begin(), images.image_list_.end()); + // remove duplicates - this happens in certain strange cases + // You can see it in DashboardClient when Google Gadgets plugin + // is installed. Apple's crash reporter log and gdb "info shared" + // both show the same library multiple times at the same address + + vector<DynamicImageRef>::iterator it = unique(images.image_list_.begin(), + images.image_list_.end()); + images.image_list_.erase(it, images.image_list_.end()); +} + +void DynamicImages::ReadImageInfoForTask() { + uint64_t imageList = GetDyldAllImageInfosPointer(); + + if (imageList) { + if (Is64Bit()) + ReadImageInfo<MachO64>(*this, imageList); + else + ReadImageInfo<MachO32>(*this, imageList); + } +} + +//============================================================================== +DynamicImage *DynamicImages::GetExecutableImage() { + int executable_index = GetExecutableImageIndex(); + + if (executable_index >= 0) { + return GetImage(executable_index); + } + + return NULL; +} + +//============================================================================== +// returns -1 if failure to find executable +int DynamicImages::GetExecutableImageIndex() { + int image_count = GetImageCount(); + + for (int i = 0; i < image_count; ++i) { + DynamicImage *image = GetImage(i); + if (image->GetFileType() == MH_EXECUTE) { + return i; + } + } + + return -1; +} + +//============================================================================== +// static +cpu_type_t DynamicImages::DetermineTaskCPUType(task_t task) { + if (task == mach_task_self()) + return GetNativeCPUType(); + + int mib[CTL_MAXNAME]; + size_t mibLen = CTL_MAXNAME; + int err = sysctlnametomib("sysctl.proc_cputype", mib, &mibLen); + if (err == 0) { + assert(mibLen < CTL_MAXNAME); + pid_for_task(task, &mib[mibLen]); + mibLen += 1; + + cpu_type_t cpu_type; + size_t cpuTypeSize = sizeof(cpu_type); + sysctl(mib, static_cast<u_int>(mibLen), &cpu_type, &cpuTypeSize, 0, 0); + return cpu_type; + } + + return GetNativeCPUType(); +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/dynamic_images.h b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/dynamic_images.h new file mode 100644 index 000000000..65147900b --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/dynamic_images.h @@ -0,0 +1,319 @@ +// Copyright (c) 2007, 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. + +// dynamic_images.h +// +// Implements most of the function of the dyld API, but allowing an +// arbitrary task to be introspected, unlike the dyld API which +// only allows operation on the current task. The current implementation +// is limited to use by 32-bit tasks. + +#ifndef CLIENT_MAC_HANDLER_DYNAMIC_IMAGES_H__ +#define CLIENT_MAC_HANDLER_DYNAMIC_IMAGES_H__ + +#include <mach/mach.h> +#include <mach-o/dyld.h> +#include <mach-o/loader.h> +#include <sys/types.h> + +#include <string> +#include <vector> + +#include "mach_vm_compat.h" + +namespace google_breakpad { + +using std::string; +using std::vector; + +//============================================================================== +// The memory layout of this struct matches the dyld_image_info struct +// defined in "dyld_gdb.h" in the darwin source. +typedef struct dyld_image_info32 { + uint32_t load_address_; // struct mach_header* + uint32_t file_path_; // char* + uint32_t file_mod_date_; +} dyld_image_info32; + +typedef struct dyld_image_info64 { + uint64_t load_address_; // struct mach_header* + uint64_t file_path_; // char* + uint64_t file_mod_date_; +} dyld_image_info64; + +//============================================================================== +// This is as defined in "dyld_gdb.h" in the darwin source. +// _dyld_all_image_infos (in dyld) is a structure of this type +// which will be used to determine which dynamic code has been loaded. +typedef struct dyld_all_image_infos32 { + uint32_t version; // == 1 in Mac OS X 10.4 + uint32_t infoArrayCount; + uint32_t infoArray; // const struct dyld_image_info* + uint32_t notification; + bool processDetachedFromSharedRegion; +} dyld_all_image_infos32; + +typedef struct dyld_all_image_infos64 { + uint32_t version; // == 1 in Mac OS X 10.4 + uint32_t infoArrayCount; + uint64_t infoArray; // const struct dyld_image_info* + uint64_t notification; + bool processDetachedFromSharedRegion; +} dyld_all_image_infos64; + +// some typedefs to isolate 64/32 bit differences +#ifdef __LP64__ +typedef mach_header_64 breakpad_mach_header; +typedef segment_command_64 breakpad_mach_segment_command; +#else +typedef mach_header breakpad_mach_header; +typedef segment_command breakpad_mach_segment_command; +#endif + +// Helper functions to deal with 32-bit/64-bit Mach-O differences. +class DynamicImage; +template<typename MachBits> +bool FindTextSection(DynamicImage& image); + +template<typename MachBits> +uint32_t GetFileTypeFromHeader(DynamicImage& image); + +//============================================================================== +// Represents a single dynamically loaded mach-o image +class DynamicImage { + public: + DynamicImage(uint8_t *header, // data is copied + size_t header_size, // includes load commands + uint64_t load_address, + string file_path, + uintptr_t image_mod_date, + mach_port_t task, + cpu_type_t cpu_type) + : header_(header, header + header_size), + header_size_(header_size), + load_address_(load_address), + vmaddr_(0), + vmsize_(0), + slide_(0), + version_(0), + file_path_(file_path), + file_mod_date_(image_mod_date), + task_(task), + cpu_type_(cpu_type) { + CalculateMemoryAndVersionInfo(); + } + + // Size of mach_header plus load commands + size_t GetHeaderSize() const {return header_.size();} + + // Full path to mach-o binary + string GetFilePath() {return file_path_;} + + uint64_t GetModDate() const {return file_mod_date_;} + + // Actual address where the image was loaded + uint64_t GetLoadAddress() const {return load_address_;} + + // Address where the image should be loaded + mach_vm_address_t GetVMAddr() const {return vmaddr_;} + + // Difference between GetLoadAddress() and GetVMAddr() + ptrdiff_t GetVMAddrSlide() const {return slide_;} + + // Size of the image + mach_vm_size_t GetVMSize() const {return vmsize_;} + + // Task owning this loaded image + mach_port_t GetTask() {return task_;} + + // CPU type of the task + cpu_type_t GetCPUType() {return cpu_type_;} + + // filetype from the Mach-O header. + uint32_t GetFileType(); + + // Return true if the task is a 64-bit architecture. + bool Is64Bit() { return (GetCPUType() & CPU_ARCH_ABI64) == CPU_ARCH_ABI64; } + + uint32_t GetVersion() {return version_;} + // For sorting + bool operator<(const DynamicImage &inInfo) { + return GetLoadAddress() < inInfo.GetLoadAddress(); + } + + // Sanity checking + bool IsValid() {return GetVMSize() != 0;} + + private: + DynamicImage(const DynamicImage &); + DynamicImage &operator=(const DynamicImage &); + + friend class DynamicImages; + template<typename MachBits> + friend bool FindTextSection(DynamicImage& image); + template<typename MachBits> + friend uint32_t GetFileTypeFromHeader(DynamicImage& image); + + // Initializes vmaddr_, vmsize_, and slide_ + void CalculateMemoryAndVersionInfo(); + + const vector<uint8_t> header_; // our local copy of the header + size_t header_size_; // mach_header plus load commands + uint64_t load_address_; // base address image is mapped into + mach_vm_address_t vmaddr_; + mach_vm_size_t vmsize_; + ptrdiff_t slide_; + uint32_t version_; // Dylib version + string file_path_; // path dyld used to load the image + uintptr_t file_mod_date_; // time_t of image file + + mach_port_t task_; + cpu_type_t cpu_type_; // CPU type of task_ +}; + +//============================================================================== +// DynamicImageRef is just a simple wrapper for a pointer to +// DynamicImage. The reason we use it instead of a simple typedef is so +// that we can use stl::sort() on a vector of DynamicImageRefs +// and simple class pointers can't implement operator<(). +// +class DynamicImageRef { + public: + explicit DynamicImageRef(DynamicImage *inP) : p(inP) {} + // The copy constructor is required by STL + DynamicImageRef(const DynamicImageRef &inRef) : p(inRef.p) {} + + bool operator<(const DynamicImageRef &inRef) const { + return (*const_cast<DynamicImageRef*>(this)->p) + < (*const_cast<DynamicImageRef&>(inRef).p); + } + + bool operator==(const DynamicImageRef &inInfo) const { + return (*const_cast<DynamicImageRef*>(this)->p).GetLoadAddress() == + (*const_cast<DynamicImageRef&>(inInfo)).GetLoadAddress(); + } + + // Be just like DynamicImage* + DynamicImage *operator->() {return p;} + operator DynamicImage*() {return p;} + + private: + DynamicImage *p; +}; + +// Helper function to deal with 32-bit/64-bit Mach-O differences. +class DynamicImages; +template<typename MachBits> +void ReadImageInfo(DynamicImages& images, uint64_t image_list_address); + +//============================================================================== +// An object of type DynamicImages may be created to allow introspection of +// an arbitrary task's dynamically loaded mach-o binaries. This makes the +// assumption that the current task has send rights to the target task. +class DynamicImages { + public: + explicit DynamicImages(mach_port_t task); + + ~DynamicImages() { + for (int i = 0; i < GetImageCount(); ++i) { + delete image_list_[i]; + } + } + + // Returns the number of dynamically loaded mach-o images. + int GetImageCount() const {return static_cast<int>(image_list_.size());} + + // Returns an individual image. + DynamicImage *GetImage(int i) { + if (i < (int)image_list_.size()) { + return image_list_[i]; + } + return NULL; + } + + // Returns the image corresponding to the main executable. + DynamicImage *GetExecutableImage(); + int GetExecutableImageIndex(); + + // Returns the task which we're looking at. + mach_port_t GetTask() const {return task_;} + + // CPU type of the task + cpu_type_t GetCPUType() {return cpu_type_;} + + // Return true if the task is a 64-bit architecture. + bool Is64Bit() { return (GetCPUType() & CPU_ARCH_ABI64) == CPU_ARCH_ABI64; } + + // Determine the CPU type of the task being dumped. + static cpu_type_t DetermineTaskCPUType(task_t task); + + // Get the native CPU type of this task. + static cpu_type_t GetNativeCPUType() { +#if defined(__i386__) + return CPU_TYPE_I386; +#elif defined(__x86_64__) + return CPU_TYPE_X86_64; +#elif defined(__ppc__) + return CPU_TYPE_POWERPC; +#elif defined(__ppc64__) + return CPU_TYPE_POWERPC64; +#elif defined(__arm__) + return CPU_TYPE_ARM; +#elif defined(__aarch64__) + return CPU_TYPE_ARM64; +#else +#error "GetNativeCPUType not implemented for this architecture" +#endif + } + + private: + template<typename MachBits> + friend void ReadImageInfo(DynamicImages& images, uint64_t image_list_address); + + bool IsOurTask() {return task_ == mach_task_self();} + + // Initialization + void ReadImageInfoForTask(); + uint64_t GetDyldAllImageInfosPointer(); + + mach_port_t task_; + cpu_type_t cpu_type_; // CPU type of task_ + vector<DynamicImageRef> image_list_; +}; + +// Fill bytes with the contents of memory at a particular +// location in another task. +kern_return_t ReadTaskMemory(task_port_t target_task, + const uint64_t address, + size_t length, + vector<uint8_t> &bytes); + +} // namespace google_breakpad + +#endif // CLIENT_MAC_HANDLER_DYNAMIC_IMAGES_H__ diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/exception_handler.cc b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/exception_handler.cc new file mode 100644 index 000000000..dd0e1678c --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/exception_handler.cc @@ -0,0 +1,854 @@ +// Copyright (c) 2006, 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 <mach/exc.h> +#include <mach/mig.h> +#include <pthread.h> +#include <signal.h> +#include <TargetConditionals.h> + +#include <map> + +#include "client/mac/handler/exception_handler.h" +#include "client/mac/handler/minidump_generator.h" +#include "common/mac/macho_utilities.h" +#include "common/mac/scoped_task_suspend-inl.h" +#include "google_breakpad/common/minidump_exception_mac.h" + +#ifndef __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 + +#ifndef USE_PROTECTED_ALLOCATIONS +#if TARGET_OS_IPHONE +#define USE_PROTECTED_ALLOCATIONS 1 +#else +#define USE_PROTECTED_ALLOCATIONS 0 +#endif +#endif + +// If USE_PROTECTED_ALLOCATIONS is activated then the +// gBreakpadAllocator needs to be setup in other code +// ahead of time. Please see ProtectedMemoryAllocator.h +// for more details. +#if USE_PROTECTED_ALLOCATIONS + #include "protected_memory_allocator.h" + extern ProtectedMemoryAllocator *gBreakpadAllocator; +#endif + +namespace google_breakpad { + +static union { +#if USE_PROTECTED_ALLOCATIONS +#if defined PAGE_MAX_SIZE + char protected_buffer[PAGE_MAX_SIZE] __attribute__((aligned(PAGE_MAX_SIZE))); +#else + char protected_buffer[PAGE_SIZE] __attribute__((aligned(PAGE_SIZE))); +#endif // defined PAGE_MAX_SIZE +#endif // USE_PROTECTED_ALLOCATIONS + google_breakpad::ExceptionHandler *handler; +} gProtectedData; + +using std::map; + +// These structures and techniques are illustrated in +// Mac OS X Internals, Amit Singh, ch 9.7 +struct ExceptionMessage { + mach_msg_header_t header; + mach_msg_body_t body; + mach_msg_port_descriptor_t thread; + mach_msg_port_descriptor_t task; + NDR_record_t ndr; + exception_type_t exception; + mach_msg_type_number_t code_count; + integer_t code[EXCEPTION_CODE_MAX]; + char padding[512]; +}; + +struct ExceptionParameters { + ExceptionParameters() : count(0) {} + mach_msg_type_number_t count; + exception_mask_t masks[EXC_TYPES_COUNT]; + mach_port_t ports[EXC_TYPES_COUNT]; + exception_behavior_t behaviors[EXC_TYPES_COUNT]; + thread_state_flavor_t flavors[EXC_TYPES_COUNT]; +}; + +struct ExceptionReplyMessage { + mach_msg_header_t header; + NDR_record_t ndr; + kern_return_t return_code; +}; + +// Only catch these three exceptions. The other ones are nebulously defined +// and may result in treating a non-fatal exception as fatal. +exception_mask_t s_exception_mask = EXC_MASK_BAD_ACCESS | +EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC | EXC_MASK_BREAKPOINT; + +#if !TARGET_OS_IPHONE +extern "C" { + // Forward declarations for functions that need "C" style compilation + boolean_t exc_server(mach_msg_header_t* request, + mach_msg_header_t* reply); + + // This symbol must be visible to dlsym() - see + // http://code.google.com/p/google-breakpad/issues/detail?id=345 for details. + kern_return_t catch_exception_raise(mach_port_t target_port, + mach_port_t failed_thread, + mach_port_t task, + exception_type_t exception, + exception_data_t code, + mach_msg_type_number_t code_count) + __attribute__((visibility("default"))); +} +#endif + +kern_return_t ForwardException(mach_port_t task, + mach_port_t failed_thread, + exception_type_t exception, + exception_data_t code, + mach_msg_type_number_t code_count); + +#if TARGET_OS_IPHONE +// Implementation is based on the implementation generated by mig. +boolean_t breakpad_exc_server(mach_msg_header_t* InHeadP, + mach_msg_header_t* OutHeadP) { + OutHeadP->msgh_bits = + MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(InHeadP->msgh_bits), 0); + OutHeadP->msgh_remote_port = InHeadP->msgh_remote_port; + /* Minimal size: routine() will update it if different */ + OutHeadP->msgh_size = (mach_msg_size_t)sizeof(mig_reply_error_t); + OutHeadP->msgh_local_port = MACH_PORT_NULL; + OutHeadP->msgh_id = InHeadP->msgh_id + 100; + + if (InHeadP->msgh_id != 2401) { + ((mig_reply_error_t*)OutHeadP)->NDR = NDR_record; + ((mig_reply_error_t*)OutHeadP)->RetCode = MIG_BAD_ID; + return FALSE; + } + +#ifdef __MigPackStructs +#pragma pack(4) +#endif + typedef struct { + mach_msg_header_t Head; + /* start of the kernel processed data */ + mach_msg_body_t msgh_body; + mach_msg_port_descriptor_t thread; + mach_msg_port_descriptor_t task; + /* end of the kernel processed data */ + NDR_record_t NDR; + exception_type_t exception; + mach_msg_type_number_t codeCnt; + integer_t code[2]; + mach_msg_trailer_t trailer; + } Request; + + typedef struct { + mach_msg_header_t Head; + NDR_record_t NDR; + kern_return_t RetCode; + } Reply; +#ifdef __MigPackStructs +#pragma pack() +#endif + + Request* In0P = (Request*)InHeadP; + Reply* OutP = (Reply*)OutHeadP; + + if (In0P->task.name != mach_task_self()) { + return FALSE; + } + OutP->RetCode = ForwardException(In0P->task.name, + In0P->thread.name, + In0P->exception, + In0P->code, + In0P->codeCnt); + OutP->NDR = NDR_record; + return TRUE; +} +#else +boolean_t breakpad_exc_server(mach_msg_header_t* request, + mach_msg_header_t* reply) { + return exc_server(request, reply); +} + +// Callback from exc_server() +kern_return_t catch_exception_raise(mach_port_t port, mach_port_t failed_thread, + mach_port_t task, + exception_type_t exception, + exception_data_t code, + mach_msg_type_number_t code_count) { + if (task != mach_task_self()) { + return KERN_FAILURE; + } + return ForwardException(task, failed_thread, exception, code, code_count); +} +#endif + +ExceptionHandler::ExceptionHandler(const string &dump_path, + FilterCallback filter, + MinidumpCallback callback, + void* callback_context, + bool install_handler, + const char* port_name) + : dump_path_(), + filter_(filter), + callback_(callback), + callback_context_(callback_context), + directCallback_(NULL), + handler_thread_(NULL), + handler_port_(MACH_PORT_NULL), + previous_(NULL), + installed_exception_handler_(false), + is_in_teardown_(false), + last_minidump_write_result_(false), + use_minidump_write_mutex_(false) { + // This will update to the ID and C-string pointers + set_dump_path(dump_path); + MinidumpGenerator::GatherSystemInformation(); +#if !TARGET_OS_IPHONE + if (port_name) + crash_generation_client_.reset(new CrashGenerationClient(port_name)); +#endif + Setup(install_handler); +} + +// special constructor if we want to bypass minidump writing and +// simply get a callback with the exception information +ExceptionHandler::ExceptionHandler(DirectCallback callback, + void* callback_context, + bool install_handler) + : dump_path_(), + filter_(NULL), + callback_(NULL), + callback_context_(callback_context), + directCallback_(callback), + handler_thread_(NULL), + handler_port_(MACH_PORT_NULL), + previous_(NULL), + installed_exception_handler_(false), + is_in_teardown_(false), + last_minidump_write_result_(false), + use_minidump_write_mutex_(false) { + MinidumpGenerator::GatherSystemInformation(); + Setup(install_handler); +} + +ExceptionHandler::~ExceptionHandler() { + Teardown(); +} + +bool ExceptionHandler::WriteMinidump(bool write_exception_stream) { + // If we're currently writing, just return + if (use_minidump_write_mutex_) + return false; + + use_minidump_write_mutex_ = true; + last_minidump_write_result_ = false; + + // Lock the mutex. Since we just created it, this will return immediately. + if (pthread_mutex_lock(&minidump_write_mutex_) == 0) { + // Send an empty message to the handle port so that a minidump will + // be written + bool result = SendMessageToHandlerThread(write_exception_stream ? + kWriteDumpWithExceptionMessage : + kWriteDumpMessage); + if (!result) { + pthread_mutex_unlock(&minidump_write_mutex_); + return false; + } + + // Wait for the minidump writer to complete its writing. It will unlock + // the mutex when completed + pthread_mutex_lock(&minidump_write_mutex_); + } + + use_minidump_write_mutex_ = false; + UpdateNextID(); + return last_minidump_write_result_; +} + +// static +bool ExceptionHandler::WriteMinidump(const string &dump_path, + bool write_exception_stream, + MinidumpCallback callback, + void* callback_context) { + ExceptionHandler handler(dump_path, NULL, callback, callback_context, false, + NULL); + return handler.WriteMinidump(write_exception_stream); +} + +// static +bool ExceptionHandler::WriteMinidumpForChild(mach_port_t child, + mach_port_t child_blamed_thread, + const string &dump_path, + MinidumpCallback callback, + void* callback_context) { + ScopedTaskSuspend suspend(child); + + MinidumpGenerator generator(child, MACH_PORT_NULL); + string dump_id; + string dump_filename = generator.UniqueNameInDirectory(dump_path, &dump_id); + + generator.SetExceptionInformation(EXC_BREAKPOINT, +#if defined(__i386__) || defined(__x86_64__) + EXC_I386_BPT, +#elif defined(__ppc__) || defined(__ppc64__) + EXC_PPC_BREAKPOINT, +#elif defined(__arm__) || defined(__aarch64__) + EXC_ARM_BREAKPOINT, +#else +#error architecture not supported +#endif + 0, + child_blamed_thread); + bool result = generator.Write(dump_filename.c_str()); + + if (callback) { + return callback(dump_path.c_str(), dump_id.c_str(), + callback_context, result); + } + return result; +} + +bool ExceptionHandler::WriteMinidumpWithException( + int exception_type, + int exception_code, + int exception_subcode, + breakpad_ucontext_t* task_context, + mach_port_t thread_name, + bool exit_after_write, + bool report_current_thread) { + bool result = false; + + if (directCallback_) { + if (directCallback_(callback_context_, + exception_type, + exception_code, + exception_subcode, + thread_name) ) { + if (exit_after_write) + _exit(exception_type); + } +#if !TARGET_OS_IPHONE + } else if (IsOutOfProcess()) { + if (exception_type && exception_code) { + // If this is a real exception, give the filter (if any) a chance to + // decide if this should be sent. + if (filter_ && !filter_(callback_context_)) + return false; + result = crash_generation_client_->RequestDumpForException( + exception_type, + exception_code, + exception_subcode, + thread_name); + if (result && exit_after_write) { + _exit(exception_type); + } + } +#endif + } else { + string minidump_id; + + // Putting the MinidumpGenerator in its own context will ensure that the + // destructor is executed, closing the newly created minidump file. + if (!dump_path_.empty()) { + MinidumpGenerator md(mach_task_self(), + report_current_thread ? MACH_PORT_NULL : + mach_thread_self()); + md.SetTaskContext(task_context); + if (exception_type && exception_code) { + // If this is a real exception, give the filter (if any) a chance to + // decide if this should be sent. + if (filter_ && !filter_(callback_context_)) + return false; + + md.SetExceptionInformation(exception_type, exception_code, + exception_subcode, thread_name); + } + + result = md.Write(next_minidump_path_c_); + } + + // Call user specified callback (if any) + if (callback_) { + // If the user callback returned true and we're handling an exception + // (rather than just writing out the file), then we should exit without + // forwarding the exception to the next handler. + if (callback_(dump_path_c_, next_minidump_id_c_, callback_context_, + result)) { + if (exit_after_write) + _exit(exception_type); + } + } + } + + return result; +} + +kern_return_t ForwardException(mach_port_t task, mach_port_t failed_thread, + exception_type_t exception, + exception_data_t code, + mach_msg_type_number_t code_count) { + // At this time, we should have called Uninstall() on the exception handler + // so that the current exception ports are the ones that we should be + // forwarding to. + ExceptionParameters current; + + current.count = EXC_TYPES_COUNT; + mach_port_t current_task = mach_task_self(); + task_get_exception_ports(current_task, + s_exception_mask, + current.masks, + ¤t.count, + current.ports, + current.behaviors, + current.flavors); + + // Find the first exception handler that matches the exception + unsigned int found; + for (found = 0; found < current.count; ++found) { + if (current.masks[found] & (1 << exception)) { + break; + } + } + + // Nothing to forward + if (found == current.count) { + fprintf(stderr, "** No previous ports for forwarding!! \n"); + exit(KERN_FAILURE); + } + + mach_port_t target_port = current.ports[found]; + exception_behavior_t target_behavior = current.behaviors[found]; + + kern_return_t result; + // TODO: Handle the case where |target_behavior| has MACH_EXCEPTION_CODES + // set. https://code.google.com/p/google-breakpad/issues/detail?id=551 + switch (target_behavior) { + case EXCEPTION_DEFAULT: + result = exception_raise(target_port, failed_thread, task, exception, + code, code_count); + break; + default: + fprintf(stderr, "** Unknown exception behavior: %d\n", target_behavior); + result = KERN_FAILURE; + break; + } + + return result; +} + +// static +void* ExceptionHandler::WaitForMessage(void* exception_handler_class) { + ExceptionHandler* self = + reinterpret_cast<ExceptionHandler*>(exception_handler_class); + ExceptionMessage receive; + + // Wait for the exception info + while (1) { + receive.header.msgh_local_port = self->handler_port_; + receive.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(receive)); + kern_return_t result = mach_msg(&(receive.header), + MACH_RCV_MSG | MACH_RCV_LARGE, 0, + receive.header.msgh_size, + self->handler_port_, + MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + + + if (result == KERN_SUCCESS) { + // Uninstall our handler so that we don't get in a loop if the process of + // writing out a minidump causes an exception. However, if the exception + // was caused by a fork'd process, don't uninstall things + + // If the actual exception code is zero, then we're calling this handler + // in a way that indicates that we want to either exit this thread or + // generate a minidump + // + // While reporting, all threads (except this one) must be suspended + // to avoid misleading stacks. If appropriate they will be resumed + // afterwards. + if (!receive.exception) { + // Don't touch self, since this message could have been sent + // from its destructor. + if (receive.header.msgh_id == kShutdownMessage) + return NULL; + + self->SuspendThreads(); + +#if USE_PROTECTED_ALLOCATIONS + if (gBreakpadAllocator) + gBreakpadAllocator->Unprotect(); +#endif + + mach_port_t thread = MACH_PORT_NULL; + int exception_type = 0; + int exception_code = 0; + if (receive.header.msgh_id == kWriteDumpWithExceptionMessage) { + thread = receive.thread.name; + exception_type = EXC_BREAKPOINT; +#if defined(__i386__) || defined(__x86_64__) + exception_code = EXC_I386_BPT; +#elif defined(__ppc__) || defined(__ppc64__) + exception_code = EXC_PPC_BREAKPOINT; +#elif defined(__arm__) || defined(__aarch64__) + exception_code = EXC_ARM_BREAKPOINT; +#else +#error architecture not supported +#endif + } + + // Write out the dump and save the result for later retrieval + self->last_minidump_write_result_ = + self->WriteMinidumpWithException(exception_type, exception_code, + 0, NULL, thread, + false, false); + +#if USE_PROTECTED_ALLOCATIONS + if (gBreakpadAllocator) + gBreakpadAllocator->Protect(); +#endif + + self->ResumeThreads(); + + if (self->use_minidump_write_mutex_) + pthread_mutex_unlock(&self->minidump_write_mutex_); + } else { + // When forking a child process with the exception handler installed, + // if the child crashes, it will send the exception back to the parent + // process. The check for task == self_task() ensures that only + // exceptions that occur in the parent process are caught and + // processed. If the exception was not caused by this task, we + // still need to call into the exception server and have it return + // KERN_FAILURE (see catch_exception_raise) in order for the kernel + // to move onto the host exception handler for the child task + if (receive.task.name == mach_task_self()) { + self->SuspendThreads(); + +#if USE_PROTECTED_ALLOCATIONS + if (gBreakpadAllocator) + gBreakpadAllocator->Unprotect(); +#endif + + int subcode = 0; + if (receive.exception == EXC_BAD_ACCESS && receive.code_count > 1) + subcode = receive.code[1]; + + // Generate the minidump with the exception data. + self->WriteMinidumpWithException(receive.exception, receive.code[0], + subcode, NULL, receive.thread.name, + true, false); + +#if USE_PROTECTED_ALLOCATIONS + // This may have become protected again within + // WriteMinidumpWithException, but it needs to be unprotected for + // UninstallHandler. + if (gBreakpadAllocator) + gBreakpadAllocator->Unprotect(); +#endif + + self->UninstallHandler(true); + +#if USE_PROTECTED_ALLOCATIONS + if (gBreakpadAllocator) + gBreakpadAllocator->Protect(); +#endif + } + // Pass along the exception to the server, which will setup the + // message and call catch_exception_raise() and put the return + // code into the reply. + ExceptionReplyMessage reply; + if (!breakpad_exc_server(&receive.header, &reply.header)) + exit(1); + + // Send a reply and exit + mach_msg(&(reply.header), MACH_SEND_MSG, + reply.header.msgh_size, 0, MACH_PORT_NULL, + MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + } + } + } + + return NULL; +} + +// static +void ExceptionHandler::SignalHandler(int sig, siginfo_t* info, void* uc) { +#if USE_PROTECTED_ALLOCATIONS + if (gBreakpadAllocator) + gBreakpadAllocator->Unprotect(); +#endif + gProtectedData.handler->WriteMinidumpWithException( + EXC_SOFTWARE, + MD_EXCEPTION_CODE_MAC_ABORT, + 0, + static_cast<breakpad_ucontext_t*>(uc), + mach_thread_self(), + true, + true); +#if USE_PROTECTED_ALLOCATIONS + if (gBreakpadAllocator) + gBreakpadAllocator->Protect(); +#endif +} + +bool ExceptionHandler::InstallHandler() { + // If a handler is already installed, something is really wrong. + if (gProtectedData.handler != NULL) { + return false; + } + + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sigaddset(&sa.sa_mask, SIGABRT); + sa.sa_sigaction = ExceptionHandler::SignalHandler; + sa.sa_flags = SA_SIGINFO; + + scoped_ptr<struct sigaction> old(new struct sigaction); + if (sigaction(SIGABRT, &sa, old.get()) == -1) { + return false; + } + old_handler_.swap(old); + gProtectedData.handler = this; +#if USE_PROTECTED_ALLOCATIONS + assert(((size_t)(gProtectedData.protected_buffer) & PAGE_MASK) == 0); + mprotect(gProtectedData.protected_buffer, PAGE_SIZE, PROT_READ); +#endif + + try { +#if USE_PROTECTED_ALLOCATIONS + previous_ = new (gBreakpadAllocator->Allocate(sizeof(ExceptionParameters)) ) + ExceptionParameters(); +#else + previous_ = new ExceptionParameters(); +#endif + } + catch (std::bad_alloc) { + return false; + } + + // Save the current exception ports so that we can forward to them + previous_->count = EXC_TYPES_COUNT; + mach_port_t current_task = mach_task_self(); + kern_return_t result = task_get_exception_ports(current_task, + s_exception_mask, + previous_->masks, + &previous_->count, + previous_->ports, + previous_->behaviors, + previous_->flavors); + + // Setup the exception ports on this task + if (result == KERN_SUCCESS) + result = task_set_exception_ports(current_task, s_exception_mask, + handler_port_, EXCEPTION_DEFAULT, + THREAD_STATE_NONE); + + installed_exception_handler_ = (result == KERN_SUCCESS); + + return installed_exception_handler_; +} + +bool ExceptionHandler::UninstallHandler(bool in_exception) { + kern_return_t result = KERN_SUCCESS; + + if (old_handler_.get()) { + sigaction(SIGABRT, old_handler_.get(), NULL); +#if USE_PROTECTED_ALLOCATIONS + mprotect(gProtectedData.protected_buffer, PAGE_SIZE, + PROT_READ | PROT_WRITE); +#endif + old_handler_.reset(); + gProtectedData.handler = NULL; + } + + if (installed_exception_handler_) { + mach_port_t current_task = mach_task_self(); + + // Restore the previous ports + for (unsigned int i = 0; i < previous_->count; ++i) { + result = task_set_exception_ports(current_task, previous_->masks[i], + previous_->ports[i], + previous_->behaviors[i], + previous_->flavors[i]); + if (result != KERN_SUCCESS) + return false; + } + + // this delete should NOT happen if an exception just occurred! + if (!in_exception) { +#if USE_PROTECTED_ALLOCATIONS + previous_->~ExceptionParameters(); +#else + delete previous_; +#endif + } + + previous_ = NULL; + installed_exception_handler_ = false; + } + + return result == KERN_SUCCESS; +} + +bool ExceptionHandler::Setup(bool install_handler) { + if (pthread_mutex_init(&minidump_write_mutex_, NULL)) + return false; + + // Create a receive right + mach_port_t current_task = mach_task_self(); + kern_return_t result = mach_port_allocate(current_task, + MACH_PORT_RIGHT_RECEIVE, + &handler_port_); + // Add send right + if (result == KERN_SUCCESS) + result = mach_port_insert_right(current_task, handler_port_, handler_port_, + MACH_MSG_TYPE_MAKE_SEND); + + if (install_handler && result == KERN_SUCCESS) + if (!InstallHandler()) + return false; + + if (result == KERN_SUCCESS) { + // Install the handler in its own thread, detached as we won't be joining. + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + int thread_create_result = pthread_create(&handler_thread_, &attr, + &WaitForMessage, this); + pthread_attr_destroy(&attr); + result = thread_create_result ? KERN_FAILURE : KERN_SUCCESS; + } + + return result == KERN_SUCCESS; +} + +bool ExceptionHandler::Teardown() { + kern_return_t result = KERN_SUCCESS; + is_in_teardown_ = true; + + if (!UninstallHandler(false)) + return false; + + // Send an empty message so that the handler_thread exits + if (SendMessageToHandlerThread(kShutdownMessage)) { + mach_port_t current_task = mach_task_self(); + result = mach_port_deallocate(current_task, handler_port_); + if (result != KERN_SUCCESS) + return false; + } else { + return false; + } + + handler_thread_ = NULL; + handler_port_ = MACH_PORT_NULL; + pthread_mutex_destroy(&minidump_write_mutex_); + + return result == KERN_SUCCESS; +} + +bool ExceptionHandler::SendMessageToHandlerThread( + HandlerThreadMessage message_id) { + ExceptionMessage msg; + memset(&msg, 0, sizeof(msg)); + msg.header.msgh_id = message_id; + if (message_id == kWriteDumpMessage || + message_id == kWriteDumpWithExceptionMessage) { + // Include this thread's port. + msg.thread.name = mach_thread_self(); + msg.thread.disposition = MACH_MSG_TYPE_PORT_SEND; + msg.thread.type = MACH_MSG_PORT_DESCRIPTOR; + } + msg.header.msgh_size = sizeof(msg) - sizeof(msg.padding); + msg.header.msgh_remote_port = handler_port_; + msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, + MACH_MSG_TYPE_MAKE_SEND_ONCE); + kern_return_t result = mach_msg(&(msg.header), + MACH_SEND_MSG | MACH_SEND_TIMEOUT, + msg.header.msgh_size, 0, 0, + MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + + return result == KERN_SUCCESS; +} + +void ExceptionHandler::UpdateNextID() { + next_minidump_path_ = + (MinidumpGenerator::UniqueNameInDirectory(dump_path_, &next_minidump_id_)); + + next_minidump_path_c_ = next_minidump_path_.c_str(); + next_minidump_id_c_ = next_minidump_id_.c_str(); +} + +bool ExceptionHandler::SuspendThreads() { + thread_act_port_array_t threads_for_task; + mach_msg_type_number_t thread_count; + + if (task_threads(mach_task_self(), &threads_for_task, &thread_count)) + return false; + + // suspend all of the threads except for this one + for (unsigned int i = 0; i < thread_count; ++i) { + if (threads_for_task[i] != mach_thread_self()) { + if (thread_suspend(threads_for_task[i])) + return false; + } + } + + return true; +} + +bool ExceptionHandler::ResumeThreads() { + thread_act_port_array_t threads_for_task; + mach_msg_type_number_t thread_count; + + if (task_threads(mach_task_self(), &threads_for_task, &thread_count)) + return false; + + // resume all of the threads except for this one + for (unsigned int i = 0; i < thread_count; ++i) { + if (threads_for_task[i] != mach_thread_self()) { + if (thread_resume(threads_for_task[i])) + return false; + } + } + + return true; +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/exception_handler.h b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/exception_handler.h new file mode 100644 index 000000000..f1d9ae92d --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/exception_handler.h @@ -0,0 +1,281 @@ +// Copyright (c) 2006, 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. + +// exception_handler.h: MacOS exception handler +// This class can install a Mach exception port handler to trap most common +// programming errors. If an exception occurs, a minidump file will be +// generated which contains detailed information about the process and the +// exception. + +#ifndef CLIENT_MAC_HANDLER_EXCEPTION_HANDLER_H__ +#define CLIENT_MAC_HANDLER_EXCEPTION_HANDLER_H__ + +#include <mach/mach.h> +#include <TargetConditionals.h> + +#include <string> + +#include "client/mac/handler/ucontext_compat.h" +#include "common/scoped_ptr.h" + +#if !TARGET_OS_IPHONE +#include "client/mac/crash_generation/crash_generation_client.h" +#endif + +namespace google_breakpad { + +using std::string; + +struct ExceptionParameters; + +enum HandlerThreadMessage { + // Message ID telling the handler thread to write a dump. + kWriteDumpMessage = 0, + // Message ID telling the handler thread to write a dump and include + // an exception stream. + kWriteDumpWithExceptionMessage = 1, + // Message ID telling the handler thread to quit. + kShutdownMessage = 2 +}; + +class ExceptionHandler { + public: + // A callback function to run before Breakpad performs any substantial + // processing of an exception. A FilterCallback is called before writing + // a minidump. context is the parameter supplied by the user as + // callback_context when the handler was created. + // + // If a FilterCallback returns true, Breakpad will continue processing, + // attempting to write a minidump. If a FilterCallback returns false, Breakpad + // will immediately report the exception as unhandled without writing a + // minidump, allowing another handler the opportunity to handle it. + typedef bool (*FilterCallback)(void *context); + + // A callback function to run after the minidump has been written. + // |minidump_id| is a unique id for the dump, so the minidump + // file is <dump_dir>/<minidump_id>.dmp. + // |context| is the value passed into the constructor. + // |succeeded| indicates whether a minidump file was successfully written. + // Return true if the exception was fully handled and breakpad should exit. + // Return false to allow any other exception handlers to process the + // exception. + typedef bool (*MinidumpCallback)(const char *dump_dir, + const char *minidump_id, + void *context, bool succeeded); + + // A callback function which will be called directly if an exception occurs. + // This bypasses the minidump file writing and simply gives the client + // the exception information. + typedef bool (*DirectCallback)( void *context, + int exception_type, + int exception_code, + int exception_subcode, + mach_port_t thread_name); + + // Creates a new ExceptionHandler instance to handle writing minidumps. + // Minidump files will be written to dump_path, and the optional callback + // is called after writing the dump file, as described above. + // If install_handler is true, then a minidump will be written whenever + // an unhandled exception occurs. If it is false, minidumps will only + // be written when WriteMinidump is called. + // If port_name is non-NULL, attempt to perform out-of-process dump generation + // If port_name is NULL, in-process dump generation will be used. + ExceptionHandler(const string &dump_path, + FilterCallback filter, MinidumpCallback callback, + void *callback_context, bool install_handler, + const char *port_name); + + // A special constructor if we want to bypass minidump writing and + // simply get a callback with the exception information. + ExceptionHandler(DirectCallback callback, + void *callback_context, + bool install_handler); + + ~ExceptionHandler(); + + // Get and set the minidump path. + string dump_path() const { return dump_path_; } + void set_dump_path(const string &dump_path) { + dump_path_ = dump_path; + dump_path_c_ = dump_path_.c_str(); + UpdateNextID(); // Necessary to put dump_path_ in next_minidump_path_. + } + + // Writes a minidump immediately. This can be used to capture the + // execution state independently of a crash. Returns true on success. + bool WriteMinidump() { + return WriteMinidump(false); + } + + bool WriteMinidump(bool write_exception_stream); + + // Convenience form of WriteMinidump which does not require an + // ExceptionHandler instance. + static bool WriteMinidump(const string &dump_path, MinidumpCallback callback, + void *callback_context) { + return WriteMinidump(dump_path, false, callback, callback_context); + } + + static bool WriteMinidump(const string &dump_path, + bool write_exception_stream, + MinidumpCallback callback, + void *callback_context); + + // Write a minidump of child immediately. This can be used to capture + // the execution state of a child process independently of a crash. + static bool WriteMinidumpForChild(mach_port_t child, + mach_port_t child_blamed_thread, + const std::string &dump_path, + MinidumpCallback callback, + void *callback_context); + + // Returns whether out-of-process dump generation is used or not. + bool IsOutOfProcess() const { +#if TARGET_OS_IPHONE + return false; +#else + return crash_generation_client_.get() != NULL; +#endif + } + + private: + // Install the mach exception handler + bool InstallHandler(); + + // Uninstall the mach exception handler (if any) + bool UninstallHandler(bool in_exception); + + // Setup the handler thread, and if |install_handler| is true, install the + // mach exception port handler + bool Setup(bool install_handler); + + // Uninstall the mach exception handler (if any) and terminate the helper + // thread + bool Teardown(); + + // Send a mach message to the exception handler. Return true on + // success, false otherwise. + bool SendMessageToHandlerThread(HandlerThreadMessage message_id); + + // All minidump writing goes through this one routine. + // |task_context| can be NULL. If not, it will be used to retrieve the + // context of the current thread, instead of using |thread_get_state|. + bool WriteMinidumpWithException(int exception_type, + int exception_code, + int exception_subcode, + breakpad_ucontext_t *task_context, + mach_port_t thread_name, + bool exit_after_write, + bool report_current_thread); + + // When installed, this static function will be call from a newly created + // pthread with |this| as the argument + static void *WaitForMessage(void *exception_handler_class); + + // Signal handler for SIGABRT. + static void SignalHandler(int sig, siginfo_t* info, void* uc); + + // disallow copy ctor and operator= + explicit ExceptionHandler(const ExceptionHandler &); + void operator=(const ExceptionHandler &); + + // Generates a new ID and stores it in next_minidump_id_, and stores the + // path of the next minidump to be written in next_minidump_path_. + void UpdateNextID(); + + // These functions will suspend/resume all threads except for the + // reporting thread + bool SuspendThreads(); + bool ResumeThreads(); + + // The destination directory for the minidump + string dump_path_; + + // The basename of the next minidump w/o extension + string next_minidump_id_; + + // The full path to the next minidump to be written, including extension + string next_minidump_path_; + + // Pointers to the UTF-8 versions of above + const char *dump_path_c_; + const char *next_minidump_id_c_; + const char *next_minidump_path_c_; + + // The callback function and pointer to be passed back after the minidump + // has been written + FilterCallback filter_; + MinidumpCallback callback_; + void *callback_context_; + + // The callback function to be passed back when we don't want a minidump + // file to be written + DirectCallback directCallback_; + + // The thread that is created for the handler + pthread_t handler_thread_; + + // The port that is waiting on an exception message to be sent, if the + // handler is installed + mach_port_t handler_port_; + + // These variables save the previous exception handler's data so that it + // can be re-installed when this handler is uninstalled + ExceptionParameters *previous_; + + // True, if we've installed the exception handler + bool installed_exception_handler_; + + // True, if we're in the process of uninstalling the exception handler and + // the thread. + bool is_in_teardown_; + + // Save the last result of the last minidump + bool last_minidump_write_result_; + + // A mutex for use when writing out a minidump that was requested on a + // thread other than the exception handler. + pthread_mutex_t minidump_write_mutex_; + + // True, if we're using the mutext to indicate when mindump writing occurs + bool use_minidump_write_mutex_; + + // Old signal handler for SIGABRT. Used to be able to restore it when + // uninstalling. + scoped_ptr<struct sigaction> old_handler_; + +#if !TARGET_OS_IPHONE + // Client for out-of-process dump generation. + scoped_ptr<CrashGenerationClient> crash_generation_client_; +#endif +}; + +} // namespace google_breakpad + +#endif // CLIENT_MAC_HANDLER_EXCEPTION_HANDLER_H__ diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/mach_vm_compat.h b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/mach_vm_compat.h new file mode 100644 index 000000000..9e9028b92 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/mach_vm_compat.h @@ -0,0 +1,48 @@ +// 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. + +#ifndef CLIENT_MAC_GENERATOR_MACH_VM_COMPAT_H_ +#define CLIENT_MAC_GENERATOR_MACH_VM_COMPAT_H_ + +#include <TargetConditionals.h> + +// On iOS 5 and higher, mach/mach_vm.h is not supported. Use the corresponding +// vm_map functions instead. +#if TARGET_OS_IPHONE +#include <mach/vm_map.h> +#define mach_vm_address_t vm_address_t +#define mach_vm_deallocate vm_deallocate +#define mach_vm_read vm_read +#define mach_vm_region_recurse vm_region_recurse_64 +#define mach_vm_size_t vm_size_t +#else +#include <mach/mach_vm.h> +#endif // TARGET_OS_IPHONE + +#endif // CLIENT_MAC_GENERATOR_MACH_VM_COMPAT_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/minidump_generator.cc b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/minidump_generator.cc new file mode 100644 index 000000000..48cd2e99b --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/minidump_generator.cc @@ -0,0 +1,1604 @@ +// Copyright (c) 2006, 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 <algorithm> +#include <cstdio> + +#include <mach/host_info.h> +#include <mach/machine.h> +#include <mach/vm_statistics.h> +#include <mach-o/dyld.h> +#include <mach-o/loader.h> +#include <sys/sysctl.h> +#include <sys/resource.h> + +#include <CoreFoundation/CoreFoundation.h> + +#include "client/mac/handler/minidump_generator.h" + +#if defined(HAS_ARM_SUPPORT) || defined(HAS_ARM64_SUPPORT) +#include <mach/arm/thread_status.h> +#endif +#ifdef HAS_PPC_SUPPORT +#include <mach/ppc/thread_status.h> +#endif +#ifdef HAS_X86_SUPPORT +#include <mach/i386/thread_status.h> +#endif + +#include "client/minidump_file_writer-inl.h" +#include "common/mac/file_id.h" +#include "common/mac/macho_id.h" +#include "common/mac/string_utilities.h" + +using MacStringUtils::ConvertToString; +using MacStringUtils::IntegerValueAtIndex; + +namespace google_breakpad { + +#if defined(__LP64__) && __LP64__ +#define LC_SEGMENT_ARCH LC_SEGMENT_64 +#else +#define LC_SEGMENT_ARCH LC_SEGMENT +#endif + +// constructor when generating from within the crashed process +MinidumpGenerator::MinidumpGenerator() + : writer_(), + exception_type_(0), + exception_code_(0), + exception_subcode_(0), + exception_thread_(0), + crashing_task_(mach_task_self()), + handler_thread_(mach_thread_self()), + cpu_type_(DynamicImages::GetNativeCPUType()), + task_context_(NULL), + dynamic_images_(NULL), + memory_blocks_(&allocator_) { + GatherSystemInformation(); +} + +// constructor when generating from a different process than the +// crashed process +MinidumpGenerator::MinidumpGenerator(mach_port_t crashing_task, + mach_port_t handler_thread) + : writer_(), + exception_type_(0), + exception_code_(0), + exception_subcode_(0), + exception_thread_(0), + crashing_task_(crashing_task), + handler_thread_(handler_thread), + cpu_type_(DynamicImages::GetNativeCPUType()), + task_context_(NULL), + dynamic_images_(NULL), + memory_blocks_(&allocator_) { + if (crashing_task != mach_task_self()) { + dynamic_images_ = new DynamicImages(crashing_task_); + cpu_type_ = dynamic_images_->GetCPUType(); + } else { + dynamic_images_ = NULL; + cpu_type_ = DynamicImages::GetNativeCPUType(); + } + + GatherSystemInformation(); +} + +MinidumpGenerator::~MinidumpGenerator() { + delete dynamic_images_; +} + +char MinidumpGenerator::build_string_[16]; +int MinidumpGenerator::os_major_version_ = 0; +int MinidumpGenerator::os_minor_version_ = 0; +int MinidumpGenerator::os_build_number_ = 0; + +// static +void MinidumpGenerator::GatherSystemInformation() { + // If this is non-zero, then we've already gathered the information + if (os_major_version_) + return; + + // This code extracts the version and build information from the OS + CFStringRef vers_path = + CFSTR("/System/Library/CoreServices/SystemVersion.plist"); + CFURLRef sys_vers = + CFURLCreateWithFileSystemPath(NULL, + vers_path, + kCFURLPOSIXPathStyle, + false); + CFReadStreamRef read_stream = CFReadStreamCreateWithFile(NULL, sys_vers); + CFRelease(sys_vers); + if (!read_stream) { + return; + } + if (!CFReadStreamOpen(read_stream)) { + CFRelease(read_stream); + return; + } + CFMutableDataRef data = NULL; + while (true) { + // Actual data file tests: Mac at 480 bytes and iOS at 413 bytes. + const CFIndex kMaxBufferLength = 1024; + UInt8 data_bytes[kMaxBufferLength]; + CFIndex num_bytes_read = + CFReadStreamRead(read_stream, data_bytes, kMaxBufferLength); + if (num_bytes_read < 0) { + if (data) { + CFRelease(data); + data = NULL; + } + break; + } else if (num_bytes_read == 0) { + break; + } else if (!data) { + data = CFDataCreateMutable(NULL, 0); + } + CFDataAppendBytes(data, data_bytes, num_bytes_read); + } + CFReadStreamClose(read_stream); + CFRelease(read_stream); + if (!data) { + return; + } + CFDictionaryRef list = + static_cast<CFDictionaryRef>(CFPropertyListCreateWithData( + NULL, data, kCFPropertyListImmutable, NULL, NULL)); + CFRelease(data); + if (!list) { + return; + } + CFStringRef build_version = static_cast<CFStringRef> + (CFDictionaryGetValue(list, CFSTR("ProductBuildVersion"))); + CFStringRef product_version = static_cast<CFStringRef> + (CFDictionaryGetValue(list, CFSTR("ProductVersion"))); + string build_str = ConvertToString(build_version); + string product_str = ConvertToString(product_version); + + CFRelease(list); + + strlcpy(build_string_, build_str.c_str(), sizeof(build_string_)); + + // Parse the string that looks like "10.4.8" + os_major_version_ = IntegerValueAtIndex(product_str, 0); + os_minor_version_ = IntegerValueAtIndex(product_str, 1); + os_build_number_ = IntegerValueAtIndex(product_str, 2); +} + +void MinidumpGenerator::SetTaskContext(breakpad_ucontext_t *task_context) { + task_context_ = task_context; +} + +string MinidumpGenerator::UniqueNameInDirectory(const string &dir, + string *unique_name) { + CFUUIDRef uuid = CFUUIDCreate(NULL); + CFStringRef uuid_cfstr = CFUUIDCreateString(NULL, uuid); + CFRelease(uuid); + string file_name(ConvertToString(uuid_cfstr)); + CFRelease(uuid_cfstr); + string path(dir); + + // Ensure that the directory (if non-empty) has a trailing slash so that + // we can append the file name and have a valid pathname. + if (!dir.empty()) { + if (dir.at(dir.size() - 1) != '/') + path.append(1, '/'); + } + + path.append(file_name); + path.append(".dmp"); + + if (unique_name) + *unique_name = file_name; + + return path; +} + +bool MinidumpGenerator::Write(const char *path) { + WriteStreamFN writers[] = { + &MinidumpGenerator::WriteThreadListStream, + &MinidumpGenerator::WriteMemoryListStream, + &MinidumpGenerator::WriteSystemInfoStream, + &MinidumpGenerator::WriteModuleListStream, + &MinidumpGenerator::WriteMiscInfoStream, + &MinidumpGenerator::WriteBreakpadInfoStream, + // Exception stream needs to be the last entry in this array as it may + // be omitted in the case where the minidump is written without an + // exception. + &MinidumpGenerator::WriteExceptionStream, + }; + bool result = false; + + // If opening was successful, create the header, directory, and call each + // writer. The destructor for the TypedMDRVAs will cause the data to be + // flushed. The destructor for the MinidumpFileWriter will close the file. + if (writer_.Open(path)) { + TypedMDRVA<MDRawHeader> header(&writer_); + TypedMDRVA<MDRawDirectory> dir(&writer_); + + if (!header.Allocate()) + return false; + + int writer_count = static_cast<int>(sizeof(writers) / sizeof(writers[0])); + + // If we don't have exception information, don't write out the + // exception stream + if (!exception_thread_ && !exception_type_) + --writer_count; + + // Add space for all writers + if (!dir.AllocateArray(writer_count)) + return false; + + MDRawHeader *header_ptr = header.get(); + header_ptr->signature = MD_HEADER_SIGNATURE; + header_ptr->version = MD_HEADER_VERSION; + time(reinterpret_cast<time_t *>(&(header_ptr->time_date_stamp))); + header_ptr->stream_count = writer_count; + header_ptr->stream_directory_rva = dir.position(); + + MDRawDirectory local_dir; + result = true; + for (int i = 0; (result) && (i < writer_count); ++i) { + result = (this->*writers[i])(&local_dir); + + if (result) + dir.CopyIndex(i, &local_dir); + } + } + return result; +} + +size_t MinidumpGenerator::CalculateStackSize(mach_vm_address_t start_addr) { + mach_vm_address_t stack_region_base = start_addr; + mach_vm_size_t stack_region_size; + natural_t nesting_level = 0; + vm_region_submap_info_64 submap_info; + mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT_64; + + vm_region_recurse_info_t region_info; + region_info = reinterpret_cast<vm_region_recurse_info_t>(&submap_info); + + if (start_addr == 0) { + return 0; + } + + kern_return_t result = + mach_vm_region_recurse(crashing_task_, &stack_region_base, + &stack_region_size, &nesting_level, + region_info, &info_count); + + if (result != KERN_SUCCESS || start_addr < stack_region_base) { + // Failure or stack corruption, since mach_vm_region had to go + // higher in the process address space to find a valid region. + return 0; + } + + unsigned int tag = submap_info.user_tag; + + // If the user tag is VM_MEMORY_STACK, look for more readable regions with + // the same tag placed immediately above the computed stack region. Under + // some circumstances, the stack for thread 0 winds up broken up into + // multiple distinct abutting regions. This can happen for several reasons, + // including user code that calls setrlimit(RLIMIT_STACK, ...) or changes + // the access on stack pages by calling mprotect. + if (tag == VM_MEMORY_STACK) { + while (true) { + mach_vm_address_t next_region_base = stack_region_base + + stack_region_size; + mach_vm_address_t proposed_next_region_base = next_region_base; + mach_vm_size_t next_region_size; + nesting_level = 0; + info_count = VM_REGION_SUBMAP_INFO_COUNT_64; + result = mach_vm_region_recurse(crashing_task_, &next_region_base, + &next_region_size, &nesting_level, + region_info, &info_count); + if (result != KERN_SUCCESS || + next_region_base != proposed_next_region_base || + submap_info.user_tag != tag || + (submap_info.protection & VM_PROT_READ) == 0) { + break; + } + + stack_region_size += next_region_size; + } + } + + return stack_region_base + stack_region_size - start_addr; +} + +bool MinidumpGenerator::WriteStackFromStartAddress( + mach_vm_address_t start_addr, + MDMemoryDescriptor *stack_location) { + UntypedMDRVA memory(&writer_); + + bool result = false; + size_t size = CalculateStackSize(start_addr); + + if (size == 0) { + // In some situations the stack address for the thread can come back 0. + // In these cases we skip over the threads in question and stuff the + // stack with a clearly borked value. + start_addr = 0xDEADBEEF; + size = 16; + if (!memory.Allocate(size)) + return false; + + unsigned long long dummy_stack[2]; // Fill dummy stack with 16 bytes of + // junk. + dummy_stack[0] = 0xDEADBEEF; + dummy_stack[1] = 0xDEADBEEF; + + result = memory.Copy(dummy_stack, size); + } else { + + if (!memory.Allocate(size)) + return false; + + if (dynamic_images_) { + vector<uint8_t> stack_memory; + if (ReadTaskMemory(crashing_task_, + start_addr, + size, + stack_memory) != KERN_SUCCESS) { + return false; + } + + result = memory.Copy(&stack_memory[0], size); + } else { + result = memory.Copy(reinterpret_cast<const void *>(start_addr), size); + } + } + + stack_location->start_of_memory_range = start_addr; + stack_location->memory = memory.location(); + + return result; +} + +bool MinidumpGenerator::WriteStack(breakpad_thread_state_data_t state, + MDMemoryDescriptor *stack_location) { + switch (cpu_type_) { +#ifdef HAS_ARM_SUPPORT + case CPU_TYPE_ARM: + return WriteStackARM(state, stack_location); +#endif +#ifdef HAS_ARM64_SUPPORT + case CPU_TYPE_ARM64: + return WriteStackARM64(state, stack_location); +#endif +#ifdef HAS_PPC_SUPPORT + case CPU_TYPE_POWERPC: + return WriteStackPPC(state, stack_location); + case CPU_TYPE_POWERPC64: + return WriteStackPPC64(state, stack_location); +#endif +#ifdef HAS_X86_SUPPORT + case CPU_TYPE_I386: + return WriteStackX86(state, stack_location); + case CPU_TYPE_X86_64: + return WriteStackX86_64(state, stack_location); +#endif + default: + return false; + } +} + +bool MinidumpGenerator::WriteContext(breakpad_thread_state_data_t state, + MDLocationDescriptor *register_location) { + switch (cpu_type_) { +#ifdef HAS_ARM_SUPPORT + case CPU_TYPE_ARM: + return WriteContextARM(state, register_location); +#endif +#ifdef HAS_ARM64_SUPPORT + case CPU_TYPE_ARM64: + return WriteContextARM64(state, register_location); +#endif +#ifdef HAS_PPC_SUPPORT + case CPU_TYPE_POWERPC: + return WriteContextPPC(state, register_location); + case CPU_TYPE_POWERPC64: + return WriteContextPPC64(state, register_location); +#endif +#ifdef HAS_X86_SUPPORT + case CPU_TYPE_I386: + return WriteContextX86(state, register_location); + case CPU_TYPE_X86_64: + return WriteContextX86_64(state, register_location); +#endif + default: + return false; + } +} + +uint64_t MinidumpGenerator::CurrentPCForStack( + breakpad_thread_state_data_t state) { + switch (cpu_type_) { +#ifdef HAS_ARM_SUPPORT + case CPU_TYPE_ARM: + return CurrentPCForStackARM(state); +#endif +#ifdef HAS_ARM64_SUPPORT + case CPU_TYPE_ARM64: + return CurrentPCForStackARM64(state); +#endif +#ifdef HAS_PPC_SUPPORT + case CPU_TYPE_POWERPC: + return CurrentPCForStackPPC(state); + case CPU_TYPE_POWERPC64: + return CurrentPCForStackPPC64(state); +#endif +#ifdef HAS_X86_SUPPORT + case CPU_TYPE_I386: + return CurrentPCForStackX86(state); + case CPU_TYPE_X86_64: + return CurrentPCForStackX86_64(state); +#endif + default: + assert(0 && "Unknown CPU type!"); + return 0; + } +} + +#ifdef HAS_ARM_SUPPORT +bool MinidumpGenerator::WriteStackARM(breakpad_thread_state_data_t state, + MDMemoryDescriptor *stack_location) { + arm_thread_state_t *machine_state = + reinterpret_cast<arm_thread_state_t *>(state); + mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, sp); + return WriteStackFromStartAddress(start_addr, stack_location); +} + +uint64_t +MinidumpGenerator::CurrentPCForStackARM(breakpad_thread_state_data_t state) { + arm_thread_state_t *machine_state = + reinterpret_cast<arm_thread_state_t *>(state); + + return REGISTER_FROM_THREADSTATE(machine_state, pc); +} + +bool MinidumpGenerator::WriteContextARM(breakpad_thread_state_data_t state, + MDLocationDescriptor *register_location) +{ + TypedMDRVA<MDRawContextARM> context(&writer_); + arm_thread_state_t *machine_state = + reinterpret_cast<arm_thread_state_t *>(state); + + if (!context.Allocate()) + return false; + + *register_location = context.location(); + MDRawContextARM *context_ptr = context.get(); + context_ptr->context_flags = MD_CONTEXT_ARM_FULL; + +#define AddGPR(a) context_ptr->iregs[a] = REGISTER_FROM_THREADSTATE(machine_state, r[a]) + + context_ptr->iregs[13] = REGISTER_FROM_THREADSTATE(machine_state, sp); + context_ptr->iregs[14] = REGISTER_FROM_THREADSTATE(machine_state, lr); + context_ptr->iregs[15] = REGISTER_FROM_THREADSTATE(machine_state, pc); + context_ptr->cpsr = REGISTER_FROM_THREADSTATE(machine_state, cpsr); + + AddGPR(0); + AddGPR(1); + AddGPR(2); + AddGPR(3); + AddGPR(4); + AddGPR(5); + AddGPR(6); + AddGPR(7); + AddGPR(8); + AddGPR(9); + AddGPR(10); + AddGPR(11); + AddGPR(12); +#undef AddGPR + + return true; +} +#endif + +#ifdef HAS_ARM64_SUPPORT +bool MinidumpGenerator::WriteStackARM64(breakpad_thread_state_data_t state, + MDMemoryDescriptor *stack_location) { + arm_thread_state64_t *machine_state = + reinterpret_cast<arm_thread_state64_t *>(state); + mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, sp); + return WriteStackFromStartAddress(start_addr, stack_location); +} + +uint64_t +MinidumpGenerator::CurrentPCForStackARM64(breakpad_thread_state_data_t state) { + arm_thread_state64_t *machine_state = + reinterpret_cast<arm_thread_state64_t *>(state); + + return REGISTER_FROM_THREADSTATE(machine_state, pc); +} + +bool +MinidumpGenerator::WriteContextARM64(breakpad_thread_state_data_t state, + MDLocationDescriptor *register_location) +{ + TypedMDRVA<MDRawContextARM64> context(&writer_); + arm_thread_state64_t *machine_state = + reinterpret_cast<arm_thread_state64_t *>(state); + + if (!context.Allocate()) + return false; + + *register_location = context.location(); + MDRawContextARM64 *context_ptr = context.get(); + context_ptr->context_flags = MD_CONTEXT_ARM64_FULL; + +#define AddGPR(a) context_ptr->iregs[a] = \ + REGISTER_FROM_THREADSTATE(machine_state, x[a]) + + context_ptr->iregs[29] = REGISTER_FROM_THREADSTATE(machine_state, fp); + context_ptr->iregs[30] = REGISTER_FROM_THREADSTATE(machine_state, lr); + context_ptr->iregs[31] = REGISTER_FROM_THREADSTATE(machine_state, sp); + context_ptr->iregs[32] = REGISTER_FROM_THREADSTATE(machine_state, pc); + context_ptr->cpsr = REGISTER_FROM_THREADSTATE(machine_state, cpsr); + + AddGPR(0); + AddGPR(1); + AddGPR(2); + AddGPR(3); + AddGPR(4); + AddGPR(5); + AddGPR(6); + AddGPR(7); + AddGPR(8); + AddGPR(9); + AddGPR(10); + AddGPR(11); + AddGPR(12); + AddGPR(13); + AddGPR(14); + AddGPR(15); + AddGPR(16); + AddGPR(17); + AddGPR(18); + AddGPR(19); + AddGPR(20); + AddGPR(21); + AddGPR(22); + AddGPR(23); + AddGPR(24); + AddGPR(25); + AddGPR(26); + AddGPR(27); + AddGPR(28); +#undef AddGPR + + return true; +} +#endif + +#ifdef HAS_PCC_SUPPORT +bool MinidumpGenerator::WriteStackPPC(breakpad_thread_state_data_t state, + MDMemoryDescriptor *stack_location) { + ppc_thread_state_t *machine_state = + reinterpret_cast<ppc_thread_state_t *>(state); + mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, r1); + return WriteStackFromStartAddress(start_addr, stack_location); +} + +bool MinidumpGenerator::WriteStackPPC64(breakpad_thread_state_data_t state, + MDMemoryDescriptor *stack_location) { + ppc_thread_state64_t *machine_state = + reinterpret_cast<ppc_thread_state64_t *>(state); + mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, r1); + return WriteStackFromStartAddress(start_addr, stack_location); +} + +uint64_t +MinidumpGenerator::CurrentPCForStackPPC(breakpad_thread_state_data_t state) { + ppc_thread_state_t *machine_state = + reinterpret_cast<ppc_thread_state_t *>(state); + + return REGISTER_FROM_THREADSTATE(machine_state, srr0); +} + +uint64_t +MinidumpGenerator::CurrentPCForStackPPC64(breakpad_thread_state_data_t state) { + ppc_thread_state64_t *machine_state = + reinterpret_cast<ppc_thread_state64_t *>(state); + + return REGISTER_FROM_THREADSTATE(machine_state, srr0); +} + +bool MinidumpGenerator::WriteContextPPC(breakpad_thread_state_data_t state, + MDLocationDescriptor *register_location) +{ + TypedMDRVA<MDRawContextPPC> context(&writer_); + ppc_thread_state_t *machine_state = + reinterpret_cast<ppc_thread_state_t *>(state); + + if (!context.Allocate()) + return false; + + *register_location = context.location(); + MDRawContextPPC *context_ptr = context.get(); + context_ptr->context_flags = MD_CONTEXT_PPC_BASE; + +#define AddReg(a) context_ptr->a = static_cast<__typeof__(context_ptr->a)>( \ + REGISTER_FROM_THREADSTATE(machine_state, a)) +#define AddGPR(a) context_ptr->gpr[a] = \ + static_cast<__typeof__(context_ptr->a)>( \ + REGISTER_FROM_THREADSTATE(machine_state, r ## a) + + AddReg(srr0); + AddReg(cr); + AddReg(xer); + AddReg(ctr); + AddReg(lr); + AddReg(vrsave); + + AddGPR(0); + AddGPR(1); + AddGPR(2); + AddGPR(3); + AddGPR(4); + AddGPR(5); + AddGPR(6); + AddGPR(7); + AddGPR(8); + AddGPR(9); + AddGPR(10); + AddGPR(11); + AddGPR(12); + AddGPR(13); + AddGPR(14); + AddGPR(15); + AddGPR(16); + AddGPR(17); + AddGPR(18); + AddGPR(19); + AddGPR(20); + AddGPR(21); + AddGPR(22); + AddGPR(23); + AddGPR(24); + AddGPR(25); + AddGPR(26); + AddGPR(27); + AddGPR(28); + AddGPR(29); + AddGPR(30); + AddGPR(31); + AddReg(mq); +#undef AddReg +#undef AddGPR + + return true; +} + +bool MinidumpGenerator::WriteContextPPC64( + breakpad_thread_state_data_t state, + MDLocationDescriptor *register_location) { + TypedMDRVA<MDRawContextPPC64> context(&writer_); + ppc_thread_state64_t *machine_state = + reinterpret_cast<ppc_thread_state64_t *>(state); + + if (!context.Allocate()) + return false; + + *register_location = context.location(); + MDRawContextPPC64 *context_ptr = context.get(); + context_ptr->context_flags = MD_CONTEXT_PPC_BASE; + +#define AddReg(a) context_ptr->a = static_cast<__typeof__(context_ptr->a)>( \ + REGISTER_FROM_THREADSTATE(machine_state, a)) +#define AddGPR(a) context_ptr->gpr[a] = \ + static_cast<__typeof__(context_ptr->a)>( \ + REGISTER_FROM_THREADSTATE(machine_state, r ## a) + + AddReg(srr0); + AddReg(cr); + AddReg(xer); + AddReg(ctr); + AddReg(lr); + AddReg(vrsave); + + AddGPR(0); + AddGPR(1); + AddGPR(2); + AddGPR(3); + AddGPR(4); + AddGPR(5); + AddGPR(6); + AddGPR(7); + AddGPR(8); + AddGPR(9); + AddGPR(10); + AddGPR(11); + AddGPR(12); + AddGPR(13); + AddGPR(14); + AddGPR(15); + AddGPR(16); + AddGPR(17); + AddGPR(18); + AddGPR(19); + AddGPR(20); + AddGPR(21); + AddGPR(22); + AddGPR(23); + AddGPR(24); + AddGPR(25); + AddGPR(26); + AddGPR(27); + AddGPR(28); + AddGPR(29); + AddGPR(30); + AddGPR(31); +#undef AddReg +#undef AddGPR + + return true; +} + +#endif + +#ifdef HAS_X86_SUPPORT +bool MinidumpGenerator::WriteStackX86(breakpad_thread_state_data_t state, + MDMemoryDescriptor *stack_location) { + i386_thread_state_t *machine_state = + reinterpret_cast<i386_thread_state_t *>(state); + + mach_vm_address_t start_addr = REGISTER_FROM_THREADSTATE(machine_state, esp); + return WriteStackFromStartAddress(start_addr, stack_location); +} + +bool MinidumpGenerator::WriteStackX86_64(breakpad_thread_state_data_t state, + MDMemoryDescriptor *stack_location) { + x86_thread_state64_t *machine_state = + reinterpret_cast<x86_thread_state64_t *>(state); + + mach_vm_address_t start_addr = static_cast<mach_vm_address_t>( + REGISTER_FROM_THREADSTATE(machine_state, rsp)); + return WriteStackFromStartAddress(start_addr, stack_location); +} + +uint64_t +MinidumpGenerator::CurrentPCForStackX86(breakpad_thread_state_data_t state) { + i386_thread_state_t *machine_state = + reinterpret_cast<i386_thread_state_t *>(state); + + return REGISTER_FROM_THREADSTATE(machine_state, eip); +} + +uint64_t +MinidumpGenerator::CurrentPCForStackX86_64(breakpad_thread_state_data_t state) { + x86_thread_state64_t *machine_state = + reinterpret_cast<x86_thread_state64_t *>(state); + + return REGISTER_FROM_THREADSTATE(machine_state, rip); +} + +bool MinidumpGenerator::WriteContextX86(breakpad_thread_state_data_t state, + MDLocationDescriptor *register_location) +{ + TypedMDRVA<MDRawContextX86> context(&writer_); + i386_thread_state_t *machine_state = + reinterpret_cast<i386_thread_state_t *>(state); + + if (!context.Allocate()) + return false; + + *register_location = context.location(); + MDRawContextX86 *context_ptr = context.get(); + +#define AddReg(a) context_ptr->a = static_cast<__typeof__(context_ptr->a)>( \ + REGISTER_FROM_THREADSTATE(machine_state, a)) + + context_ptr->context_flags = MD_CONTEXT_X86; + AddReg(eax); + AddReg(ebx); + AddReg(ecx); + AddReg(edx); + AddReg(esi); + AddReg(edi); + AddReg(ebp); + AddReg(esp); + + AddReg(cs); + AddReg(ds); + AddReg(ss); + AddReg(es); + AddReg(fs); + AddReg(gs); + AddReg(eflags); + + AddReg(eip); +#undef AddReg + + return true; +} + +bool MinidumpGenerator::WriteContextX86_64( + breakpad_thread_state_data_t state, + MDLocationDescriptor *register_location) { + TypedMDRVA<MDRawContextAMD64> context(&writer_); + x86_thread_state64_t *machine_state = + reinterpret_cast<x86_thread_state64_t *>(state); + + if (!context.Allocate()) + return false; + + *register_location = context.location(); + MDRawContextAMD64 *context_ptr = context.get(); + +#define AddReg(a) context_ptr->a = static_cast<__typeof__(context_ptr->a)>( \ + REGISTER_FROM_THREADSTATE(machine_state, a)) + + context_ptr->context_flags = MD_CONTEXT_AMD64; + AddReg(rax); + AddReg(rbx); + AddReg(rcx); + AddReg(rdx); + AddReg(rdi); + AddReg(rsi); + AddReg(rbp); + AddReg(rsp); + AddReg(r8); + AddReg(r9); + AddReg(r10); + AddReg(r11); + AddReg(r12); + AddReg(r13); + AddReg(r14); + AddReg(r15); + AddReg(rip); + // according to AMD's software developer guide, bits above 18 are + // not used in the flags register. Since the minidump format + // specifies 32 bits for the flags register, we can truncate safely + // with no loss. + context_ptr->eflags = static_cast<uint32_t>(REGISTER_FROM_THREADSTATE(machine_state, rflags)); + AddReg(cs); + AddReg(fs); + AddReg(gs); +#undef AddReg + + return true; +} +#endif + +bool MinidumpGenerator::GetThreadState(thread_act_t target_thread, + thread_state_t state, + mach_msg_type_number_t *count) { + if (task_context_ && target_thread == mach_thread_self()) { + switch (cpu_type_) { +#ifdef HAS_ARM_SUPPORT + case CPU_TYPE_ARM: + size_t final_size = + std::min(static_cast<size_t>(*count), sizeof(arm_thread_state_t)); + memcpy(state, &task_context_->breakpad_uc_mcontext->__ss, final_size); + *count = static_cast<mach_msg_type_number_t>(final_size); + return true; +#endif +#ifdef HAS_ARM64_SUPPORT + case CPU_TYPE_ARM64: { + size_t final_size = + std::min(static_cast<size_t>(*count), sizeof(arm_thread_state64_t)); + memcpy(state, &task_context_->breakpad_uc_mcontext->__ss, final_size); + *count = static_cast<mach_msg_type_number_t>(final_size); + return true; + } +#endif +#ifdef HAS_X86_SUPPORT + case CPU_TYPE_I386: + case CPU_TYPE_X86_64: { + size_t state_size = cpu_type_ == CPU_TYPE_I386 ? + sizeof(i386_thread_state_t) : sizeof(x86_thread_state64_t); + size_t final_size = + std::min(static_cast<size_t>(*count), state_size); + memcpy(state, &task_context_->breakpad_uc_mcontext->__ss, final_size); + *count = static_cast<mach_msg_type_number_t>(final_size); + return true; + } +#endif + } + } + + thread_state_flavor_t flavor; + switch (cpu_type_) { +#ifdef HAS_ARM_SUPPORT + case CPU_TYPE_ARM: + flavor = ARM_THREAD_STATE; + break; +#endif +#ifdef HAS_ARM64_SUPPORT + case CPU_TYPE_ARM64: + flavor = ARM_THREAD_STATE64; + break; +#endif +#ifdef HAS_PPC_SUPPORT + case CPU_TYPE_POWERPC: + flavor = PPC_THREAD_STATE; + break; + case CPU_TYPE_POWERPC64: + flavor = PPC_THREAD_STATE64; + break; +#endif +#ifdef HAS_X86_SUPPORT + case CPU_TYPE_I386: + flavor = i386_THREAD_STATE; + break; + case CPU_TYPE_X86_64: + flavor = x86_THREAD_STATE64; + break; +#endif + default: + return false; + } + return thread_get_state(target_thread, flavor, + state, count) == KERN_SUCCESS; +} + +bool MinidumpGenerator::WriteThreadStream(mach_port_t thread_id, + MDRawThread *thread) { + breakpad_thread_state_data_t state; + mach_msg_type_number_t state_count + = static_cast<mach_msg_type_number_t>(sizeof(state)); + + if (GetThreadState(thread_id, state, &state_count)) { + if (!WriteStack(state, &thread->stack)) + return false; + + memory_blocks_.push_back(thread->stack); + + if (!WriteContext(state, &thread->thread_context)) + return false; + + thread->thread_id = thread_id; + } else { + return false; + } + + return true; +} + +bool MinidumpGenerator::WriteThreadListStream( + MDRawDirectory *thread_list_stream) { + TypedMDRVA<MDRawThreadList> list(&writer_); + thread_act_port_array_t threads_for_task; + mach_msg_type_number_t thread_count; + int non_generator_thread_count; + + if (task_threads(crashing_task_, &threads_for_task, &thread_count)) + return false; + + // Don't include the generator thread + if (handler_thread_ != MACH_PORT_NULL) + non_generator_thread_count = thread_count - 1; + else + non_generator_thread_count = thread_count; + if (!list.AllocateObjectAndArray(non_generator_thread_count, + sizeof(MDRawThread))) + return false; + + thread_list_stream->stream_type = MD_THREAD_LIST_STREAM; + thread_list_stream->location = list.location(); + + list.get()->number_of_threads = non_generator_thread_count; + + MDRawThread thread; + int thread_idx = 0; + + for (unsigned int i = 0; i < thread_count; ++i) { + memset(&thread, 0, sizeof(MDRawThread)); + + if (threads_for_task[i] != handler_thread_) { + if (!WriteThreadStream(threads_for_task[i], &thread)) + return false; + + list.CopyIndexAfterObject(thread_idx++, &thread, sizeof(MDRawThread)); + } + } + + return true; +} + +bool MinidumpGenerator::WriteMemoryListStream( + MDRawDirectory *memory_list_stream) { + TypedMDRVA<MDRawMemoryList> list(&writer_); + + // If the dump has an exception, include some memory around the + // instruction pointer. + const size_t kIPMemorySize = 256; // bytes + bool have_ip_memory = false; + MDMemoryDescriptor ip_memory_d; + if (exception_thread_ && exception_type_) { + breakpad_thread_state_data_t state; + mach_msg_type_number_t stateCount + = static_cast<mach_msg_type_number_t>(sizeof(state)); + + if (GetThreadState(exception_thread_, state, &stateCount)) { + uint64_t ip = CurrentPCForStack(state); + // Bound it to the upper and lower bounds of the region + // it's contained within. If it's not in a known memory region, + // don't bother trying to write it. + mach_vm_address_t addr = static_cast<vm_address_t>(ip); + mach_vm_size_t size; + natural_t nesting_level = 0; + vm_region_submap_info_64 info; + mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT_64; + vm_region_recurse_info_t recurse_info; + recurse_info = reinterpret_cast<vm_region_recurse_info_t>(&info); + + kern_return_t ret = + mach_vm_region_recurse(crashing_task_, + &addr, + &size, + &nesting_level, + recurse_info, + &info_count); + if (ret == KERN_SUCCESS && ip >= addr && ip < (addr + size)) { + // Try to get 128 bytes before and after the IP, but + // settle for whatever's available. + ip_memory_d.start_of_memory_range = + std::max(uintptr_t(addr), + uintptr_t(ip - (kIPMemorySize / 2))); + uintptr_t end_of_range = + std::min(uintptr_t(ip + (kIPMemorySize / 2)), + uintptr_t(addr + size)); + uintptr_t range_diff = end_of_range - + static_cast<uintptr_t>(ip_memory_d.start_of_memory_range); + ip_memory_d.memory.data_size = static_cast<uint32_t>(range_diff); + have_ip_memory = true; + // This needs to get appended to the list even though + // the memory bytes aren't filled in yet so the entire + // list can be written first. The memory bytes will get filled + // in after the memory list is written. + memory_blocks_.push_back(ip_memory_d); + } + } + } + + // Now fill in the memory list and write it. + size_t memory_count = memory_blocks_.size(); + if (!list.AllocateObjectAndArray(memory_count, + sizeof(MDMemoryDescriptor))) + return false; + + memory_list_stream->stream_type = MD_MEMORY_LIST_STREAM; + memory_list_stream->location = list.location(); + + list.get()->number_of_memory_ranges = static_cast<uint32_t>(memory_count); + + unsigned int i; + for (i = 0; i < memory_count; ++i) { + list.CopyIndexAfterObject(i, &memory_blocks_[i], + sizeof(MDMemoryDescriptor)); + } + + if (have_ip_memory) { + // Now read the memory around the instruction pointer. + UntypedMDRVA ip_memory(&writer_); + if (!ip_memory.Allocate(ip_memory_d.memory.data_size)) + return false; + + if (dynamic_images_) { + // Out-of-process. + vector<uint8_t> memory; + if (ReadTaskMemory(crashing_task_, + ip_memory_d.start_of_memory_range, + ip_memory_d.memory.data_size, + memory) != KERN_SUCCESS) { + return false; + } + + ip_memory.Copy(&memory[0], ip_memory_d.memory.data_size); + } else { + // In-process, just copy from local memory. + ip_memory.Copy( + reinterpret_cast<const void *>(ip_memory_d.start_of_memory_range), + ip_memory_d.memory.data_size); + } + + ip_memory_d.memory = ip_memory.location(); + // Write this again now that the data location is filled in. + list.CopyIndexAfterObject(i - 1, &ip_memory_d, + sizeof(MDMemoryDescriptor)); + } + + return true; +} + +bool +MinidumpGenerator::WriteExceptionStream(MDRawDirectory *exception_stream) { + TypedMDRVA<MDRawExceptionStream> 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 = exception_thread_; + + // This naming is confusing, but it is the proper translation from + // mach naming to minidump naming. + exception_ptr->exception_record.exception_code = exception_type_; + exception_ptr->exception_record.exception_flags = exception_code_; + + breakpad_thread_state_data_t state; + mach_msg_type_number_t state_count + = static_cast<mach_msg_type_number_t>(sizeof(state)); + + if (!GetThreadState(exception_thread_, state, &state_count)) + return false; + + if (!WriteContext(state, &exception_ptr->thread_context)) + return false; + + if (exception_type_ == EXC_BAD_ACCESS) + exception_ptr->exception_record.exception_address = exception_subcode_; + else + exception_ptr->exception_record.exception_address = CurrentPCForStack(state); + + return true; +} + +bool MinidumpGenerator::WriteSystemInfoStream( + MDRawDirectory *system_info_stream) { + TypedMDRVA<MDRawSystemInfo> info(&writer_); + + if (!info.Allocate()) + return false; + + system_info_stream->stream_type = MD_SYSTEM_INFO_STREAM; + system_info_stream->location = info.location(); + + // CPU Information + uint32_t number_of_processors; + size_t len = sizeof(number_of_processors); + sysctlbyname("hw.ncpu", &number_of_processors, &len, NULL, 0); + MDRawSystemInfo *info_ptr = info.get(); + + switch (cpu_type_) { +#ifdef HAS_ARM_SUPPORT + case CPU_TYPE_ARM: + info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_ARM; + break; +#endif +#ifdef HAS_ARM64_SUPPORT + case CPU_TYPE_ARM64: + info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_ARM64; + break; +#endif +#ifdef HAS_PPC_SUPPORT + case CPU_TYPE_POWERPC: + case CPU_TYPE_POWERPC64: + info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_PPC; + break; +#endif +#ifdef HAS_X86_SUPPORT + case CPU_TYPE_I386: + case CPU_TYPE_X86_64: + if (cpu_type_ == CPU_TYPE_I386) + info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_X86; + else + info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_AMD64; +#ifdef __i386__ + // ebx is used for PIC code, so we need + // to preserve it. +#define cpuid(op,eax,ebx,ecx,edx) \ + asm ("pushl %%ebx \n\t" \ + "cpuid \n\t" \ + "movl %%ebx,%1 \n\t" \ + "popl %%ebx" \ + : "=a" (eax), \ + "=g" (ebx), \ + "=c" (ecx), \ + "=d" (edx) \ + : "0" (op)) +#elif defined(__x86_64__) + +#define cpuid(op,eax,ebx,ecx,edx) \ + asm ("cpuid \n\t" \ + : "=a" (eax), \ + "=b" (ebx), \ + "=c" (ecx), \ + "=d" (edx) \ + : "0" (op)) +#endif + +#if defined(__i386__) || defined(__x86_64__) + int unused, unused2; + // get vendor id + cpuid(0, unused, info_ptr->cpu.x86_cpu_info.vendor_id[0], + info_ptr->cpu.x86_cpu_info.vendor_id[2], + info_ptr->cpu.x86_cpu_info.vendor_id[1]); + // get version and feature info + cpuid(1, info_ptr->cpu.x86_cpu_info.version_information, unused, unused2, + info_ptr->cpu.x86_cpu_info.feature_information); + + // family + info_ptr->processor_level = + (info_ptr->cpu.x86_cpu_info.version_information & 0xF00) >> 8; + // 0xMMSS (Model, Stepping) + info_ptr->processor_revision = static_cast<uint16_t>( + (info_ptr->cpu.x86_cpu_info.version_information & 0xF) | + ((info_ptr->cpu.x86_cpu_info.version_information & 0xF0) << 4)); + + // decode extended model info + if (info_ptr->processor_level == 0xF || + info_ptr->processor_level == 0x6) { + info_ptr->processor_revision |= + ((info_ptr->cpu.x86_cpu_info.version_information & 0xF0000) >> 4); + } + + // decode extended family info + if (info_ptr->processor_level == 0xF) { + info_ptr->processor_level += + ((info_ptr->cpu.x86_cpu_info.version_information & 0xFF00000) >> 20); + } + +#endif // __i386__ || __x86_64_ + break; +#endif // HAS_X86_SUPPORT + default: + info_ptr->processor_architecture = MD_CPU_ARCHITECTURE_UNKNOWN; + break; + } + + info_ptr->number_of_processors = static_cast<uint8_t>(number_of_processors); +#if TARGET_OS_IPHONE + info_ptr->platform_id = MD_OS_IOS; +#else + info_ptr->platform_id = MD_OS_MAC_OS_X; +#endif // TARGET_OS_IPHONE + + MDLocationDescriptor build_string_loc; + + if (!writer_.WriteString(build_string_, 0, + &build_string_loc)) + return false; + + info_ptr->csd_version_rva = build_string_loc.rva; + info_ptr->major_version = os_major_version_; + info_ptr->minor_version = os_minor_version_; + info_ptr->build_number = os_build_number_; + + return true; +} + +bool MinidumpGenerator::WriteModuleStream(unsigned int index, + MDRawModule *module) { + if (dynamic_images_) { + // we're in a different process than the crashed process + DynamicImage *image = dynamic_images_->GetImage(index); + + if (!image) + return false; + + memset(module, 0, sizeof(MDRawModule)); + + MDLocationDescriptor string_location; + + string name = image->GetFilePath(); + if (!writer_.WriteString(name.c_str(), 0, &string_location)) + return false; + + module->base_of_image = image->GetVMAddr() + image->GetVMAddrSlide(); + module->size_of_image = static_cast<uint32_t>(image->GetVMSize()); + module->module_name_rva = string_location.rva; + + // We'll skip the executable module, because they don't have + // LC_ID_DYLIB load commands, and the crash processing server gets + // version information from the Plist file, anyway. + if (index != static_cast<uint32_t>(FindExecutableModule())) { + module->version_info.signature = MD_VSFIXEDFILEINFO_SIGNATURE; + module->version_info.struct_version |= MD_VSFIXEDFILEINFO_VERSION; + // Convert MAC dylib version format, which is a 32 bit number, to the + // format used by minidump. The mac format is <16 bits>.<8 bits>.<8 bits> + // so it fits nicely into the windows version with some massaging + // The mapping is: + // 1) upper 16 bits of MAC version go to lower 16 bits of product HI + // 2) Next most significant 8 bits go to upper 16 bits of product LO + // 3) Least significant 8 bits go to lower 16 bits of product LO + uint32_t modVersion = image->GetVersion(); + module->version_info.file_version_hi = 0; + module->version_info.file_version_hi = modVersion >> 16; + module->version_info.file_version_lo |= (modVersion & 0xff00) << 8; + module->version_info.file_version_lo |= (modVersion & 0xff); + } + + if (!WriteCVRecord(module, image->GetCPUType(), name.c_str(), false)) { + return false; + } + } else { + // Getting module info in the crashed process + const breakpad_mach_header *header; + header = (breakpad_mach_header*)_dyld_get_image_header(index); + if (!header) + return false; + +#ifdef __LP64__ + assert(header->magic == MH_MAGIC_64); + + if(header->magic != MH_MAGIC_64) + return false; +#else + assert(header->magic == MH_MAGIC); + + if(header->magic != MH_MAGIC) + return false; +#endif + + int cpu_type = header->cputype; + unsigned long slide = _dyld_get_image_vmaddr_slide(index); + const char* name = _dyld_get_image_name(index); + const struct load_command *cmd = + reinterpret_cast<const struct load_command *>(header + 1); + + memset(module, 0, sizeof(MDRawModule)); + + for (unsigned int i = 0; cmd && (i < header->ncmds); i++) { + if (cmd->cmd == LC_SEGMENT_ARCH) { + + const breakpad_mach_segment_command *seg = + reinterpret_cast<const breakpad_mach_segment_command *>(cmd); + + if (!strcmp(seg->segname, "__TEXT")) { + MDLocationDescriptor string_location; + + if (!writer_.WriteString(name, 0, &string_location)) + return false; + + module->base_of_image = seg->vmaddr + slide; + module->size_of_image = static_cast<uint32_t>(seg->vmsize); + module->module_name_rva = string_location.rva; + + bool in_memory = false; +#if TARGET_OS_IPHONE + in_memory = true; +#endif + if (!WriteCVRecord(module, cpu_type, name, in_memory)) + return false; + + return true; + } + } + + cmd = reinterpret_cast<struct load_command*>((char *)cmd + cmd->cmdsize); + } + } + + return true; +} + +int MinidumpGenerator::FindExecutableModule() { + if (dynamic_images_) { + int index = dynamic_images_->GetExecutableImageIndex(); + + if (index >= 0) { + return index; + } + } else { + int image_count = _dyld_image_count(); + const struct mach_header *header; + + for (int index = 0; index < image_count; ++index) { + header = _dyld_get_image_header(index); + + if (header->filetype == MH_EXECUTE) + return index; + } + } + + // failed - just use the first image + return 0; +} + +bool MinidumpGenerator::WriteCVRecord(MDRawModule *module, int cpu_type, + const char *module_path, bool in_memory) { + TypedMDRVA<MDCVInfoPDB70> cv(&writer_); + + // Only return the last path component of the full module path + const char *module_name = strrchr(module_path, '/'); + + // Increment past the slash + if (module_name) + ++module_name; + else + module_name = "<Unknown>"; + + size_t module_name_length = strlen(module_name); + + if (!cv.AllocateObjectAndArray(module_name_length + 1, sizeof(uint8_t))) + return false; + + if (!cv.CopyIndexAfterObject(0, module_name, module_name_length)) + return false; + + module->cv_record = cv.location(); + MDCVInfoPDB70 *cv_ptr = cv.get(); + cv_ptr->cv_signature = MD_CVINFOPDB70_SIGNATURE; + cv_ptr->age = 0; + + // Get the module identifier + unsigned char identifier[16]; + bool result = false; + if (in_memory) { + MacFileUtilities::MachoID macho(module_path, + reinterpret_cast<void *>(module->base_of_image), + static_cast<size_t>(module->size_of_image)); + result = macho.UUIDCommand(cpu_type, CPU_SUBTYPE_MULTIPLE, identifier); + if (!result) + result = macho.MD5(cpu_type, CPU_SUBTYPE_MULTIPLE, identifier); + } + + if (!result) { + FileID file_id(module_path); + result = file_id.MachoIdentifier(cpu_type, CPU_SUBTYPE_MULTIPLE, + identifier); + } + + if (result) { + cv_ptr->signature.data1 = + static_cast<uint32_t>(identifier[0]) << 24 | + static_cast<uint32_t>(identifier[1]) << 16 | + static_cast<uint32_t>(identifier[2]) << 8 | + static_cast<uint32_t>(identifier[3]); + cv_ptr->signature.data2 = + static_cast<uint16_t>(identifier[4] << 8) | identifier[5]; + cv_ptr->signature.data3 = + static_cast<uint16_t>(identifier[6] << 8) | identifier[7]; + cv_ptr->signature.data4[0] = identifier[8]; + cv_ptr->signature.data4[1] = identifier[9]; + cv_ptr->signature.data4[2] = identifier[10]; + cv_ptr->signature.data4[3] = identifier[11]; + cv_ptr->signature.data4[4] = identifier[12]; + cv_ptr->signature.data4[5] = identifier[13]; + cv_ptr->signature.data4[6] = identifier[14]; + cv_ptr->signature.data4[7] = identifier[15]; + } + + return true; +} + +bool MinidumpGenerator::WriteModuleListStream( + MDRawDirectory *module_list_stream) { + TypedMDRVA<MDRawModuleList> list(&writer_); + + uint32_t image_count = dynamic_images_ ? + dynamic_images_->GetImageCount() : + _dyld_image_count(); + + if (!list.AllocateObjectAndArray(image_count, MD_MODULE_SIZE)) + return false; + + module_list_stream->stream_type = MD_MODULE_LIST_STREAM; + module_list_stream->location = list.location(); + list.get()->number_of_modules = static_cast<uint32_t>(image_count); + + // Write out the executable module as the first one + MDRawModule module; + uint32_t executableIndex = FindExecutableModule(); + + if (!WriteModuleStream(static_cast<unsigned>(executableIndex), &module)) { + return false; + } + + list.CopyIndexAfterObject(0, &module, MD_MODULE_SIZE); + int destinationIndex = 1; // Write all other modules after this one + + for (uint32_t i = 0; i < image_count; ++i) { + if (i != executableIndex) { + if (!WriteModuleStream(static_cast<unsigned>(i), &module)) { + return false; + } + + list.CopyIndexAfterObject(destinationIndex++, &module, MD_MODULE_SIZE); + } + } + + return true; +} + +bool MinidumpGenerator::WriteMiscInfoStream(MDRawDirectory *misc_info_stream) { + TypedMDRVA<MDRawMiscInfo> info(&writer_); + + if (!info.Allocate()) + return false; + + misc_info_stream->stream_type = MD_MISC_INFO_STREAM; + misc_info_stream->location = info.location(); + + MDRawMiscInfo *info_ptr = info.get(); + info_ptr->size_of_info = static_cast<uint32_t>(sizeof(MDRawMiscInfo)); + info_ptr->flags1 = MD_MISCINFO_FLAGS1_PROCESS_ID | + MD_MISCINFO_FLAGS1_PROCESS_TIMES | + MD_MISCINFO_FLAGS1_PROCESSOR_POWER_INFO; + + // Process ID + info_ptr->process_id = getpid(); + + // Times + struct rusage usage; + if (getrusage(RUSAGE_SELF, &usage) != -1) { + // Omit the fractional time since the MDRawMiscInfo only wants seconds + info_ptr->process_user_time = + static_cast<uint32_t>(usage.ru_utime.tv_sec); + info_ptr->process_kernel_time = + static_cast<uint32_t>(usage.ru_stime.tv_sec); + } + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, + static_cast<int>(info_ptr->process_id) }; + uint mibsize = static_cast<uint>(sizeof(mib) / sizeof(mib[0])); + struct kinfo_proc proc; + size_t size = sizeof(proc); + if (sysctl(mib, mibsize, &proc, &size, NULL, 0) == 0) { + info_ptr->process_create_time = + static_cast<uint32_t>(proc.kp_proc.p_starttime.tv_sec); + } + + // Speed + uint64_t speed; + const uint64_t kOneMillion = 1000 * 1000; + size = sizeof(speed); + sysctlbyname("hw.cpufrequency_max", &speed, &size, NULL, 0); + info_ptr->processor_max_mhz = static_cast<uint32_t>(speed / kOneMillion); + info_ptr->processor_mhz_limit = static_cast<uint32_t>(speed / kOneMillion); + size = sizeof(speed); + sysctlbyname("hw.cpufrequency", &speed, &size, NULL, 0); + info_ptr->processor_current_mhz = static_cast<uint32_t>(speed / kOneMillion); + + return true; +} + +bool MinidumpGenerator::WriteBreakpadInfoStream( + MDRawDirectory *breakpad_info_stream) { + TypedMDRVA<MDRawBreakpadInfo> info(&writer_); + + if (!info.Allocate()) + return false; + + breakpad_info_stream->stream_type = MD_BREAKPAD_INFO_STREAM; + breakpad_info_stream->location = info.location(); + MDRawBreakpadInfo *info_ptr = info.get(); + + if (exception_thread_ && exception_type_) { + info_ptr->validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID | + MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID; + info_ptr->dump_thread_id = handler_thread_; + info_ptr->requesting_thread_id = exception_thread_; + } else { + info_ptr->validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID; + info_ptr->dump_thread_id = handler_thread_; + info_ptr->requesting_thread_id = 0; + } + + return true; +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/minidump_generator.h b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/minidump_generator.h new file mode 100644 index 000000000..4e4b4a684 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/minidump_generator.h @@ -0,0 +1,236 @@ +// Copyright (c) 2006, 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. + +// minidump_generator.h: Create a minidump of the current MacOS process. + +#ifndef CLIENT_MAC_GENERATOR_MINIDUMP_GENERATOR_H__ +#define CLIENT_MAC_GENERATOR_MINIDUMP_GENERATOR_H__ + +#include <mach/mach.h> +#include <TargetConditionals.h> + +#include <string> + +#include "client/mac/handler/ucontext_compat.h" +#include "client/minidump_file_writer.h" +#include "common/memory.h" +#include "common/mac/macho_utilities.h" +#include "google_breakpad/common/minidump_format.h" + +#include "dynamic_images.h" +#include "mach_vm_compat.h" + +#if !TARGET_OS_IPHONE && (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7) + #define HAS_PPC_SUPPORT +#endif +#if defined(__arm__) +#define HAS_ARM_SUPPORT +#elif defined(__aarch64__) +#define HAS_ARM64_SUPPORT +#elif defined(__i386__) || defined(__x86_64__) + #define HAS_X86_SUPPORT +#endif + +namespace google_breakpad { + +using std::string; + +// Use the REGISTER_FROM_THREADSTATE to access a register name from the +// breakpad_thread_state_t structure. +#if __DARWIN_UNIX03 || TARGET_CPU_X86_64 || TARGET_CPU_PPC64 || TARGET_CPU_ARM +// In The 10.5 SDK Headers Apple prepended __ to the variable names in the +// i386_thread_state_t structure. There's no good way to tell what version of +// the SDK we're compiling against so we just toggle on the same preprocessor +// symbol Apple's headers use. +#define REGISTER_FROM_THREADSTATE(a, b) ((a)->__ ## b) +#else +#define REGISTER_FROM_THREADSTATE(a, b) (a->b) +#endif + +// Creates a minidump file of the current process. If there is exception data, +// use SetExceptionInformation() to add this to the minidump. The minidump +// file is generated by the Write() function. +// Usage: +// MinidumpGenerator minidump(); +// minidump.Write("/tmp/minidump"); +// +class MinidumpGenerator { + public: + MinidumpGenerator(); + MinidumpGenerator(mach_port_t crashing_task, mach_port_t handler_thread); + + virtual ~MinidumpGenerator(); + + // Return <dir>/<unique_name>.dmp + // Sets |unique_name| (if requested) to the unique name for the minidump + static string UniqueNameInDirectory(const string &dir, string *unique_name); + + // Write out the minidump into |path| + // All of the components of |path| must exist and be writable + // Return true if successful, false otherwise + bool Write(const char *path); + + // Specify some exception information, if applicable + void SetExceptionInformation(int type, int code, int subcode, + mach_port_t thread_name) { + exception_type_ = type; + exception_code_ = code; + exception_subcode_ = subcode; + exception_thread_ = thread_name; + } + + // Specify the task context. If |task_context| is not NULL, it will be used + // to retrieve the context of the current thread, instead of using + // |thread_get_state|. + void SetTaskContext(breakpad_ucontext_t *task_context); + + // Gather system information. This should be call at least once before using + // 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 *); + + // Stream writers + bool WriteThreadListStream(MDRawDirectory *thread_list_stream); + bool WriteMemoryListStream(MDRawDirectory *memory_list_stream); + bool WriteSystemInfoStream(MDRawDirectory *system_info_stream); + bool WriteModuleListStream(MDRawDirectory *module_list_stream); + bool WriteMiscInfoStream(MDRawDirectory *misc_info_stream); + bool WriteBreakpadInfoStream(MDRawDirectory *breakpad_info_stream); + + // Helpers + uint64_t CurrentPCForStack(breakpad_thread_state_data_t state); + bool GetThreadState(thread_act_t target_thread, thread_state_t state, + mach_msg_type_number_t *count); + bool WriteStackFromStartAddress(mach_vm_address_t start_addr, + MDMemoryDescriptor *stack_location); + bool WriteStack(breakpad_thread_state_data_t state, + MDMemoryDescriptor *stack_location); + bool WriteContext(breakpad_thread_state_data_t state, + MDLocationDescriptor *register_location); + bool WriteCVRecord(MDRawModule *module, int cpu_type, + const char *module_path, bool in_memory); + bool WriteModuleStream(unsigned int index, MDRawModule *module); + size_t CalculateStackSize(mach_vm_address_t start_addr); + int FindExecutableModule(); + + // Per-CPU implementations of these methods +#ifdef HAS_ARM_SUPPORT + bool WriteStackARM(breakpad_thread_state_data_t state, + MDMemoryDescriptor *stack_location); + bool WriteContextARM(breakpad_thread_state_data_t state, + MDLocationDescriptor *register_location); + uint64_t CurrentPCForStackARM(breakpad_thread_state_data_t state); +#endif +#ifdef HAS_ARM64_SUPPORT + bool WriteStackARM64(breakpad_thread_state_data_t state, + MDMemoryDescriptor *stack_location); + bool WriteContextARM64(breakpad_thread_state_data_t state, + MDLocationDescriptor *register_location); + uint64_t CurrentPCForStackARM64(breakpad_thread_state_data_t state); +#endif +#ifdef HAS_PPC_SUPPORT + bool WriteStackPPC(breakpad_thread_state_data_t state, + MDMemoryDescriptor *stack_location); + bool WriteContextPPC(breakpad_thread_state_data_t state, + MDLocationDescriptor *register_location); + uint64_t CurrentPCForStackPPC(breakpad_thread_state_data_t state); + bool WriteStackPPC64(breakpad_thread_state_data_t state, + MDMemoryDescriptor *stack_location); + bool WriteContextPPC64(breakpad_thread_state_data_t state, + MDLocationDescriptor *register_location); + uint64_t CurrentPCForStackPPC64(breakpad_thread_state_data_t state); +#endif +#ifdef HAS_X86_SUPPORT + bool WriteStackX86(breakpad_thread_state_data_t state, + MDMemoryDescriptor *stack_location); + bool WriteContextX86(breakpad_thread_state_data_t state, + MDLocationDescriptor *register_location); + uint64_t CurrentPCForStackX86(breakpad_thread_state_data_t state); + bool WriteStackX86_64(breakpad_thread_state_data_t state, + MDMemoryDescriptor *stack_location); + bool WriteContextX86_64(breakpad_thread_state_data_t state, + MDLocationDescriptor *register_location); + uint64_t CurrentPCForStackX86_64(breakpad_thread_state_data_t state); +#endif + + // disallow copy ctor and operator= + 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_; + int exception_subcode_; + mach_port_t exception_thread_; + mach_port_t crashing_task_; + mach_port_t handler_thread_; + + // CPU type of the task being dumped. + cpu_type_t cpu_type_; + + // System information + static char build_string_[16]; + static int os_major_version_; + static int os_minor_version_; + static int os_build_number_; + + // Context of the task to dump. + breakpad_ucontext_t *task_context_; + + // Information about dynamically loaded code + DynamicImages *dynamic_images_; + + // PageAllocator makes it possible to allocate memory + // 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. + wasteful_vector<MDMemoryDescriptor> memory_blocks_; +}; + +} // namespace google_breakpad + +#endif // CLIENT_MAC_GENERATOR_MINIDUMP_GENERATOR_H__ diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/minidump_test.xcodeproj/project.pbxproj b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/minidump_test.xcodeproj/project.pbxproj new file mode 100644 index 000000000..2a597d502 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/minidump_test.xcodeproj/project.pbxproj @@ -0,0 +1,841 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 8BFC813F11FF9A58002CB4DC /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BFC813E11FF9A58002CB4DC /* libcrypto.dylib */; }; + 8BFC814411FF9A9C002CB4DC /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BFC813E11FF9A58002CB4DC /* libcrypto.dylib */; }; + 8BFC814511FF9A9D002CB4DC /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BFC813E11FF9A58002CB4DC /* libcrypto.dylib */; }; + 8BFC814811FF9B13002CB4DC /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BFC813E11FF9A58002CB4DC /* libcrypto.dylib */; }; + 8BFC814911FF9B13002CB4DC /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BFC813E11FF9A58002CB4DC /* libcrypto.dylib */; }; + 8BFC814A11FF9B13002CB4DC /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BFC813E11FF9A58002CB4DC /* libcrypto.dylib */; }; + 8BFC814B11FF9B3F002CB4DC /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9721FA10E8B0E2300D7E813 /* SenTestingKit.framework */; }; + 8BFC814C11FF9B3F002CB4DC /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9721F6B0E8B0D7000D7E813 /* Cocoa.framework */; }; + 8BFC81A211FF9C2E002CB4DC /* CPlusTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BFC819211FF9C23002CB4DC /* CPlusTest.framework */; }; + 8BFC81A311FF9C2F002CB4DC /* CPlusTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8BFC819211FF9C23002CB4DC /* CPlusTest.framework */; }; + 8BFC81AD11FF9C8A002CB4DC /* breakpad_nlist_64.cc in Sources */ = {isa = PBXBuildFile; fileRef = F98208A10DB32CAE0017AECA /* breakpad_nlist_64.cc */; }; + 8BFC81AE11FF9C8C002CB4DC /* breakpad_nlist_64.cc in Sources */ = {isa = PBXBuildFile; fileRef = F98208A10DB32CAE0017AECA /* breakpad_nlist_64.cc */; }; + 8BFC81AF11FF9C8C002CB4DC /* breakpad_nlist_64.cc in Sources */ = {isa = PBXBuildFile; fileRef = F98208A10DB32CAE0017AECA /* breakpad_nlist_64.cc */; }; + 8BFC81B011FF9C8D002CB4DC /* breakpad_nlist_64.cc in Sources */ = {isa = PBXBuildFile; fileRef = F98208A10DB32CAE0017AECA /* breakpad_nlist_64.cc */; }; + 9B35FF5A0B267D5F008DE8C7 /* convert_UTF.c in Sources */ = {isa = PBXBuildFile; fileRef = 9B35FF560B267D5F008DE8C7 /* convert_UTF.c */; }; + 9B35FF5B0B267D5F008DE8C7 /* string_conversion.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9B35FF580B267D5F008DE8C7 /* string_conversion.cc */; }; + 9B37CEEC0AF98ECD00FA4BD4 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B37CEEB0AF98ECD00FA4BD4 /* CoreFoundation.framework */; }; + 9B7CA7700B12873A00CD3A1D /* minidump_file_writer-inl.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9BE3C01E0B0CE329009892DF /* minidump_file_writer-inl.h */; }; + 9B7CA8540B12989000CD3A1D /* minidump_file_writer_unittest.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9B7CA8530B12989000CD3A1D /* minidump_file_writer_unittest.cc */; }; + 9B7CA8550B1298A100CD3A1D /* minidump_file_writer.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9BD82C230B01344C0055103E /* minidump_file_writer.cc */; }; + 9BC1D2940B336F2300F2A2B4 /* convert_UTF.c in Sources */ = {isa = PBXBuildFile; fileRef = 9B35FF560B267D5F008DE8C7 /* convert_UTF.c */; }; + 9BC1D2950B336F2500F2A2B4 /* string_conversion.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9B35FF580B267D5F008DE8C7 /* string_conversion.cc */; }; + 9BD82AC10B0029DF0055103E /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B37CEEB0AF98ECD00FA4BD4 /* CoreFoundation.framework */; }; + 9BD82BFF0B01333D0055103E /* exception_handler_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9BD82BFD0B01333D0055103E /* exception_handler_test.cc */; }; + 9BD82C020B01333D0055103E /* minidump_generator_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9BD82BFE0B01333D0055103E /* minidump_generator_test.cc */; }; + 9BD82C0D0B0133520055103E /* exception_handler.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9BD82C090B0133520055103E /* exception_handler.cc */; }; + 9BD82C0E0B0133520055103E /* minidump_generator.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9BD82C0B0B0133520055103E /* minidump_generator.cc */; }; + 9BD82C0F0B0133520055103E /* exception_handler.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9BD82C090B0133520055103E /* exception_handler.cc */; }; + 9BD82C100B0133520055103E /* exception_handler.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9BD82C0A0B0133520055103E /* exception_handler.h */; }; + 9BD82C110B0133520055103E /* minidump_generator.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9BD82C0B0B0133520055103E /* minidump_generator.cc */; }; + 9BD82C120B0133520055103E /* minidump_generator.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9BD82C0C0B0133520055103E /* minidump_generator.h */; }; + 9BD82C250B01344C0055103E /* minidump_file_writer.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9BD82C230B01344C0055103E /* minidump_file_writer.cc */; }; + 9BD82C260B01344C0055103E /* minidump_file_writer.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9BD82C230B01344C0055103E /* minidump_file_writer.cc */; }; + 9BD82C270B01344C0055103E /* minidump_file_writer.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9BD82C240B01344C0055103E /* minidump_file_writer.h */; }; + 9BD82C2D0B01345E0055103E /* string_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9BD82C2B0B01345E0055103E /* string_utilities.cc */; }; + 9BD82C2E0B01345E0055103E /* string_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9BD82C2B0B01345E0055103E /* string_utilities.cc */; }; + 9BD82C2F0B01345E0055103E /* string_utilities.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9BD82C2C0B01345E0055103E /* string_utilities.h */; }; + D2F651000BEF947200920385 /* file_id.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F650FA0BEF947200920385 /* file_id.cc */; }; + D2F651010BEF947200920385 /* file_id.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = D2F650FB0BEF947200920385 /* file_id.h */; }; + D2F651020BEF947200920385 /* macho_id.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F650FC0BEF947200920385 /* macho_id.cc */; }; + D2F651030BEF947200920385 /* macho_id.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = D2F650FD0BEF947200920385 /* macho_id.h */; }; + D2F651040BEF947200920385 /* macho_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F650FE0BEF947200920385 /* macho_utilities.cc */; }; + D2F651050BEF947200920385 /* macho_utilities.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = D2F650FF0BEF947200920385 /* macho_utilities.h */; }; + D2F651090BEF949A00920385 /* dynamic_images.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F651070BEF949A00920385 /* dynamic_images.cc */; }; + D2F6510A0BEF949A00920385 /* dynamic_images.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = D2F651080BEF949A00920385 /* dynamic_images.h */; }; + D2F6510E0BEF94EB00920385 /* macho_walker.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F6510C0BEF94EB00920385 /* macho_walker.cc */; }; + D2F6510F0BEF94EB00920385 /* macho_walker.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = D2F6510D0BEF94EB00920385 /* macho_walker.h */; }; + D2F651110BEF951700920385 /* string_conversion.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9B35FF580B267D5F008DE8C7 /* string_conversion.cc */; }; + D2F651130BEF951C00920385 /* string_conversion.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9B35FF590B267D5F008DE8C7 /* string_conversion.h */; }; + D2F651150BEF953000920385 /* convert_UTF.c in Sources */ = {isa = PBXBuildFile; fileRef = 9B35FF560B267D5F008DE8C7 /* convert_UTF.c */; }; + D2F651160BEF953100920385 /* convert_UTF.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9B35FF570B267D5F008DE8C7 /* convert_UTF.h */; }; + D2F6511B0BEF970E00920385 /* dynamic_images.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F651070BEF949A00920385 /* dynamic_images.cc */; }; + D2F6511D0BEF973500920385 /* file_id.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F650FA0BEF947200920385 /* file_id.cc */; }; + D2F6511E0BEF973600920385 /* macho_id.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F650FC0BEF947200920385 /* macho_id.cc */; }; + D2F6511F0BEF973900920385 /* macho_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F650FE0BEF947200920385 /* macho_utilities.cc */; }; + D2F651210BEF975400920385 /* macho_walker.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F6510C0BEF94EB00920385 /* macho_walker.cc */; }; + F93A887D0E8B4C8C0026AF89 /* macho_walker.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F6510C0BEF94EB00920385 /* macho_walker.cc */; }; + F93A887E0E8B4C8C0026AF89 /* macho_id.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F650FC0BEF947200920385 /* macho_id.cc */; }; + F93A887F0E8B4C8C0026AF89 /* macho_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F650FE0BEF947200920385 /* macho_utilities.cc */; }; + F93A88800E8B4C8C0026AF89 /* file_id.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F650FA0BEF947200920385 /* file_id.cc */; }; + F93A88860E8B4C9A0026AF89 /* dwarftests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F9721F310E8B07E800D7E813 /* dwarftests.mm */; }; + F93A88870E8B4C9A0026AF89 /* dump_syms.mm in Sources */ = {isa = PBXBuildFile; fileRef = F9721F390E8B0D0D00D7E813 /* dump_syms.mm */; }; + F93A88880E8B4C9A0026AF89 /* bytereader.cc in Sources */ = {isa = PBXBuildFile; fileRef = F9721F760E8B0DC700D7E813 /* bytereader.cc */; }; + F93A88890E8B4C9A0026AF89 /* dwarf2reader.cc in Sources */ = {isa = PBXBuildFile; fileRef = F9721F770E8B0DC700D7E813 /* dwarf2reader.cc */; }; + F93A888A0E8B4C9A0026AF89 /* functioninfo.cc in Sources */ = {isa = PBXBuildFile; fileRef = F9721F780E8B0DC700D7E813 /* functioninfo.cc */; }; + F93A888B0E8B4C9A0026AF89 /* md5.cc in Sources */ = {isa = PBXBuildFile; fileRef = F9721FA80E8B0E4800D7E813 /* md5.cc */; }; + F9721F6C0E8B0D7000D7E813 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9721F6B0E8B0D7000D7E813 /* Cocoa.framework */; }; + F9721FA20E8B0E2300D7E813 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9721FA10E8B0E2300D7E813 /* SenTestingKit.framework */; }; + F982089C0DB3280D0017AECA /* breakpad_nlist_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F982089B0DB3280D0017AECA /* breakpad_nlist_test.cc */; }; + F98208A30DB32CAE0017AECA /* breakpad_nlist_64.cc in Sources */ = {isa = PBXBuildFile; fileRef = F98208A10DB32CAE0017AECA /* breakpad_nlist_64.cc */; }; + F9AE5B390DBFDBDB00505983 /* dynamic_images.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F651070BEF949A00920385 /* dynamic_images.cc */; }; + F9AE5B3A0DBFDBDB00505983 /* DynamicImagesTests.cc in Sources */ = {isa = PBXBuildFile; fileRef = F9C5A4210DB82DD800209C76 /* DynamicImagesTests.cc */; }; + F9B34E870DBC1E1600306484 /* dynamic_images.cc in Sources */ = {isa = PBXBuildFile; fileRef = D2F651070BEF949A00920385 /* dynamic_images.cc */; }; + F9C5A4220DB82DD800209C76 /* DynamicImagesTests.cc in Sources */ = {isa = PBXBuildFile; fileRef = F9C5A4210DB82DD800209C76 /* DynamicImagesTests.cc */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 8DD76F690486A84900D96B5E /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 8; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + 9BD82C100B0133520055103E /* exception_handler.h in CopyFiles */, + 9BD82C120B0133520055103E /* minidump_generator.h in CopyFiles */, + 9BD82C270B01344C0055103E /* minidump_file_writer.h in CopyFiles */, + 9BD82C2F0B01345E0055103E /* string_utilities.h in CopyFiles */, + 9B7CA7700B12873A00CD3A1D /* minidump_file_writer-inl.h in CopyFiles */, + D2F651010BEF947200920385 /* file_id.h in CopyFiles */, + D2F651030BEF947200920385 /* macho_id.h in CopyFiles */, + D2F651050BEF947200920385 /* macho_utilities.h in CopyFiles */, + D2F6510A0BEF949A00920385 /* dynamic_images.h in CopyFiles */, + D2F6510F0BEF94EB00920385 /* macho_walker.h in CopyFiles */, + D2F651130BEF951C00920385 /* string_conversion.h in CopyFiles */, + D2F651160BEF953100920385 /* convert_UTF.h in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 8BFC812011FF99D5002CB4DC /* Breakpad.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Breakpad.xcconfig; path = ../../../common/mac/Breakpad.xcconfig; sourceTree = SOURCE_ROOT; }; + 8BFC812111FF99D5002CB4DC /* BreakpadDebug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = BreakpadDebug.xcconfig; path = ../../../common/mac/BreakpadDebug.xcconfig; sourceTree = SOURCE_ROOT; }; + 8BFC812211FF99D5002CB4DC /* BreakpadRelease.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = BreakpadRelease.xcconfig; path = ../../../common/mac/BreakpadRelease.xcconfig; sourceTree = SOURCE_ROOT; }; + 8BFC813E11FF9A58002CB4DC /* libcrypto.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libcrypto.dylib; path = usr/lib/libcrypto.dylib; sourceTree = SDKROOT; }; + 8BFC815411FF9B7F002CB4DC /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; }; + 8BFC819211FF9C23002CB4DC /* CPlusTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CPlusTest.framework; path = Library/Frameworks/CPlusTest.framework; sourceTree = DEVELOPER_DIR; }; + 8DD76F6C0486A84900D96B5E /* generator_test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = generator_test; sourceTree = BUILT_PRODUCTS_DIR; }; + 9B35FF560B267D5F008DE8C7 /* convert_UTF.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = convert_UTF.c; path = ../../../common/convert_UTF.c; sourceTree = SOURCE_ROOT; }; + 9B35FF570B267D5F008DE8C7 /* convert_UTF.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = convert_UTF.h; path = ../../../common/convert_UTF.h; sourceTree = SOURCE_ROOT; }; + 9B35FF580B267D5F008DE8C7 /* string_conversion.cc */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = string_conversion.cc; path = ../../../common/string_conversion.cc; sourceTree = SOURCE_ROOT; }; + 9B35FF590B267D5F008DE8C7 /* string_conversion.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = string_conversion.h; path = ../../../common/string_conversion.h; sourceTree = SOURCE_ROOT; }; + 9B37CEEB0AF98ECD00FA4BD4 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; + 9B7CA84E0B1297F200CD3A1D /* unit_test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = unit_test; sourceTree = BUILT_PRODUCTS_DIR; }; + 9B7CA8530B12989000CD3A1D /* minidump_file_writer_unittest.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = minidump_file_writer_unittest.cc; path = ../../minidump_file_writer_unittest.cc; sourceTree = "<group>"; }; + 9BD82A9B0B00267E0055103E /* handler_test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = handler_test; sourceTree = BUILT_PRODUCTS_DIR; }; + 9BD82BFD0B01333D0055103E /* exception_handler_test.cc */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = exception_handler_test.cc; sourceTree = SOURCE_ROOT; }; + 9BD82BFE0B01333D0055103E /* minidump_generator_test.cc */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = minidump_generator_test.cc; sourceTree = SOURCE_ROOT; }; + 9BD82C090B0133520055103E /* exception_handler.cc */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = exception_handler.cc; sourceTree = SOURCE_ROOT; }; + 9BD82C0A0B0133520055103E /* exception_handler.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = exception_handler.h; sourceTree = SOURCE_ROOT; }; + 9BD82C0B0B0133520055103E /* minidump_generator.cc */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = minidump_generator.cc; sourceTree = SOURCE_ROOT; }; + 9BD82C0C0B0133520055103E /* minidump_generator.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = minidump_generator.h; sourceTree = SOURCE_ROOT; }; + 9BD82C230B01344C0055103E /* minidump_file_writer.cc */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = minidump_file_writer.cc; path = ../../minidump_file_writer.cc; sourceTree = SOURCE_ROOT; }; + 9BD82C240B01344C0055103E /* minidump_file_writer.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = minidump_file_writer.h; path = ../../minidump_file_writer.h; sourceTree = SOURCE_ROOT; }; + 9BD82C2B0B01345E0055103E /* string_utilities.cc */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = string_utilities.cc; path = ../../../common/mac/string_utilities.cc; sourceTree = SOURCE_ROOT; }; + 9BD82C2C0B01345E0055103E /* string_utilities.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = string_utilities.h; path = ../../../common/mac/string_utilities.h; sourceTree = SOURCE_ROOT; }; + 9BE3C01E0B0CE329009892DF /* minidump_file_writer-inl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "minidump_file_writer-inl.h"; path = "../../minidump_file_writer-inl.h"; sourceTree = SOURCE_ROOT; }; + D2F650FA0BEF947200920385 /* file_id.cc */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = file_id.cc; path = ../../../common/mac/file_id.cc; sourceTree = SOURCE_ROOT; }; + D2F650FB0BEF947200920385 /* file_id.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = file_id.h; path = ../../../common/mac/file_id.h; sourceTree = SOURCE_ROOT; }; + D2F650FC0BEF947200920385 /* macho_id.cc */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = macho_id.cc; path = ../../../common/mac/macho_id.cc; sourceTree = SOURCE_ROOT; }; + D2F650FD0BEF947200920385 /* macho_id.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = macho_id.h; path = ../../../common/mac/macho_id.h; sourceTree = SOURCE_ROOT; }; + D2F650FE0BEF947200920385 /* macho_utilities.cc */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = macho_utilities.cc; path = ../../../common/mac/macho_utilities.cc; sourceTree = SOURCE_ROOT; }; + D2F650FF0BEF947200920385 /* macho_utilities.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = macho_utilities.h; path = ../../../common/mac/macho_utilities.h; sourceTree = SOURCE_ROOT; }; + D2F651070BEF949A00920385 /* dynamic_images.cc */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = dynamic_images.cc; sourceTree = "<group>"; }; + D2F651080BEF949A00920385 /* dynamic_images.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = dynamic_images.h; sourceTree = "<group>"; }; + D2F6510C0BEF94EB00920385 /* macho_walker.cc */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = macho_walker.cc; path = ../../../common/mac/macho_walker.cc; sourceTree = SOURCE_ROOT; }; + D2F6510D0BEF94EB00920385 /* macho_walker.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = macho_walker.h; path = ../../../common/mac/macho_walker.h; sourceTree = SOURCE_ROOT; }; + F917C4F70E03265A00F86017 /* breakpad_exc_server.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = breakpad_exc_server.c; sourceTree = "<group>"; }; + F917C4F80E03265A00F86017 /* breakpad_exc_server.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = breakpad_exc_server.h; sourceTree = "<group>"; }; + F93A88750E8B4C700026AF89 /* octestcases.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = octestcases.octest; sourceTree = BUILT_PRODUCTS_DIR; }; + F93A88760E8B4C700026AF89 /* obj-cTestCases-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "obj-cTestCases-Info.plist"; sourceTree = "<group>"; }; + F9721F300E8B07E800D7E813 /* dwarftests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dwarftests.h; sourceTree = "<group>"; }; + F9721F310E8B07E800D7E813 /* dwarftests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = dwarftests.mm; sourceTree = "<group>"; }; + F9721F380E8B0CFC00D7E813 /* dump_syms.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dump_syms.h; path = ../../../common/mac/dump_syms.h; sourceTree = SOURCE_ROOT; }; + F9721F390E8B0D0D00D7E813 /* dump_syms.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = dump_syms.mm; path = ../../../common/mac/dump_syms.mm; sourceTree = SOURCE_ROOT; }; + F9721F6B0E8B0D7000D7E813 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; + F9721F760E8B0DC700D7E813 /* bytereader.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = bytereader.cc; path = ../../../common/dwarf/bytereader.cc; sourceTree = SOURCE_ROOT; }; + F9721F770E8B0DC700D7E813 /* dwarf2reader.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = dwarf2reader.cc; path = ../../../common/dwarf/dwarf2reader.cc; sourceTree = SOURCE_ROOT; }; + F9721F780E8B0DC700D7E813 /* functioninfo.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = functioninfo.cc; path = ../../../common/dwarf/functioninfo.cc; sourceTree = SOURCE_ROOT; }; + F9721FA10E8B0E2300D7E813 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; }; + F9721FA80E8B0E4800D7E813 /* md5.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = md5.cc; path = ../../../common/md5.cc; sourceTree = SOURCE_ROOT; }; + F982089A0DB3280D0017AECA /* breakpad_nlist_test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = breakpad_nlist_test.h; sourceTree = "<group>"; }; + F982089B0DB3280D0017AECA /* breakpad_nlist_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = breakpad_nlist_test.cc; sourceTree = "<group>"; }; + F98208A10DB32CAE0017AECA /* breakpad_nlist_64.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = breakpad_nlist_64.cc; sourceTree = "<group>"; }; + F98208A20DB32CAE0017AECA /* breakpad_nlist_64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = breakpad_nlist_64.h; sourceTree = "<group>"; }; + F9AE19B50DB040E300C98454 /* minidump_tests32-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "minidump_tests32-Info.plist"; sourceTree = "<group>"; }; + F9AE19C30DB04A9500C98454 /* minidump_tests64.cptest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = minidump_tests64.cptest; sourceTree = BUILT_PRODUCTS_DIR; }; + F9AE5B330DBFDBA300505983 /* minidump_tests32.cptest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = minidump_tests32.cptest; sourceTree = BUILT_PRODUCTS_DIR; }; + F9AE5B340DBFDBA300505983 /* minidump_tests64-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "minidump_tests64-Info.plist"; sourceTree = "<group>"; }; + F9C5A4200DB82DD800209C76 /* DynamicImagesTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DynamicImagesTests.h; sourceTree = "<group>"; }; + F9C5A4210DB82DD800209C76 /* DynamicImagesTests.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DynamicImagesTests.cc; sourceTree = "<group>"; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8DD76F660486A84900D96B5E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9B37CEEC0AF98ECD00FA4BD4 /* CoreFoundation.framework in Frameworks */, + 8BFC813F11FF9A58002CB4DC /* libcrypto.dylib in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9B7CA84C0B1297F200CD3A1D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8BFC814511FF9A9D002CB4DC /* libcrypto.dylib in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9BD82A990B00267E0055103E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9BD82AC10B0029DF0055103E /* CoreFoundation.framework in Frameworks */, + 8BFC814411FF9A9C002CB4DC /* libcrypto.dylib in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F93A88720E8B4C700026AF89 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8BFC814A11FF9B13002CB4DC /* libcrypto.dylib in Frameworks */, + 8BFC814B11FF9B3F002CB4DC /* SenTestingKit.framework in Frameworks */, + 8BFC814C11FF9B3F002CB4DC /* Cocoa.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F9AE19C00DB04A9500C98454 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8BFC814811FF9B13002CB4DC /* libcrypto.dylib in Frameworks */, + 8BFC81A211FF9C2E002CB4DC /* CPlusTest.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F9AE5B300DBFDBA300505983 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F9721F6C0E8B0D7000D7E813 /* Cocoa.framework in Frameworks */, + F9721FA20E8B0E2300D7E813 /* SenTestingKit.framework in Frameworks */, + 8BFC814911FF9B13002CB4DC /* libcrypto.dylib in Frameworks */, + 8BFC81A311FF9C2F002CB4DC /* CPlusTest.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 08FB7794FE84155DC02AAC07 /* MinidumpWriter */ = { + isa = PBXGroup; + children = ( + 8BFC812011FF99D5002CB4DC /* Breakpad.xcconfig */, + 8BFC812111FF99D5002CB4DC /* BreakpadDebug.xcconfig */, + 8BFC812211FF99D5002CB4DC /* BreakpadRelease.xcconfig */, + F9721FA80E8B0E4800D7E813 /* md5.cc */, + F9721F760E8B0DC700D7E813 /* bytereader.cc */, + F9721F770E8B0DC700D7E813 /* dwarf2reader.cc */, + F9721F780E8B0DC700D7E813 /* functioninfo.cc */, + F9721F390E8B0D0D00D7E813 /* dump_syms.mm */, + F9721F380E8B0CFC00D7E813 /* dump_syms.h */, + F917C4F70E03265A00F86017 /* breakpad_exc_server.c */, + F917C4F80E03265A00F86017 /* breakpad_exc_server.h */, + F98208A10DB32CAE0017AECA /* breakpad_nlist_64.cc */, + F98208A20DB32CAE0017AECA /* breakpad_nlist_64.h */, + D2F6510C0BEF94EB00920385 /* macho_walker.cc */, + D2F6510D0BEF94EB00920385 /* macho_walker.h */, + D2F651070BEF949A00920385 /* dynamic_images.cc */, + D2F651080BEF949A00920385 /* dynamic_images.h */, + D2F650FA0BEF947200920385 /* file_id.cc */, + D2F650FB0BEF947200920385 /* file_id.h */, + D2F650FC0BEF947200920385 /* macho_id.cc */, + D2F650FD0BEF947200920385 /* macho_id.h */, + D2F650FE0BEF947200920385 /* macho_utilities.cc */, + D2F650FF0BEF947200920385 /* macho_utilities.h */, + F9C5A41F0DB82DB000209C76 /* testcases */, + 9BD82C040B0133420055103E /* Breakpad */, + 08FB7795FE84155DC02AAC07 /* Source */, + 9B37CEEA0AF98EB600FA4BD4 /* Frameworks */, + 1AB674ADFE9D54B511CA2CBB /* Products */, + F9AE19B50DB040E300C98454 /* minidump_tests32-Info.plist */, + F9AE5B340DBFDBA300505983 /* minidump_tests64-Info.plist */, + F93A88760E8B4C700026AF89 /* obj-cTestCases-Info.plist */, + ); + name = MinidumpWriter; + sourceTree = "<group>"; + }; + 08FB7795FE84155DC02AAC07 /* Source */ = { + isa = PBXGroup; + children = ( + 9BD82BFD0B01333D0055103E /* exception_handler_test.cc */, + 9BD82BFE0B01333D0055103E /* minidump_generator_test.cc */, + 9B7CA8530B12989000CD3A1D /* minidump_file_writer_unittest.cc */, + ); + name = Source; + sourceTree = "<group>"; + }; + 1AB674ADFE9D54B511CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 8DD76F6C0486A84900D96B5E /* generator_test */, + 9BD82A9B0B00267E0055103E /* handler_test */, + 9B7CA84E0B1297F200CD3A1D /* unit_test */, + F9AE19C30DB04A9500C98454 /* minidump_tests64.cptest */, + F9AE5B330DBFDBA300505983 /* minidump_tests32.cptest */, + F93A88750E8B4C700026AF89 /* octestcases.octest */, + ); + name = Products; + sourceTree = "<group>"; + }; + 9B37CEEA0AF98EB600FA4BD4 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 8BFC813E11FF9A58002CB4DC /* libcrypto.dylib */, + 8BFC815411FF9B7F002CB4DC /* Carbon.framework */, + F9721FA10E8B0E2300D7E813 /* SenTestingKit.framework */, + F9721F6B0E8B0D7000D7E813 /* Cocoa.framework */, + 9B37CEEB0AF98ECD00FA4BD4 /* CoreFoundation.framework */, + 8BFC819211FF9C23002CB4DC /* CPlusTest.framework */, + ); + name = Frameworks; + sourceTree = "<group>"; + }; + 9BD82C040B0133420055103E /* Breakpad */ = { + isa = PBXGroup; + children = ( + 9B35FF560B267D5F008DE8C7 /* convert_UTF.c */, + 9B35FF570B267D5F008DE8C7 /* convert_UTF.h */, + 9B35FF580B267D5F008DE8C7 /* string_conversion.cc */, + 9B35FF590B267D5F008DE8C7 /* string_conversion.h */, + 9BD82C090B0133520055103E /* exception_handler.cc */, + 9BD82C0A0B0133520055103E /* exception_handler.h */, + 9BD82C0B0B0133520055103E /* minidump_generator.cc */, + 9BD82C0C0B0133520055103E /* minidump_generator.h */, + 9BD82C230B01344C0055103E /* minidump_file_writer.cc */, + 9BE3C01E0B0CE329009892DF /* minidump_file_writer-inl.h */, + 9BD82C240B01344C0055103E /* minidump_file_writer.h */, + 9BD82C2B0B01345E0055103E /* string_utilities.cc */, + 9BD82C2C0B01345E0055103E /* string_utilities.h */, + ); + name = Breakpad; + sourceTree = "<group>"; + }; + F9C5A41F0DB82DB000209C76 /* testcases */ = { + isa = PBXGroup; + children = ( + F982089A0DB3280D0017AECA /* breakpad_nlist_test.h */, + F982089B0DB3280D0017AECA /* breakpad_nlist_test.cc */, + F9C5A4200DB82DD800209C76 /* DynamicImagesTests.h */, + F9C5A4210DB82DD800209C76 /* DynamicImagesTests.cc */, + F9721F300E8B07E800D7E813 /* dwarftests.h */, + F9721F310E8B07E800D7E813 /* dwarftests.mm */, + ); + path = testcases; + sourceTree = "<group>"; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 8DD76F620486A84900D96B5E /* generator_test */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1DEB923108733DC60010E9CD /* Build configuration list for PBXNativeTarget "generator_test" */; + buildPhases = ( + 8DD76F640486A84900D96B5E /* Sources */, + 8DD76F660486A84900D96B5E /* Frameworks */, + 8DD76F690486A84900D96B5E /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = generator_test; + productInstallPath = "$(HOME)/bin"; + productName = MinidumpWriter; + productReference = 8DD76F6C0486A84900D96B5E /* generator_test */; + productType = "com.apple.product-type.tool"; + }; + 9B7CA84D0B1297F200CD3A1D /* unit_test */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9B7CA8500B12984300CD3A1D /* Build configuration list for PBXNativeTarget "unit_test" */; + buildPhases = ( + 9B7CA84B0B1297F200CD3A1D /* Sources */, + 9B7CA84C0B1297F200CD3A1D /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = unit_test; + productName = "filewriter unit test"; + productReference = 9B7CA84E0B1297F200CD3A1D /* unit_test */; + productType = "com.apple.product-type.tool"; + }; + 9BD82A9A0B00267E0055103E /* handler_test */ = { + isa = PBXNativeTarget; + buildConfigurationList = 9BD82AA60B0026BF0055103E /* Build configuration list for PBXNativeTarget "handler_test" */; + buildPhases = ( + 9BD82A980B00267E0055103E /* Sources */, + 9BD82A990B00267E0055103E /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = handler_test; + productName = ExceptionTester; + productReference = 9BD82A9B0B00267E0055103E /* handler_test */; + productType = "com.apple.product-type.tool"; + }; + F93A88740E8B4C700026AF89 /* obj-c_TestCases */ = { + isa = PBXNativeTarget; + buildConfigurationList = F93A88790E8B4C700026AF89 /* Build configuration list for PBXNativeTarget "obj-c_TestCases" */; + buildPhases = ( + F93A88700E8B4C700026AF89 /* Resources */, + F93A88710E8B4C700026AF89 /* Sources */, + F93A88720E8B4C700026AF89 /* Frameworks */, + F93A88730E8B4C700026AF89 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "obj-c_TestCases"; + productName = octestcases; + productReference = F93A88750E8B4C700026AF89 /* octestcases.octest */; + productType = "com.apple.product-type.bundle"; + }; + F9AE19C20DB04A9500C98454 /* minidump_tests64 */ = { + isa = PBXNativeTarget; + buildConfigurationList = F9AE19C70DB04AA200C98454 /* Build configuration list for PBXNativeTarget "minidump_tests64" */; + buildPhases = ( + F9AE19BE0DB04A9500C98454 /* Resources */, + F9AE19BF0DB04A9500C98454 /* Sources */, + F9AE19C00DB04A9500C98454 /* Frameworks */, + F9AE19C10DB04A9500C98454 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = minidump_tests64; + productName = minidump_tests; + productReference = F9AE19C30DB04A9500C98454 /* minidump_tests64.cptest */; + productType = "com.apple.product-type.bundle"; + }; + F9AE5B320DBFDBA300505983 /* minidump_tests32 */ = { + isa = PBXNativeTarget; + buildConfigurationList = F9AE5B380DBFDBA300505983 /* Build configuration list for PBXNativeTarget "minidump_tests32" */; + buildPhases = ( + F9AE5B2E0DBFDBA300505983 /* Resources */, + F9AE5B2F0DBFDBA300505983 /* Sources */, + F9AE5B300DBFDBA300505983 /* Frameworks */, + F9AE5B310DBFDBA300505983 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = minidump_tests32; + productName = Untitled; + productReference = F9AE5B330DBFDBA300505983 /* minidump_tests32.cptest */; + productType = "com.apple.product-type.bundle"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 08FB7793FE84155DC02AAC07 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 1DEB923508733DC60010E9CD /* Build configuration list for PBXProject "minidump_test" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 1; + knownRegions = ( + English, + Japanese, + French, + German, + ); + mainGroup = 08FB7794FE84155DC02AAC07 /* MinidumpWriter */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8DD76F620486A84900D96B5E /* generator_test */, + 9BD82A9A0B00267E0055103E /* handler_test */, + 9B7CA84D0B1297F200CD3A1D /* unit_test */, + F9AE19C20DB04A9500C98454 /* minidump_tests64 */, + F9AE5B320DBFDBA300505983 /* minidump_tests32 */, + F93A88740E8B4C700026AF89 /* obj-c_TestCases */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + F93A88700E8B4C700026AF89 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F9AE19BE0DB04A9500C98454 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F9AE5B2E0DBFDBA300505983 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + F93A88730E8B4C700026AF89 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n"; + }; + F9AE19C10DB04A9500C98454 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n# Run gcov on the framework getting tested\nif [ \"${CONFIGURATION}\" = 'Coverage' ];\nthen\n FRAMEWORK_NAME=minidump_tests64\n FRAMEWORK_OBJ_DIR=${OBJROOT}/${PROJECT_NAME}.build/${CONFIGURATION}/${FRAMEWORK_NAME}.build/Objects-normal/${NATIVE_ARCH_ACTUAL}\n mkdir -p coverage\n pushd coverage\n echo find ${OBJROOT} -name *.gcda -exec gcov -o ${FRAMEWORK_OBJ_DIR} {} \\;\n find ${OBJROOT} -name *.gcda -exec gcov -o ${FRAMEWORK_OBJ_DIR} {} \\;\n popd\nfi "; + }; + F9AE5B310DBFDBA300505983 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 8DD76F640486A84900D96B5E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9BD82C020B01333D0055103E /* minidump_generator_test.cc in Sources */, + 9BD82C0F0B0133520055103E /* exception_handler.cc in Sources */, + 9BD82C110B0133520055103E /* minidump_generator.cc in Sources */, + 9BD82C260B01344C0055103E /* minidump_file_writer.cc in Sources */, + 9BD82C2E0B01345E0055103E /* string_utilities.cc in Sources */, + D2F651000BEF947200920385 /* file_id.cc in Sources */, + D2F651020BEF947200920385 /* macho_id.cc in Sources */, + D2F651040BEF947200920385 /* macho_utilities.cc in Sources */, + D2F651090BEF949A00920385 /* dynamic_images.cc in Sources */, + D2F6510E0BEF94EB00920385 /* macho_walker.cc in Sources */, + D2F651110BEF951700920385 /* string_conversion.cc in Sources */, + D2F651150BEF953000920385 /* convert_UTF.c in Sources */, + 8BFC81B011FF9C8D002CB4DC /* breakpad_nlist_64.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9B7CA84B0B1297F200CD3A1D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9B7CA8540B12989000CD3A1D /* minidump_file_writer_unittest.cc in Sources */, + 9B7CA8550B1298A100CD3A1D /* minidump_file_writer.cc in Sources */, + 9BC1D2940B336F2300F2A2B4 /* convert_UTF.c in Sources */, + 9BC1D2950B336F2500F2A2B4 /* string_conversion.cc in Sources */, + 8BFC81AE11FF9C8C002CB4DC /* breakpad_nlist_64.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9BD82A980B00267E0055103E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9BD82BFF0B01333D0055103E /* exception_handler_test.cc in Sources */, + 9BD82C0D0B0133520055103E /* exception_handler.cc in Sources */, + 9BD82C0E0B0133520055103E /* minidump_generator.cc in Sources */, + 9BD82C250B01344C0055103E /* minidump_file_writer.cc in Sources */, + 9BD82C2D0B01345E0055103E /* string_utilities.cc in Sources */, + 9B35FF5A0B267D5F008DE8C7 /* convert_UTF.c in Sources */, + 9B35FF5B0B267D5F008DE8C7 /* string_conversion.cc in Sources */, + D2F6511B0BEF970E00920385 /* dynamic_images.cc in Sources */, + D2F6511D0BEF973500920385 /* file_id.cc in Sources */, + D2F6511E0BEF973600920385 /* macho_id.cc in Sources */, + D2F6511F0BEF973900920385 /* macho_utilities.cc in Sources */, + D2F651210BEF975400920385 /* macho_walker.cc in Sources */, + 8BFC81AF11FF9C8C002CB4DC /* breakpad_nlist_64.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F93A88710E8B4C700026AF89 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F93A88860E8B4C9A0026AF89 /* dwarftests.mm in Sources */, + F93A88870E8B4C9A0026AF89 /* dump_syms.mm in Sources */, + F93A88880E8B4C9A0026AF89 /* bytereader.cc in Sources */, + F93A88890E8B4C9A0026AF89 /* dwarf2reader.cc in Sources */, + F93A888A0E8B4C9A0026AF89 /* functioninfo.cc in Sources */, + F93A888B0E8B4C9A0026AF89 /* md5.cc in Sources */, + F93A887D0E8B4C8C0026AF89 /* macho_walker.cc in Sources */, + F93A887E0E8B4C8C0026AF89 /* macho_id.cc in Sources */, + F93A887F0E8B4C8C0026AF89 /* macho_utilities.cc in Sources */, + F93A88800E8B4C8C0026AF89 /* file_id.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F9AE19BF0DB04A9500C98454 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F9B34E870DBC1E1600306484 /* dynamic_images.cc in Sources */, + F982089C0DB3280D0017AECA /* breakpad_nlist_test.cc in Sources */, + F98208A30DB32CAE0017AECA /* breakpad_nlist_64.cc in Sources */, + F9C5A4220DB82DD800209C76 /* DynamicImagesTests.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F9AE5B2F0DBFDBA300505983 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + F9AE5B390DBFDBDB00505983 /* dynamic_images.cc in Sources */, + F9AE5B3A0DBFDBDB00505983 /* DynamicImagesTests.cc in Sources */, + 8BFC81AD11FF9C8A002CB4DC /* breakpad_nlist_64.cc in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1DEB923208733DC60010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\"$(DEVELOPER_FRAMEWORKS_DIR)\"", + ); + PRODUCT_NAME = generator_test; + USER_HEADER_SEARCH_PATHS = "../../../** $(inherited)"; + }; + name = Debug; + }; + 1DEB923308733DC60010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "\"$(DEVELOPER_FRAMEWORKS_DIR)\"", + ); + PRODUCT_NAME = generator_test; + USER_HEADER_SEARCH_PATHS = "../../../** $(inherited)"; + }; + name = Release; + }; + 1DEB923608733DC60010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8BFC812111FF99D5002CB4DC /* BreakpadDebug.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + 1DEB923708733DC60010E9CD /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8BFC812211FF99D5002CB4DC /* BreakpadRelease.xcconfig */; + buildSettings = { + }; + name = Release; + }; + 9B7CA8510B12984300CD3A1D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = unit_test; + USER_HEADER_SEARCH_PATHS = "../../../** $(inherited)"; + }; + name = Debug; + }; + 9B7CA8520B12984300CD3A1D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = unit_test; + USER_HEADER_SEARCH_PATHS = "../../../** $(inherited)"; + }; + name = Release; + }; + 9BD82AA70B0026BF0055103E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = handler_test; + USER_HEADER_SEARCH_PATHS = "../../.. $(inherited)"; + }; + name = Debug; + }; + 9BD82AA80B0026BF0055103E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = handler_test; + USER_HEADER_SEARCH_PATHS = "../../.. $(inherited)"; + }; + name = Release; + }; + F93A88770E8B4C700026AF89 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks"; + INFOPLIST_FILE = "obj-cTestCases-Info.plist"; + PRODUCT_NAME = octestcases; + USER_HEADER_SEARCH_PATHS = "../../../..//**"; + WRAPPER_EXTENSION = octest; + }; + name = Debug; + }; + F93A88780E8B4C700026AF89 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks"; + INFOPLIST_FILE = "obj-cTestCases-Info.plist"; + PRODUCT_NAME = octestcases; + USER_HEADER_SEARCH_PATHS = "../../../..//**"; + WRAPPER_EXTENSION = octest; + }; + name = Release; + }; + F9AE19C40DB04A9500C98454 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks"; + INFOPLIST_FILE = "minidump_tests64-Info.plist"; + PRODUCT_NAME = minidump_tests64; + USER_HEADER_SEARCH_PATHS = "../../../**"; + WRAPPER_EXTENSION = cptest; + }; + name = Debug; + }; + F9AE19C50DB04A9500C98454 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks"; + INFOPLIST_FILE = "minidump_tests64-Info.plist"; + PRODUCT_NAME = minidump_tests64; + USER_HEADER_SEARCH_PATHS = "../../../**"; + WRAPPER_EXTENSION = cptest; + }; + name = Release; + }; + F9AE5B350DBFDBA300505983 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks"; + INFOPLIST_FILE = "minidump_tests32-Info.plist"; + PRODUCT_NAME = minidump_tests32; + USER_HEADER_SEARCH_PATHS = "../../../**"; + WRAPPER_EXTENSION = cptest; + }; + name = Debug; + }; + F9AE5B370DBFDBA300505983 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + FRAMEWORK_SEARCH_PATHS = "$(DEVELOPER_LIBRARY_DIR)/Frameworks"; + INFOPLIST_FILE = "minidump_tests32-Info.plist"; + PRODUCT_NAME = minidump_tests32; + USER_HEADER_SEARCH_PATHS = "../../../**"; + WRAPPER_EXTENSION = cptest; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DEB923108733DC60010E9CD /* Build configuration list for PBXNativeTarget "generator_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB923208733DC60010E9CD /* Debug */, + 1DEB923308733DC60010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1DEB923508733DC60010E9CD /* Build configuration list for PBXProject "minidump_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB923608733DC60010E9CD /* Debug */, + 1DEB923708733DC60010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9B7CA8500B12984300CD3A1D /* Build configuration list for PBXNativeTarget "unit_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9B7CA8510B12984300CD3A1D /* Debug */, + 9B7CA8520B12984300CD3A1D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 9BD82AA60B0026BF0055103E /* Build configuration list for PBXNativeTarget "handler_test" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9BD82AA70B0026BF0055103E /* Debug */, + 9BD82AA80B0026BF0055103E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F93A88790E8B4C700026AF89 /* Build configuration list for PBXNativeTarget "obj-c_TestCases" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F93A88770E8B4C700026AF89 /* Debug */, + F93A88780E8B4C700026AF89 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F9AE19C70DB04AA200C98454 /* Build configuration list for PBXNativeTarget "minidump_tests64" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F9AE19C40DB04A9500C98454 /* Debug */, + F9AE19C50DB04A9500C98454 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F9AE5B380DBFDBA300505983 /* Build configuration list for PBXNativeTarget "minidump_tests32" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F9AE5B350DBFDBA300505983 /* Debug */, + F9AE5B370DBFDBA300505983 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/minidump_tests32-Info.plist b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/minidump_tests32-Info.plist new file mode 100644 index 000000000..921ebf357 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/minidump_tests32-Info.plist @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleIdentifier</key> + <string>com.google.breakpad.minidump_tests32</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>BNDL</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1.0</string> +</dict> +</plist> diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/minidump_tests64-Info.plist b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/minidump_tests64-Info.plist new file mode 100644 index 000000000..acfbd3091 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/minidump_tests64-Info.plist @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleIdentifier</key> + <string>com.google.breakpad.minidump_tests64</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>BNDL</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1.0</string> + <key>CSResourcesFileMapped</key> + <string>yes</string> +</dict> +</plist> diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/moz.build b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/moz.build new file mode 100644 index 000000000..7b3260808 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/moz.build @@ -0,0 +1,22 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES += [ + 'breakpad_nlist_64.cc', + 'dynamic_images.cc', + 'exception_handler.cc', + 'minidump_generator.cc', +] + +# We allow warnings for third-party code that can be updated from upstream. +ALLOW_COMPILER_WARNINGS = True + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '../../..', +] + diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/obj-cTestCases-Info.plist b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/obj-cTestCases-Info.plist new file mode 100644 index 000000000..65013556d --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/obj-cTestCases-Info.plist @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleIdentifier</key> + <string>com.yourcompany.${PRODUCT_NAME:identifier}</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>BNDL</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1.0</string> +</dict> +</plist> diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/protected_memory_allocator.cc b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/protected_memory_allocator.cc new file mode 100644 index 000000000..6142ad124 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/protected_memory_allocator.cc @@ -0,0 +1,92 @@ +// Copyright (c) 2006, 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. +// +// ProtectedMemoryAllocator +// +// See the header file for documentation + +#include "protected_memory_allocator.h" +#include <assert.h> + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +ProtectedMemoryAllocator::ProtectedMemoryAllocator(vm_size_t pool_size) + : pool_size_(pool_size), + next_alloc_offset_(0), + valid_(false) { + + kern_return_t result = vm_allocate(mach_task_self(), + &base_address_, + pool_size, + TRUE + ); + + valid_ = (result == KERN_SUCCESS); + assert(valid_); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +ProtectedMemoryAllocator::~ProtectedMemoryAllocator() { + vm_deallocate(mach_task_self(), + base_address_, + pool_size_ + ); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +char *ProtectedMemoryAllocator::Allocate(vm_size_t bytes) { + if (valid_ && next_alloc_offset_ + bytes <= pool_size_) { + char *p = (char*)base_address_ + next_alloc_offset_; + next_alloc_offset_ += bytes; + return p; + } + + return NULL; // ran out of memory in our allocation block +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +kern_return_t ProtectedMemoryAllocator::Protect() { + kern_return_t result = vm_protect(mach_task_self(), + base_address_, + pool_size_, + FALSE, + VM_PROT_READ); + + return result; +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +kern_return_t ProtectedMemoryAllocator::Unprotect() { + kern_return_t result = vm_protect(mach_task_self(), + base_address_, + pool_size_, + FALSE, + VM_PROT_READ | VM_PROT_WRITE); + + return result; +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/protected_memory_allocator.h b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/protected_memory_allocator.h new file mode 100644 index 000000000..7e188db26 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/protected_memory_allocator.h @@ -0,0 +1,85 @@ +// Copyright (c) 2006, 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. +// +// ProtectedMemoryAllocator +// +// A very simple allocator class which allows allocation, but not deallocation. +// The allocations can be made read-only with the Protect() method. +// This class is NOT useful as a general-purpose memory allocation system, +// since it does not allow deallocation. It is useful to use for a group +// of allocations which are created in the same time-frame and destroyed +// in the same time-frame. It is useful for making allocations of memory +// which will not need to change often once initialized. This memory can then +// be protected from memory smashers by calling the Protect() method. + +#ifndef PROTECTED_MEMORY_ALLOCATOR_H__ +#define PROTECTED_MEMORY_ALLOCATOR_H__ + +#include <mach/mach.h> + +// +class ProtectedMemoryAllocator { + public: + ProtectedMemoryAllocator(vm_size_t pool_size); + ~ProtectedMemoryAllocator(); + + // Returns a pointer to an allocation of size n within the pool. + // Fails by returning NULL is no more space is available. + // Please note that the pointers returned from this method should not + // be freed in any way (for example by calling free() on them ). + char * Allocate(vm_size_t n); + + // Returns the base address of the allocation pool. + char * GetBaseAddress() { return (char*)base_address_; } + + // Returns the size of the allocation pool, including allocated + // plus free space. + vm_size_t GetTotalSize() { return pool_size_; } + + // Returns the number of bytes already allocated in the pool. + vm_size_t GetAllocatedSize() { return next_alloc_offset_; } + + // Returns the number of bytes available for allocation. + vm_size_t GetFreeSize() { return pool_size_ - next_alloc_offset_; } + + // Makes the entire allocation pool read-only including, of course, + // all allocations made from the pool. + kern_return_t Protect(); + + // Makes the entire allocation pool read/write. + kern_return_t Unprotect(); + + private: + vm_size_t pool_size_; + vm_address_t base_address_; + vm_size_t next_alloc_offset_; + bool valid_; +}; + +#endif // PROTECTED_MEMORY_ALLOCATOR_H__ diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/testcases/DynamicImagesTests.cc b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/testcases/DynamicImagesTests.cc new file mode 100644 index 000000000..0fc7825b2 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/testcases/DynamicImagesTests.cc @@ -0,0 +1,79 @@ +// Copyright (c) 2008, 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. + +// +// DynamicImagesTests.cpp +// minidump_test +// +// Created by Neal Sidhwaney on 4/17/08. +// Copyright 2008 Google Inc. All rights reserved. +// + +#include "client/mac/handler/testcases/DynamicImagesTests.h" +#include "client/mac/handler/dynamic_images.h" + +DynamicImagesTests test2(TEST_INVOCATION(DynamicImagesTests, + ReadTaskMemoryTest)); +DynamicImagesTests test3(TEST_INVOCATION(DynamicImagesTests, + ReadLibrariesFromLocalTaskTest)); + +DynamicImagesTests::DynamicImagesTests(TestInvocation *invocation) + : TestCase(invocation) { +} + +DynamicImagesTests::~DynamicImagesTests() { +} + +void DynamicImagesTests::ReadTaskMemoryTest() { + kern_return_t kr; + + // pick test2 as a symbol we know to be valid to read + // anything will work, really + void *addr = reinterpret_cast<void*>(&test2); + std::vector<uint8_t> buf(getpagesize()); + + fprintf(stderr, "reading 0x%p\n", addr); + kr = google_breakpad::ReadTaskMemory(mach_task_self(), + (uint64_t)addr, + getpagesize(), + buf); + + CPTAssert(kr == KERN_SUCCESS); + + CPTAssert(0 == memcmp(&buf[0], (const void*)addr, getpagesize())); +} + +void DynamicImagesTests::ReadLibrariesFromLocalTaskTest() { + + mach_port_t me = mach_task_self(); + google_breakpad::DynamicImages *d = new google_breakpad::DynamicImages(me); + + fprintf(stderr,"Local task image count: %d\n", d->GetImageCount()); + + CPTAssert(d->GetImageCount() > 0); +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/testcases/DynamicImagesTests.h b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/testcases/DynamicImagesTests.h new file mode 100644 index 000000000..e1e79993b --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/testcases/DynamicImagesTests.h @@ -0,0 +1,52 @@ +// Copyright (c) 2008, 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. + +// +// DynamicImagesTests.h +// minidump_test +// +// Created by Neal Sidhwaney on 4/17/08. +// Copyright 2008 Google Inc. All rights reserved. +// +// + +#ifndef _CLIENT_MAC_HANDLER_TESTCASES_DYNAMICIMAGESTESTS_H__ +#define _CLIENT_MAC_HANDLER_TESTCASES_DYNAMICIMAGESTESTS_H__ + +#include <CPlusTest/CPlusTest.h> + +class DynamicImagesTests : public TestCase { + public: + explicit DynamicImagesTests(TestInvocation* invocation); + virtual ~DynamicImagesTests(); + + void ReadTaskMemoryTest(); + void ReadLibrariesFromLocalTaskTest(); +}; + +#endif /* _CLIENT_MAC_HANDLER_TESTCASES_DYNAMICIMAGESTESTS_H__ */ diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/testcases/breakpad_nlist_test.cc b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/testcases/breakpad_nlist_test.cc new file mode 100644 index 000000000..e7332bfb6 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/testcases/breakpad_nlist_test.cc @@ -0,0 +1,106 @@ +// Copyright (c) 2008, 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. + +// +// breakpad_nlist_test.cc +// minidump_test +// +// Created by Neal Sidhwaney on 4/13/08. +// Copyright 2008 Google Inc. All rights reserved. +// + +#include "client/mac/handler/testcases/breakpad_nlist_test.h" +#include <mach-o/nlist.h> +#include "client/mac/handler/breakpad_nlist_64.h" + +BreakpadNlistTest test1(TEST_INVOCATION(BreakpadNlistTest, CompareToNM)); + +BreakpadNlistTest::BreakpadNlistTest(TestInvocation *invocation) + : TestCase(invocation) { +} + + +BreakpadNlistTest::~BreakpadNlistTest() { +} + +void BreakpadNlistTest::CompareToNM() { +#if TARGET_CPU_X86_64 + system("/usr/bin/nm -arch x86_64 /usr/lib/dyld > /tmp/dyld-namelist.txt"); +#elif TARGET_CPU_PPC64 + system("/usr/bin/nm -arch ppc64 /usr/lib/dyld > /tmp/dyld-namelist.txt"); +#endif + + FILE *fd = fopen("/tmp/dyld-namelist.txt", "rt"); + + char oneNMAddr[30]; + char symbolType; + char symbolName[500]; + while (!feof(fd)) { + fscanf(fd, "%s %c %s", oneNMAddr, &symbolType, symbolName); + breakpad_nlist symbolList[2]; + breakpad_nlist &list = symbolList[0]; + + memset(symbolList, 0, sizeof(breakpad_nlist)*2); + const char *symbolNames[2]; + symbolNames[0] = (const char*)symbolName; + symbolNames[1] = "\0"; + breakpad_nlist_64("/usr/lib/dyld", &list, symbolNames); + uint64_t nmAddr = strtol(oneNMAddr, NULL, 16); + if (!IsSymbolMoreThanOnceInDyld(symbolName)) { + CPTAssert(nmAddr == symbolList[0].n_value); + } + } + + fclose(fd); +} + +bool BreakpadNlistTest::IsSymbolMoreThanOnceInDyld(const char *symbolName) { + // These are the symbols that occur more than once when nm dumps + // the symbol table of /usr/lib/dyld. Our nlist program returns + // the first address because it's doing a search so we need to exclude + // these from causing the test to fail + const char *multipleSymbols[] = { + "__Z41__static_initialization_and_destruction_0ii", + "___tcf_0", + "___tcf_1", + "_read_encoded_value_with_base", + "_read_sleb128", + "_read_uleb128", + "\0"}; + + bool found = false; + + for (int i = 0; multipleSymbols[i][0]; i++) { + if (!strcmp(multipleSymbols[i], symbolName)) { + found = true; + break; + } + } + + return found; +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/testcases/breakpad_nlist_test.h b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/testcases/breakpad_nlist_test.h new file mode 100644 index 000000000..e93657cc9 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/testcases/breakpad_nlist_test.h @@ -0,0 +1,62 @@ +// Copyright (c) 2008, 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. + +// +// breakpad_nlist_test.h +// minidump_test +// +// Created by Neal Sidhwaney on 4/13/08. +// Copyright 2008 Google Inc. All rights reserved. +// +// + +#ifndef CLIENT_MAC_HANDLER_TESTCASES_BREAKPAD_NLIST_TEST_H__ +#define CLIENT_MAC_HANDLER_TESTCASES_BREAKPAD_NLIST_TEST_H__ + +#include <CPlusTest/CPlusTest.h> + +class BreakpadNlistTest : public TestCase { + private: + + // nm dumps multiple addresses for the same symbol in + // /usr/lib/dyld. So we track those so we don't report failures + // in mismatches between what our nlist returns and what nm has + // for the duplicate symbols. + bool IsSymbolMoreThanOnceInDyld(const char *symbolName); + + public: + explicit BreakpadNlistTest(TestInvocation* invocation); + virtual ~BreakpadNlistTest(); + + + /* This test case runs nm on /usr/lib/dyld and then compares the + output of every symbol to what our nlist implementation returns */ + void CompareToNM(); +}; + +#endif /* CLIENT_MAC_HANDLER_TESTCASES_BREAKPAD_NLIST_TEST_H__*/ diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/testcases/dwarftests.h b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/testcases/dwarftests.h new file mode 100644 index 000000000..21ff7a44f --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/testcases/dwarftests.h @@ -0,0 +1,46 @@ +// Copyright (c) 2008, 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. + +// +// dwarftests.h +// minidump_test +// +// Created by Neal Sidhwaney on 9/24/08. +// Copyright 2008 Google Inc. All rights reserved. +// + +#import <SenTestingKit/SenTestingKit.h> + + +@interface dwarftests : SenTestCase { + +} + +- (void) testDWARFSymbolFileGeneration; + +@end diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/testcases/dwarftests.mm b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/testcases/dwarftests.mm new file mode 100644 index 000000000..40c69aff2 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/testcases/dwarftests.mm @@ -0,0 +1,60 @@ +// Copyright (c) 2008, 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. + +// +// dwarftests.m +// minidump_test +// +// Created by Neal Sidhwaney on 9/24/08. +// Copyright 2008 Google Inc. All rights reserved. +// + +#import "dwarftests.h" +#import "dump_syms.h" + +@implementation dwarftests +- (void) testDWARFSymbolFileGeneration { + NSString *inputBreakpadSymbolFile = @"testcases/testdata/dump_syms_i386_breakpad.sym"; + NSString *outputBreakpadSymbolFile = @"/tmp/dump_syms_i386.breakpad"; + + DumpSymbols *dump = [[DumpSymbols alloc] initWithContentsOfFile:@"testcases/testdata/dump_syms_dwarf_data"]; + + STAssertNotNil(dump, @"DumpSymbols is nil"); + [dump setArchitecture:@"i386"]; + [dump writeSymbolFile:outputBreakpadSymbolFile]; + + NSData *d = [[NSData alloc] initWithContentsOfFile:inputBreakpadSymbolFile]; + STAssertNotNil(d, @"Input breakpad symbol file not found"); + + NSData *d1 = [[NSData alloc] initWithContentsOfFile:outputBreakpadSymbolFile]; + STAssertNotNil(d1, @"Output breakpad symbol file not found"); + + STAssertTrue([d isEqualToData:d1], + @"Symbol files were not equal!",nil); +} +@end diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/testcases/testdata/dump_syms_dwarf_data b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/testcases/testdata/dump_syms_dwarf_data Binary files differnew file mode 100644 index 000000000..5be17aeed --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/testcases/testdata/dump_syms_dwarf_data diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/testcases/testdata/dump_syms_i386_breakpad.sym b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/testcases/testdata/dump_syms_i386_breakpad.sym new file mode 100644 index 000000000..bca43c103 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/testcases/testdata/dump_syms_i386_breakpad.sym @@ -0,0 +1,5300 @@ +MODULE mac x86 94BF873C47A73BC07125291390B4C5F10 dump_syms_dwarf_data +FILE 1 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/libkern/i386/OSByteOrder.h +FILE 2 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/architecture/byte_order.h +FILE 3 /g/code/breakpad-staging/src/tools/mac/dump_syms/../../../common/mac/dump_syms.mm +FILE 4 /Developer/SDKs/MacOSX10.4u.sdk/System/Library/Frameworks/Foundation.framework/Headers/NSRange.h +FILE 5 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/new +FILE 6 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/ext/hash_fun.h +FILE 7 ../../../common/mac/dwarf/dwarf2reader.h +FILE 8 ../../../common/mac/file_id.h +FILE 9 ../../../common/mac/dwarf/functioninfo.h +FILE 10 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_tree.h +FILE 11 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_iterator.h +FILE 12 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/ext/hashtable.h +FILE 13 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_pair.h +FILE 14 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/ext/new_allocator.h +FILE 15 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/allocator.h +FILE 16 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_vector.h +FILE 17 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_iterator_base_types.h +FILE 18 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_iterator_base_funcs.h +FILE 19 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_algo.h +FILE 20 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_map.h +FILE 21 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_construct.h +FILE 22 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_function.h +FILE 23 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/ext/hash_map +FILE 24 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/basic_string.h +FILE 25 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_algobase.h +FILE 26 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_list.h +FILE 27 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/list.tcc +FILE 28 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_uninitialized.h +FILE 29 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/vector.tcc +FILE 30 /g/code/breakpad-staging/src/tools/mac/dump_syms/../../../common/mac/dwarf/functioninfo.cc +FILE 31 ../../../common/mac/dwarf/dwarf2reader.h +FILE 32 ../../../common/mac/dwarf/functioninfo.h +FILE 33 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_pair.h +FILE 34 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/ext/hashtable.h +FILE 35 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/memory +FILE 36 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/ext/new_allocator.h +FILE 37 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/basic_string.h +FILE 38 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_iterator.h +FILE 39 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_vector.h +FILE 40 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_tree.h +FILE 41 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_function.h +FILE 42 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/ext/hash_map +FILE 43 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_construct.h +FILE 44 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_algobase.h +FILE 45 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_map.h +FILE 46 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_uninitialized.h +FILE 47 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/vector.tcc +FILE 48 /g/code/breakpad-staging/src/tools/mac/dump_syms/dump_syms_tool.mm +FILE 49 /g/code/breakpad-staging/src/tools/mac/dump_syms/../../../common/mac/file_id.cc +FILE 50 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/architecture/byte_order.h +FILE 51 /g/code/breakpad-staging/src/tools/mac/dump_syms/../../../common/mac/macho_id.cc +FILE 52 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/libkern/i386/OSByteOrder.h +FILE 53 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/architecture/byte_order.h +FILE 54 /g/code/breakpad-staging/src/tools/mac/dump_syms/../../../common/mac/macho_walker.cc +FILE 55 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/libkern/i386/OSByteOrder.h +FILE 56 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/architecture/byte_order.h +FILE 57 /g/code/breakpad-staging/src/tools/mac/dump_syms/../../../common/mac/macho_utilities.cc +FILE 58 /g/code/breakpad-staging/src/tools/mac/dump_syms/../../../common/mac/dwarf/bytereader.cc +FILE 59 ../../../common/mac/dwarf/bytereader-inl.h +FILE 60 /g/code/breakpad-staging/src/tools/mac/dump_syms/../../../common/mac/dwarf/dwarf2reader.cc +FILE 61 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_algobase.h +FILE 62 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_deque.h +FILE 63 ../../../common/mac/dwarf/bytereader.h +FILE 64 ../../../common/mac/dwarf/bytereader-inl.h +FILE 65 ../../../common/mac/dwarf/line_state_machine.h +FILE 66 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_list.h +FILE 67 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/memory +FILE 68 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/ext/new_allocator.h +FILE 69 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/allocator.h +FILE 70 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_pair.h +FILE 71 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_vector.h +FILE 72 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_iterator.h +FILE 73 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_construct.h +FILE 74 ../../../common/mac/dwarf/dwarf2reader.h +FILE 75 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_stack.h +FILE 76 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/deque.tcc +FILE 77 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/list.tcc +FILE 78 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/vector.tcc +FILE 79 /Developer/SDKs/MacOSX10.4u.sdk/usr/include/c++/4.0.0/bits/stl_uninitialized.h +FILE 80 /var/tmp/gcc/gcc-5484~1/src/gcc/libgcc2.c +FUNC 162a 28 0 _OSSwapInt16 +162a 10 44 55 +163a 16 46 55 +1650 2 47 55 +FUNC 1652 1c 0 _OSSwapInt32 +1652 f 53 55 +1661 8 55 55 +1669 3 56 55 +166c 2 57 55 +FUNC 166e 2b 0 _OSSwapInt64 +166e 12 64 55 +1680 11 69 55 +1691 6 70 55 +1697 2 71 55 +1699 1 71 55 +FUNC 169a 1e 0 NXSwapShort +169a 10 43 56 +16aa c 45 56 +16b6 2 46 56 +FUNC 16b8 19 0 NXSwapInt +16b8 f 52 56 +16c7 8 54 56 +16cf 2 55 56 +16d1 1 55 56 +FUNC 16d2 19 0 NXSwapLong +16d2 f 61 56 +16e1 8 63 56 +16e9 2 64 56 +16eb 1 64 56 +FUNC 16ec 1f 0 NXSwapLongLong +16ec 12 70 56 +16fe b 72 56 +1709 2 73 56 +170b 1 73 56 +FUNC 170c 181 0 -[DumpSymbols convertCPlusPlusSymbols:] +170c 14 128 3 +1720 54 130 3 +1774 f 132 3 +1783 7 133 3 +178a 1a 136 3 +17a4 5 138 3 +17a9 1a 139 3 +17c3 23 140 3 +17e6 7 141 3 +17ed 44 142 3 +1831 1e 145 3 +184f 29 138 3 +1878 b 148 3 +1883 3 150 3 +1886 7 151 3 +188d 1 151 3 +FUNC 188e 323 0 -[DumpSymbols convertSymbols] +188e 14 154 3 +18a2 1f 155 3 +18c1 3e 156 3 +18ff 2b 160 3 +192a c 162 3 +1936 43 164 3 +1979 2a 165 3 +19a3 20 168 3 +19c3 d 169 3 +19d0 1e 171 3 +19ee 11 162 3 +19ff 7 181 3 +1a06 6 182 3 +1a0c 5 184 3 +1a11 15 185 3 +1a26 6 18 4 +1a2c 6 19 4 +1a32 6 20 4 +1a38 6 185 3 +1a3e 28 186 3 +1a66 21 187 3 +1a87 1a 188 3 +1aa1 a 190 3 +1aab c 194 3 +1ab7 43 198 3 +1afa 21 199 3 +1b1b 20 202 3 +1b3b 2e 203 3 +1b69 1e 194 3 +1b87 c 184 3 +1b93 17 207 3 +1baa 7 208 3 +1bb1 1 208 3 +FUNC 1bb2 4a2 0 -[DumpSymbols addFunction:line:address:section:] +1bb2 21 211 3 +1bd3 2f 212 3 +1c02 e 214 3 +1c10 4 219 3 +1c14 2a 221 3 +1c3e 22 223 3 +1c60 6 224 3 +1c66 2a 225 3 +1c90 4 226 3 +1c94 2e 230 3 +1cc2 2e 233 3 +1cf0 2e 236 3 +1d1e a 239 3 +1d28 2b 253 3 +1d53 e 254 3 +1d61 3c 255 3 +1d9d 22 32 4 +1dbf 3 256 3 +1dc2 6 259 3 +1dc8 a 260 3 +1dd2 3c 261 3 +1e0e 25 262 3 +1e33 2a 263 3 +1e5d 22 265 3 +1e7f 26 270 3 +1ea5 6 272 3 +1eab 37 273 3 +1ee2 2a 274 3 +1f0c 17 275 3 +1f23 43 278 3 +1f66 2e 279 3 +1f94 23 282 3 +1fb7 43 285 3 +1ffa 52 287 3 +204c 8 289 3 +FUNC 2054 5a4 0 -[DumpSymbols processSymbolItem:stringTable:] +2054 18 292 3 +206c 8 293 3 +2074 4 294 3 +2078 16 297 3 +208e c 298 3 +209a f 300 3 +20a9 b 301 3 +20b4 16 303 3 +20ca 4d 309 3 +2117 38 311 3 +214f 30 315 3 +217f 60 317 3 +21df d 322 3 +21ec 2b 325 3 +2217 3a 327 3 +2251 f 332 3 +2260 2d 333 3 +228d 1a 334 3 +22a7 32 335 3 +22d9 20 342 3 +22f9 c 343 3 +2305 24 348 3 +2329 a 349 3 +2333 3c 350 3 +236f 2a 352 3 +2399 1c 353 3 +23b5 9 354 3 +23be f 356 3 +23cd 2d 357 3 +23fa 2f 358 3 +2429 20 360 3 +2449 c 361 3 +2455 7 363 3 +245c 21 365 3 +247d 4a 368 3 +24c7 9 370 3 +24d0 1a 371 3 +24ea 4b 372 3 +2535 4 373 3 +2539 5 371 3 +253e 29 374 3 +2567 2d 376 3 +2594 4b 378 3 +25df 4 379 3 +25e3 a 382 3 +25ed b 383 3 +FUNC 25f8 c9 0 -[DumpSymbols loadSymbolInfo:offset:] +25f8 13 391 3 +260b 2b 392 3 +2636 2a 393 3 +2660 2d 395 3 +268d 2e 398 3 +26bb 6 399 3 +26c1 1 399 3 +FUNC 26c2 2be 0 -[DumpSymbols loadSTABSSymbolInfo:offset:] +26c2 16 537 3 +26d8 9 538 3 +26e1 10 539 3 +26f1 2e 540 3 +271f 9 542 3 +2728 22 543 3 +274a 4 544 3 +274e a 546 3 +2758 3c 547 3 +2794 c 549 3 +27a0 e 550 3 +27ae 6 551 3 +27b4 25 552 3 +27d9 25 553 3 +27fe 25 554 3 +2823 c 555 3 +282f c 556 3 +283b c 559 3 +2847 23 562 3 +286a a 563 3 +2874 a 564 3 +287e 2e 565 3 +28ac 39 566 3 +28e5 2e 570 3 +2913 4 571 3 +2917 17 559 3 +292e 25 575 3 +2953 9 576 3 +295c 17 549 3 +2973 4 579 3 +2977 9 580 3 +FUNC 2980 28a 0 -[DumpSymbols loadSymbolInfo64:offset:] +2980 16 583 3 +2996 9 585 3 +299f 10 586 3 +29af 2e 587 3 +29dd 9 589 3 +29e6 22 590 3 +2a08 4 591 3 +2a0c c 593 3 +2a18 e 594 3 +2a26 6 595 3 +2a2c 25 596 3 +2a51 25 597 3 +2a76 25 598 3 +2a9b 9 599 3 +2aa4 c 600 3 +2ab0 c 603 3 +2abc 17 604 3 +2ad3 23 609 3 +2af6 a 610 3 +2b00 a 611 3 +2b0a 2e 612 3 +2b38 37 613 3 +2b6f 2e 615 3 +2b9d 4 616 3 +2ba1 17 603 3 +2bb8 25 620 3 +2bdd 9 621 3 +2be6 17 593 3 +2bfd 4 624 3 +2c01 9 625 3 +FUNC 2c0a 199 0 -[DumpSymbols loadSymbolInfoForArchitecture] +2c0a 13 628 3 +2c1d 41 630 3 +2c5e 2b 631 3 +2c89 1a 632 3 +2ca3 40 634 3 +2ce3 40 635 3 +2d23 5f 637 3 +2d82 17 639 3 +2d99 4 640 3 +2d9d 6 641 3 +2da3 1 641 3 +FUNC 2da4 3e5 0 -[DumpSymbols loadHeader:offset:] +2da4 18 728 3 +2dbc 9 729 3 +2dc5 10 730 3 +2dd5 2e 731 3 +2e03 9 733 3 +2e0c 2b 734 3 +2e37 1e 736 3 +2e55 c 738 3 +2e61 e 739 3 +2e6f 6 740 3 +2e75 50 742 3 +2ec5 2e 743 3 +2ef3 2e 744 3 +2f21 2e 745 3 +2f4f 20 746 3 +2f6f 1b7 755 3 +3126 9 757 3 +312f 25 761 3 +3154 9 762 3 +315d 17 738 3 +3174 a 765 3 +317e b 766 3 +3189 1 766 3 +FUNC 318a 41d 0 -[DumpSymbols loadHeader64:offset:] +318a 18 769 3 +31a2 9 771 3 +31ab 10 772 3 +31bb 2e 773 3 +31e9 9 775 3 +31f2 c 777 3 +31fe 2b 778 3 +3229 e 779 3 +3237 6 780 3 +323d 50 781 3 +328d 49 782 3 +32d6 49 783 3 +331f 2e 784 3 +334d 9 785 3 +3356 29 786 3 +337f 1c5 794 3 +3544 9 795 3 +354d 25 799 3 +3572 9 800 3 +357b 17 777 3 +3592 a 803 3 +359c b 804 3 +35a7 1 804 3 +FUNC 35a8 52a 0 -[DumpSymbols loadModuleInfo] +35a8 14 807 3 +35bc e 808 3 +35ca 41 810 3 +360b 1a 811 3 +3625 6 812 3 +362b 6 814 3 +3631 17 815 3 +3648 c 816 3 +3654 29 820 3 +367d 29 821 3 +36a6 29 822 3 +36cf 35 824 3 +3704 12 826 3 +3716 17 827 3 +372d c 828 3 +3739 3c 832 3 +3775 a 834 3 +377f 9 836 3 +3788 25 837 3 +37ad c 839 3 +37b9 54 840 3 +380d 57 841 3 +3864 57 842 3 +38bb 57 843 3 +3912 57 844 3 +3969 1c 846 3 +3985 4b 847 3 +39d0 49 849 3 +3a19 13 839 3 +3a2c 6 851 3 +3a32 3c 852 3 +3a6e 3a 854 3 +3aa8 17 857 3 +3abf c 858 3 +3acb 7 859 3 +FUNC 3ad2 b6 0 WriteFormat +3ad2 10 862 3 +3ae2 6 867 3 +3ae8 24 868 3 +3b0c 27 869 3 +3b33 40 870 3 +3b73 c 873 3 +3b7f 9 874 3 +FUNC 3b88 35 0 -[DumpSymbols availableArchitectures] +3b88 13 1140 3 +3b9b 1c 1141 3 +3bb7 6 1142 3 +3bbd 1 1142 3 +FUNC 3bbe 1b4 0 -[DumpSymbols setArchitecture:] +3bbe 13 1158 3 +3bd1 1a 1159 3 +3beb 4 1160 3 +3bef 2a 1162 3 +3c19 9 1163 3 +3c22 2a 1165 3 +3c4c 9 1166 3 +3c55 9 1167 3 +3c5e 2a 1169 3 +3c88 6 1170 3 +3c8e 2a 1172 3 +3cb8 6 1173 3 +3cbe 2a 1175 3 +3ce8 4 1176 3 +3cec 6 1179 3 +3cf2 2c 1180 3 +3d1e 9 1181 3 +3d27 1c 1183 3 +3d43 1f 1184 3 +3d62 a 1187 3 +3d6c 6 1188 3 +FUNC 3d72 14 0 -[DumpSymbols architecture] +3d72 c 1191 3 +3d7e 6 1192 3 +3d84 2 1193 3 +FUNC 3d86 e7 0 -[DumpSymbols writeSymbolFile:] +3d86 13 1196 3 +3d99 1a 1197 3 +3db3 48 1200 3 +3dfb 9 1201 3 +3e04 1e 1203 3 +3e22 6 1205 3 +3e28 9 1206 3 +3e31 21 1208 3 +3e52 b 1210 3 +3e5d a 1212 3 +3e67 6 1213 3 +3e6d 1 1213 3 +FUNC 3e6e 65 0 -[MachSection initWithMachSection:andNumber:] +3e6e 13 1219 3 +3e81 37 1220 3 +3eb8 9 1221 3 +3ec1 9 1222 3 +3eca 3 1225 3 +3ecd 6 1226 3 +3ed3 1 1226 3 +FUNC 3ed4 14 0 -[MachSection sectionPointer] +3ed4 c 1228 3 +3ee0 6 1229 3 +3ee6 2 1230 3 +FUNC 3ee8 14 0 -[MachSection sectionNumber] +3ee8 c 1232 3 +3ef4 6 1233 3 +3efa 2 1234 3 +FUNC 3efc 17c 0 -[DumpSymbols processDWARFSourceFileInfo:] +3efc 14 459 3 +3f10 a 460 3 +3f1a 3c 461 3 +3f56 20 463 3 +3f76 5 464 3 +3f7b 3a 465 3 +3fb5 1d 466 3 +3fd2 3a 467 3 +400c 2a 468 3 +4036 3b 464 3 +4071 7 471 3 +FUNC 4078 1d7 0 DumpFunctionMap(std::map<unsigned long long, dwarf2reader::FunctionInfo*, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >) +4078 15 82 3 +408d 13 83 3 +40a0 1e 85 3 +40be 42 89 3 +4100 20 90 3 +4120 2b 91 3 +414b 1a 92 3 +4165 23 93 3 +4188 46 96 3 +41ce 46 99 3 +4214 33 83 3 +4247 8 102 3 +424f 1 102 3 +FUNC 4250 3ef 0 -[DumpSymbols processDWARFFunctionInfo:] +4250 15 473 3 +4265 25 474 3 +428a 1e 476 3 +42a8 a 480 3 +42b2 3c 481 3 +42ee 3d 483 3 +432b 23 485 3 +434e 26 487 3 +4374 6 489 3 +437a 37 490 3 +43b1 2a 491 3 +43db 17 492 3 +43f2 30 496 3 +4422 3d 497 3 +445f 2e 498 3 +448d 30 502 3 +44bd 64 504 3 +4521 34 507 3 +4555 9d 509 3 +45f2 45 474 3 +4637 8 513 3 +463f 1 513 3 +FUNC 4640 1f5 0 -[DumpSymbols processDWARFLineNumberInfo:] +4640 15 515 3 +4655 25 516 3 +467a 39 520 3 +46b3 26 521 3 +46d9 6 523 3 +46df 37 524 3 +4716 2a 525 3 +4740 17 526 3 +4757 30 529 3 +4787 61 531 3 +47e8 45 516 3 +482d 8 534 3 +4835 1 534 3 +FUNC 4836 10f 0 -[DumpSymbols dealloc] +4836 13 1145 3 +4849 1c 1146 3 +4865 1c 1147 3 +4881 1c 1148 3 +489d 1c 1149 3 +48b9 1c 1150 3 +48d5 1c 1151 3 +48f1 25 1152 3 +4916 29 1154 3 +493f 6 1155 3 +4945 1 1155 3 +FUNC 4946 512 0 -[DumpSymbols loadDWARFSymbolInfo:offset:] +4946 17 402 3 +495d 9 405 3 +4966 10 406 3 +4976 2b 408 3 +49a1 38 409 3 +49d9 3a 410 3 +4a13 2e 411 3 +4a41 31 416 3 +4a72 e 418 3 +4a80 24 420 3 +4aa4 5 422 3 +4aa9 b 424 3 +4ab4 b 425 3 +4abf e 426 3 +4acd 2b 427 3 +4af8 2b 428 3 +4b23 2c 431 3 +4b4f 52 439 3 +4ba1 34 444 3 +4bd5 1a 446 3 +4bef 21 451 3 +4c10 1e 452 3 +4c2e 21 453 3 +4c4f 40 422 3 +4c8f 6 453 3 +4c95 170 422 3 +4e05 43 456 3 +4e48 10 457 3 +FUNC 4e58 4fd 0 -[DumpSymbols generateSectionDictionary:] +4e58 18 663 3 +4e70 10 665 3 +4e80 2e 666 3 +4eae 9 668 3 +4eb7 2b 669 3 +4ee2 7 670 3 +4ee9 2e 672 3 +4f17 d 676 3 +4f24 32 678 3 +4f56 29 680 3 +4f7f a 684 3 +4f89 3c 685 3 +4fc5 31 688 3 +4ff6 5d 689 3 +5053 26 692 3 +5079 21 694 3 +509a c 698 3 +50a6 e 699 3 +50b4 6 700 3 +50ba 9 701 3 +50c3 2e 702 3 +50f1 c 704 3 +50fd 3c 706 3 +5139 66 709 3 +519f 1c 712 3 +51bb fb 714 3 +52b6 6 717 3 +52bc 5 718 3 +52c1 19 704 3 +52da 25 714 3 +52ff 2e 722 3 +532d 9 723 3 +5336 17 698 3 +534d 8 725 3 +5355 1 725 3 +FUNC 5356 24a 0 -[DumpSymbols getSectionMapForArchitecture:] +5356 14 643 3 +536a 43 645 3 +53ad 1a 648 3 +53c7 1c 645 3 +53e3 18 648 3 +53fb 40 650 3 +543b 20 651 3 +545b 17 652 3 +5472 16 651 3 +5488 cb 652 3 +5553 11 654 3 +5564 32 657 3 +5596 a 658 3 +FUNC 55a0 3fe 0 -[DumpSymbols initWithContentsOfFile:] +55a0 14 1056 3 +55b4 3b 1057 3 +55ef 44 1059 3 +5633 17 1060 3 +564a c 1061 3 +5656 1f 1064 3 +5675 2b 1067 3 +56a0 a 1069 3 +56aa 35 1083 3 +56df 2 1087 3 +56e1 1a 1088 3 +56fb 3d 1087 3 +5738 33 1092 3 +576b 6 1094 3 +5771 e 1095 3 +577f 17 1096 3 +5796 c 1097 3 +57a2 1c 1101 3 +57be 1f 1103 3 +57dd 18 1104 3 +57f5 23 1107 3 +5818 25 1109 3 +583d 1c 1107 3 +5859 17 1110 3 +5870 c 1111 3 +587c 2a 1115 3 +58a6 8 1116 3 +58ae a 1118 3 +58b8 9 1119 3 +58c1 d 1122 3 +58ce 29 1124 3 +58f7 20 1126 3 +5917 20 1128 3 +5937 57 1132 3 +598e 9 1136 3 +5997 7 1137 3 +FUNC 599e d74 0 -[DumpSymbols outputSymbolFile:] +599e 18 877 3 +59b6 2e 879 3 +59e4 30 880 3 +5a14 5d 882 3 +5a71 30 883 3 +5aa1 5d 885 3 +5afe 2e 888 3 +5b2c 38 891 3 +5b64 46 892 3 +5baa 26 893 3 +5bd0 20 895 3 +5bf0 20 904 3 +5c10 30 898 3 +5c40 f 899 3 +5c4f 1e 904 3 +5c6d 17 907 3 +5c84 17 908 3 +5c9b 44 911 3 +5cdf 44 914 3 +5d23 a 917 3 +5d2d 36 921 3 +5d63 30 923 3 +5d93 9 18 4 +5d9c 9 19 4 +5da5 c 20 4 +5db1 56 923 3 +5e07 74 925 3 +5e7b f 927 3 +5e8a 44 932 3 +5ece 20 933 3 +5eee c 934 3 +5efa 4e 935 3 +5f48 41 936 3 +5f89 f 937 3 +5f98 14 934 3 +5fac 7 941 3 +5fb3 14 942 3 +5fc7 14 943 3 +5fdb 1d 946 3 +5ff8 c 948 3 +6004 24 949 3 +6028 29 950 3 +6051 9 953 3 +605a 28 954 3 +6082 2e 955 3 +60b0 1e 957 3 +60ce 7 959 3 +60d5 26 962 3 +60fb 2a 963 3 +6125 2a 964 3 +614f 6 966 3 +6155 2a 967 3 +617f e 971 3 +618d 43 972 3 +61d0 4c 974 3 +621c 8 975 3 +6224 2e 979 3 +6252 2e 982 3 +6280 2e 985 3 +62ae 2e 988 3 +62dc 2e 991 3 +630a 2e 994 3 +6338 2e 997 3 +6366 2e 1000 3 +6394 54 1004 3 +63e8 c 1005 3 +63f4 e 1007 3 +6402 27 1008 3 +6429 8 1009 3 +6431 34 1010 3 +6465 24 1012 3 +6489 2 1013 3 +648b 2a 1017 3 +64b5 a 1019 3 +64bf 14 1020 3 +64d3 1d 1021 3 +64f0 a 1025 3 +64fa 32 1026 3 +652c 33 1028 3 +655f c 1029 3 +656b 55 1034 3 +65c0 f 1036 3 +65cf 16 1040 3 +65e5 61 1041 3 +6646 f 1043 3 +6655 47 1046 3 +669c c 1048 3 +66a8 11 948 3 +66b9 4e 1052 3 +6707 b 1053 3 +FUNC 6712 11 0 operator new(unsigned long, void*) +6712 c 94 5 +671e 5 94 5 +6723 1 94 5 +FUNC 6724 e 0 operator delete(void*, void*) +6724 c 98 5 +6730 2 98 5 +673e 7 76 6 +6745 2 77 6 +6747 1a 78 6 +6761 d 77 6 +676e 3 79 6 +6771 2 80 6 +6773 1 80 6 +6780 d 95 6 +678d 1 95 6 +678e 13 127 74 +67a1 2a 127 74 +67cb 1 127 74 +67cc 13 127 74 +67df 2a 127 74 +6809 1 127 74 +680a 13 127 74 +681d 2a 127 74 +6847 1 127 74 +FUNC 6848 e 0 dwarf2reader::LineInfoHandler::DefineDir(std::string const&, unsigned int) +6848 c 131 7 +6854 2 131 74 +FUNC 6856 26 0 dwarf2reader::LineInfoHandler::DefineFile(std::string const&, int, unsigned int, unsigned long long, unsigned long long) +6856 24 142 7 +687a 2 142 74 +FUNC 687c 1a 0 dwarf2reader::LineInfoHandler::AddLine(unsigned long long, unsigned int, unsigned int, unsigned int) +687c 18 150 7 +6894 2 150 74 +6896 12 299 74 +68a8 12 299 74 +68ba 13 301 74 +68cd 2a 301 74 +68f7 1 301 74 +68f8 13 301 74 +690b 2a 301 74 +6935 1 301 74 +6936 13 301 74 +6949 2a 301 74 +6973 1 301 74 +FUNC 6974 44 0 dwarf2reader::Dwarf2Handler::StartCompilationUnit(unsigned long long, unsigned char, unsigned char, unsigned long long, unsigned char) +6974 39 308 7 +69ad b 308 74 +FUNC 69b8 1f 0 dwarf2reader::Dwarf2Handler::StartDIE(unsigned long long, dwarf2reader::DwarfTag, std::list<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm>, std::allocator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > > const&) +69b8 18 314 7 +69d0 7 314 74 +69d7 1 314 74 +FUNC 69d8 26 0 dwarf2reader::Dwarf2Handler::ProcessAttributeUnsigned(unsigned long long, dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm, unsigned long long) +69d8 24 323 7 +69fc 2 323 74 +FUNC 69fe 26 0 dwarf2reader::Dwarf2Handler::ProcessAttributeSigned(unsigned long long, dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm, long long) +69fe 24 332 7 +6a22 2 332 74 +FUNC 6a24 26 0 dwarf2reader::Dwarf2Handler::ProcessAttributeBuffer(unsigned long long, dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm, char const*, unsigned long long) +6a24 24 345 7 +6a48 2 345 74 +FUNC 6a4a 1a 0 dwarf2reader::Dwarf2Handler::ProcessAttributeString(unsigned long long, dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm, std::string const&) +6a4a 18 354 7 +6a62 2 354 74 +FUNC 6a64 1a 0 dwarf2reader::Dwarf2Handler::EndDIE(unsigned long long) +6a64 18 360 7 +6a7c 2 360 74 +6a7e c 44 8 +6a8a 2 44 8 +6a8c 13 55 32 +6a9f 35 55 32 +6ad4 13 91 32 +6ae7 73 96 32 +6b5a 13 98 32 +6b6d 35 98 32 +6bae 1a 75 3 +6bc8 2 76 3 +FUNC 6bca 20 0 std::_Rb_tree_const_iterator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >::operator!=(std::_Rb_tree_const_iterator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > const&) const +6bca c 287 10 +6bd6 14 288 40 +FUNC 6bea 16 0 std::_Rb_tree_const_iterator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >::operator->() const +6bea c 249 10 +6bf6 a 250 40 +6c0c 7 614 72 +6c13 1 614 72 +6c14 c 241 40 +6c20 c 242 40 +FUNC 6c2c 16 0 std::_Rb_tree_const_iterator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >::operator*() const +6c2c c 245 11 +6c38 a 246 40 +6c42 c 241 40 +6c4e c 242 40 +FUNC 6c5a 20 0 std::_Rb_tree_const_iterator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >::operator!=(std::_Rb_tree_const_iterator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > > const&) const +6c5a c 287 11 +6c66 14 288 40 +FUNC 6c7a 16 0 std::_Rb_tree_const_iterator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >::operator->() const +6c7a c 249 11 +6c86 a 250 40 +6c90 c 185 34 +6c9c 18 186 34 +6cc0 14 204 34 +6cd4 c 69 70 +6ce0 d 69 70 +6ced 1 69 70 +6cee c 89 70 +6cfa 20 90 70 +6d1a c 69 70 +6d26 d 69 70 +6d33 1 69 70 +6d34 c 69 70 +6d40 d 69 70 +6d4d 1 69 70 +FUNC 6d4e 25 0 std::_Rb_tree_const_iterator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >::operator++() +6d4e c 253 13 +6d5a 14 255 40 +6d6e 5 256 40 +6d73 1 256 40 +FUNC 6d74 25 0 std::_Rb_tree_const_iterator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >::operator++() +6d74 c 253 13 +6d80 14 255 40 +6d94 5 256 40 +6d99 1 256 40 +FUNC 6d9a 14 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, dwarf2reader::FunctionInfo*>, std::_Select1st<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >::_M_begin() +6d9a c 461 13 +6da6 8 462 40 +FUNC 6dae 14 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, std::pair<std::string, unsigned int> >, std::_Select1st<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > > >::_M_begin() +6dae c 461 13 +6dba 8 462 40 +6dc2 c 65 68 +6dce 2 65 68 +6dd0 c 72 68 +6ddc 2 72 68 +6dde c 97 69 +6dea d 97 69 +6df7 1 97 69 +6df8 c 105 69 +6e04 d 105 69 +6e11 1 105 69 +6e12 c 105 69 +6e1e d 105 69 +6e2b 1 105 69 +6e2c c 67 68 +6e38 2 67 68 +6e3a c 99 69 +6e46 14 100 69 +6e5a c 99 69 +6e66 14 100 69 +FUNC 6e7a 2b 0 std::_Vector_base<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> >::get_allocator() const +6e7a 10 93 16 +6e8a 1b 94 71 +6ea5 1 94 71 +6ea6 c 65 68 +6eb2 2 65 68 +6eb4 c 72 68 +6ec0 2 72 68 +6ec2 c 97 69 +6ece d 97 69 +6edb 1 97 69 +6edc c 105 69 +6ee8 d 105 69 +6ef5 1 105 69 +6ef6 c 105 69 +6f02 d 105 69 +6f0f 1 105 69 +6f10 c 67 68 +6f1c 2 67 68 +6f1e c 99 69 +6f2a 14 100 69 +6f3e c 99 69 +6f4a 14 100 69 +FUNC 6f5e 2b 0 std::_Vector_base<std::string, std::allocator<std::string> >::get_allocator() const +6f5e 10 93 16 +6f6e 1b 94 71 +6f89 1 94 71 +6f8a c 603 72 +6f96 c 603 72 +FUNC 6fa2 23 0 std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> >::begin() +6fa2 c 333 16 +6fae 17 334 71 +6fc5 1 334 71 +FUNC 6fc6 26 0 std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> >::end() +6fc6 c 351 16 +6fd2 1a 352 71 +6ff8 5 666 72 +6ffd 1 666 72 +6ffe c 608 72 +700a 14 609 72 +702a 5 666 72 +702f 1 666 72 +FUNC 7030 35 0 bool __gnu_cxx::operator!=<dwarf2reader::SourceFileInfo const*, dwarf2reader::SourceFileInfo*, std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> > >(__gnu_cxx::__normal_iterator<dwarf2reader::SourceFileInfo const*, std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> > > const&, __gnu_cxx::__normal_iterator<dwarf2reader::SourceFileInfo*, std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> > > const&) +7030 d 693 16 +703d 28 694 72 +7065 1 694 72 +7066 c 603 72 +7072 c 603 72 +708a 27 629 72 +70b1 1 629 72 +70b2 c 84 70 +70be 1f 85 70 +70dd 1 85 70 +FUNC 70de 32 0 std::pair<std::string, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> std::make_pair<std::string, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*>(std::string, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*) +70de 10 144 16 +70ee 22 145 70 +711c a 190 34 +7132 d 194 34 +713f 1 194 34 +7140 c 84 70 +714c 17 85 70 +7163 1 85 70 +FUNC 7164 2d 0 std::pair<char const*, unsigned long> std::make_pair<char const*, unsigned long>(char const*, unsigned long) +7164 c 144 16 +7170 21 145 70 +7191 1 145 70 +7192 c 84 70 +719e 1d 85 70 +71bb 1 85 70 +FUNC 71bc 30 0 std::pair<char*, std::pair<char const*, unsigned long> > std::make_pair<char*, std::pair<char const*, unsigned long> >(char*, std::pair<char const*, unsigned long>) +71bc 10 144 16 +71cc 20 145 70 +71ec c 89 70 +71f8 20 90 70 +7218 d 89 70 +7225 70 90 70 +7295 1 90 70 +FUNC 7296 12 0 std::iterator_traits<unsigned long const*>::iterator_category std::__iterator_category<unsigned long const*>(unsigned long const* const&) +7296 c 164 17 +72a2 6 165 17 +FUNC 72a8 1d 0 std::iterator_traits<unsigned long const*>::difference_type std::__distance<unsigned long const*>(unsigned long const*, unsigned long const*, std::random_access_iterator_tag) +72a8 c 92 18 +72b4 11 97 18 +72c5 1 97 18 +FUNC 72c6 33 0 std::iterator_traits<unsigned long const*>::difference_type std::distance<unsigned long const*>(unsigned long const*, unsigned long const*) +72c6 c 114 18 +72d2 27 118 18 +72f9 1 118 18 +FUNC 72fa 20 0 void std::__advance<unsigned long const*, int>(unsigned long const*&, int, std::random_access_iterator_tag) +72fa c 150 18 +7306 14 155 18 +FUNC 731a 33 0 void std::advance<unsigned long const*, int>(unsigned long const*&, int) +731a c 172 18 +7326 27 175 18 +734d 1 175 18 +FUNC 734e 7a 0 unsigned long const* std::lower_bound<unsigned long const*, unsigned long>(unsigned long const*, unsigned long const*, unsigned long const&) +734e c 2625 19 +735a 15 2642 19 +736f 2 2646 19 +7371 8 2648 19 +7379 6 2649 19 +737f 12 2650 19 +7391 e 2651 19 +739f 6 2653 19 +73a5 4 2654 19 +73a9 e 2655 19 +73b7 6 2658 19 +73bd 6 2646 19 +73c3 5 2660 19 +73db b 227 34 +73e6 e 228 34 +73f4 1c 229 34 +7410 20 230 34 +7430 6 231 34 +7436 c 72 68 +7442 2 72 68 +7444 c 105 69 +7450 d 105 69 +745d 1 105 69 +745e c 105 69 +746a d 105 69 +7477 1 105 69 +7478 c 80 71 +7484 d 80 71 +7491 1 80 71 +7492 c 67 68 +749e 2 67 68 +74a0 c 99 69 +74ac 14 100 69 +FUNC 74c0 2b 0 std::_Vector_base<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> >::get_allocator() const +74c0 10 93 19 +74d0 1b 94 71 +74eb 1 94 71 +74ec c 238 40 +74f8 a 239 40 +FUNC 7502 26 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, dwarf2reader::FunctionInfo*>, std::_Select1st<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >::begin() const +7502 c 585 19 +750e 1a 588 40 +FUNC 7528 19 0 std::map<unsigned long long, dwarf2reader::FunctionInfo*, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >::begin() const +7528 c 243 20 +7534 d 244 45 +7541 1 244 45 +FUNC 7542 26 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, dwarf2reader::FunctionInfo*>, std::_Select1st<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >::end() const +7542 c 596 20 +754e 1a 597 40 +FUNC 7568 19 0 std::map<unsigned long long, dwarf2reader::FunctionInfo*, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >::end() const +7568 c 260 20 +7574 d 261 45 +7581 1 261 45 +7582 c 65 68 +758e 2 65 68 +7590 c 72 68 +759c 2 72 68 +759e c 97 69 +75aa d 97 69 +75b7 1 97 69 +75b8 c 105 69 +75c4 d 105 69 +75d1 1 105 69 +75d2 c 72 68 +75de 2 72 68 +75e0 c 105 69 +75ec d 105 69 +75f9 1 105 69 +75fa c 397 40 +7606 d 397 40 +7613 1 397 40 +7614 c 105 69 +7620 d 105 69 +762d 1 105 69 +FUNC 762e 14 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, dwarf2reader::FunctionInfo*>, std::_Select1st<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >::_S_right(std::_Rb_tree_node_base*) +762e c 496 20 +763a 8 497 40 +FUNC 7642 14 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, dwarf2reader::FunctionInfo*>, std::_Select1st<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >::_S_left(std::_Rb_tree_node_base*) +7642 c 488 20 +764e 8 489 40 +7656 c 65 68 +7662 2 65 68 +7664 c 72 68 +7670 2 72 68 +7672 c 97 69 +767e d 97 69 +768b 1 97 69 +768c c 105 69 +7698 d 105 69 +76a5 1 105 69 +76a6 c 72 68 +76b2 2 72 68 +76b4 c 105 69 +76c0 d 105 69 +76cd 1 105 69 +76ce c 397 40 +76da d 397 40 +76e7 1 397 40 +76e8 c 105 69 +76f4 d 105 69 +7701 1 105 69 +FUNC 7702 14 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, std::pair<std::string, unsigned int> >, std::_Select1st<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > > >::_S_right(std::_Rb_tree_node_base*) +7702 c 496 20 +770e 8 497 40 +FUNC 7716 14 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, std::pair<std::string, unsigned int> >, std::_Select1st<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > > >::_S_left(std::_Rb_tree_node_base*) +7716 c 488 20 +7722 8 489 40 +772a c 84 71 +7736 2f 85 71 +7765 2 86 71 +7767 1 86 71 +7768 c 80 71 +7774 d 80 71 +7781 1 80 71 +7782 c 96 71 +778e 12 97 71 +77a0 2 98 71 +77a2 c 84 71 +77ae 2f 85 71 +77dd 2 86 71 +77df 1 86 71 +77e0 c 80 71 +77ec d 80 71 +77f9 1 80 71 +77fa c 96 71 +7806 12 97 71 +7818 2 98 71 +7826 d 107 68 +7833 1 107 68 +FUNC 7834 2e 0 void std::_Destroy<std::string*, std::allocator<std::string> >(std::string*, std::string*, std::allocator<std::string>) +7834 c 171 21 +7840 2 173 73 +7842 12 174 73 +7854 c 173 73 +7860 2 174 73 +7862 c 167 40 +786e a 168 40 +FUNC 7878 26 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, dwarf2reader::FunctionInfo*>, std::_Select1st<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >::begin() +7878 c 581 21 +7884 1a 582 40 +FUNC 789e 19 0 std::map<unsigned long long, dwarf2reader::FunctionInfo*, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >::begin() +789e c 234 21 +78aa d 235 45 +78b7 1 235 45 +FUNC 78b8 26 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, dwarf2reader::FunctionInfo*>, std::_Select1st<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >::end() +78b8 c 592 21 +78c4 1a 593 40 +FUNC 78de 19 0 std::map<unsigned long long, dwarf2reader::FunctionInfo*, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >::end() +78de c 251 21 +78ea d 252 45 +78f7 1 252 45 +78f8 c 167 40 +7904 a 168 40 +FUNC 790e 26 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, std::pair<std::string, unsigned int> >, std::_Select1st<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > > >::begin() +790e c 581 21 +791a 1a 582 40 +FUNC 7934 19 0 std::map<unsigned long long, std::pair<std::string, unsigned int>, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > > >::begin() +7934 c 234 21 +7940 d 235 45 +794d 1 235 45 +FUNC 794e 26 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, std::pair<std::string, unsigned int> >, std::_Select1st<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > > >::end() +794e c 592 21 +795a 1a 593 40 +FUNC 7974 19 0 std::map<unsigned long long, std::pair<std::string, unsigned int>, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > > >::end() +7974 c 251 21 +7980 d 252 45 +798d 1 252 45 +FUNC 798e 11 0 std::_Select1st<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >::operator()(std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*>&) const +798e c 546 22 +799a 5 547 41 +799f 1 547 41 +79a0 c 128 34 +79ac 13 129 34 +79bf 1 129 34 +79cc 22 396 34 +79fa d 199 42 +7a07 1 199 42 +7a08 c 65 68 +7a14 2 65 68 +7a16 c 72 68 +7a22 2 72 68 +7a24 c 97 69 +7a30 d 97 69 +7a3d 1 97 69 +7a3e c 105 69 +7a4a d 105 69 +7a57 1 105 69 +7a58 c 65 68 +7a64 2 65 68 +7a66 c 72 68 +7a72 2 72 68 +7a74 c 105 69 +7a80 d 105 69 +7a8d 1 105 69 +7a8e c 97 69 +7a9a d 97 69 +7aa7 1 97 69 +7aa8 c 72 68 +7ab4 2 72 68 +7ab6 c 105 69 +7ac2 d 105 69 +7acf 1 105 69 +7adc d 94 68 +7ae9 1 94 68 +FUNC 7aea 2f 0 std::_Vector_base<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> >::_M_deallocate(dwarf2reader::CompilationUnit::Abbrev*, unsigned long) +7aea c 120 23 +7af6 6 122 71 +7afc 1d 123 71 +7b19 1 123 71 +7b1a c 108 71 +7b26 43 109 71 +7b69 1 109 71 +7b6a c 65 68 +7b76 2 65 68 +7b78 c 103 69 +7b84 d 103 69 +7b91 1 103 69 +7b92 c 65 68 +7b9e 2 65 68 +7ba0 c 103 69 +7bac d 103 69 +7bb9 1 103 69 +7bc6 d 94 68 +7bd3 1 94 68 +FUNC 7bd4 2f 0 std::_Vector_base<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> >::_M_deallocate(dwarf2reader::SourceFileInfo*, unsigned long) +7bd4 c 120 23 +7be0 6 122 71 +7be6 1d 123 71 +7c03 1 123 71 +7c04 c 108 71 +7c10 43 109 71 +7c53 1 109 71 +7c54 c 188 71 +7c60 12 189 71 +7c72 2 190 71 +7c74 c 35 32 +7c80 d 35 32 +7c8d 1 35 32 +7c9a d 107 68 +7ca7 1 107 68 +FUNC 7ca8 2e 0 void std::_Destroy<dwarf2reader::SourceFileInfo*, std::allocator<dwarf2reader::SourceFileInfo> >(dwarf2reader::SourceFileInfo*, dwarf2reader::SourceFileInfo*, std::allocator<dwarf2reader::SourceFileInfo>) +7ca8 c 171 23 +7cb4 2 173 73 +7cb6 12 174 73 +7cc8 c 173 73 +7cd4 2 174 73 +7cd6 d 272 71 +7ce3 8c 273 71 +7d6f 1 273 71 +7d7c d 94 68 +7d89 1 94 68 +FUNC 7d8a 2f 0 std::_Vector_base<std::string, std::allocator<std::string> >::_M_deallocate(std::string*, unsigned long) +7d8a c 120 23 +7d96 6 122 71 +7d9c 1d 123 71 +7db9 1 123 71 +7dba c 108 71 +7dc6 3d 109 71 +7e03 1 109 71 +7e04 c 188 71 +7e10 12 189 71 +7e22 2 190 71 +7e24 d 272 71 +7e31 8c 273 71 +7ebd 1 273 71 +7eca 2b 596 34 +7ef5 1 596 34 +7f02 7 614 72 +7f09 1 614 72 +7f0a c 65 68 +7f16 2 65 68 +7f18 c 72 68 +7f24 2 72 68 +7f26 c 103 69 +7f32 d 103 69 +7f3f 1 103 69 +7f40 c 105 69 +7f4c d 105 69 +7f59 1 105 69 +7f5a c 65 68 +7f66 2 65 68 +7f68 c 72 68 +7f74 2 72 68 +7f76 c 103 69 +7f82 d 103 69 +7f8f 1 103 69 +7f90 c 105 69 +7f9c d 105 69 +7fa9 1 105 69 +7faa c 105 69 +7fb6 d 105 69 +7fc3 1 105 69 +7fd0 d 575 34 +7fdd 1 575 34 +7fea d 575 34 +7ff7 1 575 34 +FUNC 7ff8 11 0 std::_Select1st<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >::operator()(std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> const&) const +7ff8 c 550 23 +8004 5 551 41 +8009 1 551 41 +8016 2f 600 34 +8045 1 600 34 +8046 c 84 70 +8052 1e 85 70 +FUNC 8070 11 0 std::_Select1st<std::pair<std::string const, std::pair<char const*, unsigned long long> > >::operator()(std::pair<std::string const, std::pair<char const*, unsigned long long> >&) const +8070 c 546 23 +807c 5 547 41 +8081 1 547 41 +FUNC 8082 11 0 std::_Select1st<std::pair<std::string const, std::pair<char const*, unsigned long long> > >::operator()(std::pair<std::string const, std::pair<char const*, unsigned long long> > const&) const +8082 c 550 23 +808e 5 551 41 +8093 1 551 41 +8094 c 128 34 +80a0 13 129 34 +80b3 1 129 34 +80b4 c 84 70 +80c0 1e 85 70 +80de c 65 68 +80ea 2 65 68 +80ec c 103 69 +80f8 d 103 69 +8105 1 103 69 +8106 c 65 68 +8112 2 65 68 +8114 c 72 68 +8120 2 72 68 +8122 c 105 69 +812e d 105 69 +813b 1 105 69 +813c c 103 69 +8148 d 103 69 +8155 1 103 69 +8156 c 105 69 +8162 d 105 69 +816f 1 105 69 +8170 c 80 71 +817c d 80 71 +8189 1 80 71 +818a c 67 68 +8196 2 67 68 +8198 c 99 69 +81a4 14 100 69 +FUNC 81b8 2b 0 std::_Vector_base<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> >::get_allocator() const +81b8 10 93 23 +81c8 1b 94 71 +81e3 1 94 71 +81e4 c 99 69 +81f0 14 100 69 +8210 2 107 68 +FUNC 8212 2e 0 void std::_Destroy<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> >(__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, __gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*>) +8212 c 171 23 +821e 2 173 73 +8220 12 174 73 +8232 c 173 73 +823e 2 174 73 +824c d 107 68 +8259 1 107 68 +825a c 67 68 +8266 2 67 68 +8268 c 99 69 +8274 14 100 69 +8288 c 403 40 +8294 1c 404 40 +82b0 a 406 40 +82ba a 407 40 +82c4 c 408 40 +82d0 e 409 40 +82de c 553 40 +82ea 36 554 40 +8320 2 555 40 +8322 c 103 69 +832e d 103 69 +833b 1 103 69 +FUNC 833c 2b 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, dwarf2reader::FunctionInfo*>, std::_Select1st<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >::get_allocator() const +833c 10 350 23 +834c 1b 351 40 +8367 1 351 40 +8368 c 69 70 +8374 2 69 70 +8382 d 107 68 +838f 1 107 68 +839c d 94 68 +83a9 1 94 68 +FUNC 83aa 2a 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, dwarf2reader::FunctionInfo*>, std::_Select1st<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >::_M_put_node(std::_Rb_tree_node<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >*) +83aa c 359 23 +83b6 1e 360 40 +FUNC 83d4 59 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, dwarf2reader::FunctionInfo*>, std::_Select1st<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >::destroy_node(std::_Rb_tree_node<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >*) +83d4 d 387 23 +83e1 35 389 40 +8416 17 390 40 +842d 1 390 40 +FUNC 842e 56 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, dwarf2reader::FunctionInfo*>, std::_Select1st<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >::_M_erase(std::_Rb_tree_node<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >*) +842e c 1051 23 +843a 2 1054 40 +843c 1a 1056 40 +8456 e 1057 40 +8464 12 1058 40 +8476 6 1059 40 +847c 6 1054 40 +8482 2 1059 40 +8484 d 569 40 +8491 58 570 40 +84e9 1 570 40 +84ea c 147 45 +84f6 31 148 45 +8527 1 148 45 +8528 c 92 45 +8534 d 92 45 +8541 1 92 45 +8542 c 67 68 +854e 2 67 68 +8550 c 99 69 +855c 14 100 69 +8570 c 403 40 +857c 1c 404 40 +8598 a 406 40 +85a2 a 407 40 +85ac c 408 40 +85b8 e 409 40 +85c6 c 553 40 +85d2 36 554 40 +8608 2 555 40 +860a c 103 69 +8616 d 103 69 +8623 1 103 69 +FUNC 8624 2b 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, std::pair<std::string, unsigned int> >, std::_Select1st<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > > >::get_allocator() const +8624 10 350 23 +8634 1b 351 40 +864f 1 351 40 +8650 c 69 70 +865c d 69 70 +8669 1 69 70 +866a c 69 70 +8676 30 69 70 +86b2 d 107 68 +86bf 1 107 68 +86cc d 94 68 +86d9 1 94 68 +FUNC 86da 2a 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, std::pair<std::string, unsigned int> >, std::_Select1st<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > > >::_M_put_node(std::_Rb_tree_node<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >*) +86da c 359 23 +86e6 1e 360 40 +FUNC 8704 59 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, std::pair<std::string, unsigned int> >, std::_Select1st<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > > >::destroy_node(std::_Rb_tree_node<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >*) +8704 d 387 23 +8711 35 389 40 +8746 17 390 40 +875d 1 390 40 +FUNC 875e 56 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, std::pair<std::string, unsigned int> >, std::_Select1st<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > > >::_M_erase(std::_Rb_tree_node<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >*) +875e c 1051 23 +876a 2 1054 40 +876c 1a 1056 40 +8786 e 1057 40 +8794 12 1058 40 +87a6 6 1059 40 +87ac 6 1054 40 +87b2 2 1059 40 +87b4 d 569 40 +87c1 58 570 40 +8819 1 570 40 +881a c 147 45 +8826 31 148 45 +8857 1 148 45 +8858 c 92 45 +8864 d 92 45 +8871 1 92 45 +8872 c 603 72 +887e c 603 72 +FUNC 888a 23 0 std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> >::begin() +888a c 333 23 +8896 17 334 71 +88ad 1 334 71 +88ba 2a 654 72 +FUNC 88e4 42 0 std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> >::operator[](unsigned long) +88e4 c 494 23 +88f0 36 495 71 +FUNC 8926 26 0 std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> >::end() +8926 c 351 23 +8932 1a 352 71 +FUNC 894c 28 0 bool std::operator==<char, std::char_traits<char>, std::allocator<char> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) +894c c 2115 24 +8958 1c 2116 37 +FUNC 8974 23 0 std::equal_to<std::string>::operator()(std::string const&, std::string const&) const +8974 c 199 24 +8980 17 200 41 +8997 1 200 41 +8998 c 80 71 +89a4 d 80 71 +89b1 1 80 71 +89b2 c 67 68 +89be 2 67 68 +89c0 c 99 69 +89cc 14 100 69 +FUNC 89e0 2b 0 std::_Vector_base<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> >::get_allocator() const +89e0 10 93 24 +89f0 1b 94 71 +8a0b 1 94 71 +8a0c c 99 69 +8a18 14 100 69 +8a2c c 84 71 +8a38 2f 85 71 +8a67 2 86 71 +8a69 1 86 71 +8a6a c 96 71 +8a76 12 97 71 +8a88 2 98 71 +8a96 2 107 68 +FUNC 8a98 2e 0 void std::_Destroy<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> >(__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, __gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*>) +8a98 c 171 24 +8aa4 2 173 73 +8aa6 12 174 73 +8ab8 c 173 73 +8ac4 2 174 73 +FUNC 8ac6 13 0 std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> >::max_size() const +8ac6 c 407 24 +8ad2 7 408 71 +8ad9 1 408 71 +8ada c 603 72 +8ae6 c 603 72 +FUNC 8af2 26 0 std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> >::end() +8af2 c 351 24 +8afe 1a 352 71 +FUNC 8b18 23 0 std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> >::begin() +8b18 c 333 24 +8b24 17 334 71 +8b3b 1 334 71 +8b48 2a 654 72 +8b7e 7 614 72 +8b85 1 614 72 +FUNC 8b86 42 0 std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> >::operator[](unsigned long) +8b86 c 494 24 +8b92 36 495 71 +8bd4 d 107 68 +8be1 1 107 68 +FUNC 8be2 28 0 void std::swap<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**>(__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**&, __gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**&) +8be2 c 92 25 +8bee 8 97 61 +8bf6 a 98 61 +8c00 a 99 61 +FUNC 8c0a 50 0 std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> >::swap(std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> >&) +8c0a c 733 25 +8c16 12 735 71 +8c28 18 736 71 +8c40 1a 737 71 +8c66 2b 596 34 +8c91 1 596 34 +8c9e 2f 600 34 +8ccd 1 600 34 +FUNC 8cce 28 0 void std::swap<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**>(__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**&, __gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**&) +8cce c 92 25 +8cda 8 97 61 +8ce2 a 98 61 +8cec a 99 61 +FUNC 8cf6 50 0 std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> >::swap(std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> >&) +8cf6 c 733 25 +8d02 12 735 71 +8d14 18 736 71 +8d2c 1a 737 71 +8d46 c 84 71 +8d52 2f 85 71 +8d81 2 86 71 +8d83 1 86 71 +8d84 c 96 71 +8d90 12 97 71 +8da2 2 98 71 +FUNC 8da4 13 0 std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> >::max_size() const +8da4 c 407 25 +8db0 7 408 71 +8db7 1 408 71 +8dc4 d 94 68 +8dd1 1 94 68 +FUNC 8dd2 2f 0 std::_Vector_base<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> >::_M_deallocate(__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, unsigned long) +8dd2 c 120 25 +8dde 6 122 71 +8de4 1d 123 71 +8e01 1 123 71 +8e02 c 108 71 +8e0e 3d 109 71 +8e4b 1 109 71 +8e4c c 272 71 +8e58 4b 273 71 +8ea3 1 273 71 +8ea4 c 188 71 +8eb0 12 189 71 +8ec2 2 190 71 +8ec4 c 603 72 +8ed0 c 603 72 +FUNC 8edc 2b 0 std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> >::begin() const +8edc c 342 25 +8ee8 1f 343 71 +8f07 1 343 71 +FUNC 8f08 2c 0 std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> >::end() const +8f08 c 360 25 +8f14 20 361 71 +8f40 5 666 72 +8f45 1 666 72 +8f53 2b 759 72 +FUNC 8f7e 3c 0 std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> >::size() const +8f7e c 402 25 +8f8a 30 403 71 +8fc6 26 588 34 +8ff8 15 511 34 +900d 79 513 34 +9086 21 517 34 +90a7 1 517 34 +90b4 14 225 42 +90d4 26 592 34 +FUNC 90fa 49 0 std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> >::capacity() const +90fa c 449 25 +9106 3d 451 71 +9143 1 451 71 +9144 c 103 69 +9150 d 103 69 +915d 1 103 69 +916e 1b 286 34 +9189 1 286 34 +9196 d 94 68 +91a3 1 94 68 +91b0 1e 301 34 +91db 56 622 34 +9231 17 623 34 +9254 9 1080 34 +925d 1a 1082 34 +9277 2 1083 34 +9279 8 1085 34 +9281 12 1086 34 +9293 6 1087 34 +9299 6 1083 34 +929f 1b 1089 34 +92ba 1d 1080 34 +92d7 c 1091 34 +92e3 1 1091 34 +92e4 d 360 34 +92f1 77 361 34 +9368 c 93 42 +9374 d 93 42 +9381 1 93 42 +9382 c 72 68 +938e 2 72 68 +9390 c 105 69 +939c d 105 69 +93a9 1 105 69 +93aa c 301 66 +93b6 d 301 66 +93c3 1 301 66 +93d0 d 94 68 +93dd 1 94 68 +FUNC 93de 2f 0 std::_Vector_base<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> >::_M_deallocate(__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, unsigned long) +93de c 120 26 +93ea 6 122 71 +93f0 1d 123 71 +940d 1 123 71 +940e c 108 71 +941a 3d 109 71 +9457 1 109 71 +9458 c 188 71 +9464 12 189 71 +9476 2 190 71 +9478 c 272 71 +9484 4b 273 71 +94cf 1 273 71 +94d0 c 603 72 +94dc c 603 72 +FUNC 94e8 2b 0 std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> >::begin() const +94e8 c 342 26 +94f4 1f 343 71 +9513 1 343 71 +FUNC 9514 2c 0 std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> >::end() const +9514 c 360 26 +9520 20 361 71 +954c 2d 662 72 +9579 1 662 72 +FUNC 957a 2d 0 unsigned long const& std::max<unsigned long>(unsigned long const&, unsigned long const&) +957a c 206 26 +9586 e 211 61 +9594 8 212 61 +959c b 213 61 +95a7 1 213 61 +95b4 19 650 72 +95cd 1 650 72 +95da 5 666 72 +95df 1 666 72 +95ed 2b 759 72 +9624 5 666 72 +9629 1 666 72 +9637 2b 759 72 +FUNC 9662 49 0 std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> >::capacity() const +9662 c 449 26 +966e 3d 451 71 +96ab 1 451 71 +FUNC 96ac 3c 0 std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> >::size() const +96ac c 402 26 +96b8 30 403 71 +96f4 26 588 34 +9726 26 592 34 +974c c 103 69 +9758 d 103 69 +9765 1 103 69 +9776 1b 286 34 +9791 1 286 34 +979e d 94 68 +97ab 1 94 68 +97b8 1e 301 34 +97e3 56 622 34 +9839 17 623 34 +985c 9 1080 34 +9865 1a 1082 34 +987f 2 1083 34 +9881 8 1085 34 +9889 12 1086 34 +989b 6 1087 34 +98a1 6 1083 34 +98a7 1b 1089 34 +98c2 1d 1080 34 +98df c 1091 34 +98eb 1 1091 34 +98ec d 360 34 +98f9 77 361 34 +9970 c 69 70 +997c 20 69 70 +99a9 5c 104 68 +9a05 1 104 68 +9a06 c 69 70 +9a12 2c 69 70 +9a4b 5c 104 68 +9aa7 1 104 68 +9ab4 2d 662 72 +9ae1 1 662 72 +9aee 19 650 72 +9b07 1 650 72 +9b14 5 666 72 +9b19 1 666 72 +9b27 2b 759 72 +9b52 c 72 68 +9b5e 2 72 68 +9b60 c 105 69 +9b6c d 105 69 +9b79 1 105 69 +9b7a c 69 70 +9b86 2 69 70 +9b94 d 107 68 +9ba1 1 107 68 +9bae d 94 68 +9bbb 1 94 68 +FUNC 9bbc 2a 0 std::_List_base<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm>, std::allocator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > >::_M_put_node(std::_List_node<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> >*) +9bbc c 315 26 +9bc8 1e 316 66 +FUNC 9be6 35 0 bool __gnu_cxx::operator!=<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> > >(__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> > > const&, __gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> > > const&) +9be6 d 699 26 +9bf3 28 700 72 +9c1b 1 700 72 +9c28 d 623 72 +9c35 5 624 72 +FUNC 9c3a 4b 0 void std::__fill<true>::fill<__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> > >, __gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*>(__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> > >, __gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> > >, __gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >* const&) +9c3a c 539 61 +9c46 8 541 61 +9c4e 2 542 61 +9c50 12 543 61 +9c62 21 542 61 +9c83 2 543 61 +9c85 1 543 61 +FUNC 9c86 2b 0 void std::fill<__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> > >, __gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*>(__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> > >, __gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> > >, __gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >* const&) +9c86 c 560 26 +9c92 4 567 61 +9c96 1b 568 61 +9cb1 1 568 61 +FUNC 9cb2 4b 0 void std::_Destroy<__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> > >, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> >(__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> > >, __gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> > >, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*>) +9cb2 c 171 26 +9cbe 2 173 73 +9cc0 1a 174 73 +9cda 21 173 73 +9cfb 2 174 73 +9cfd 1 174 73 +9d0a 7 98 68 +9d11 1 98 68 +9d1e 1d 85 68 +9d3b 5 86 68 +9d40 16 88 68 +9d62 1d 297 34 +9d7f 1 297 34 +9d8d e 605 34 +9d9b 9 606 34 +9da4 3c 609 34 +9de0 b 610 34 +9deb 11 609 34 +9dfc b 612 34 +9e07 12 614 34 +9e19 b 615 34 +9e24 13 612 34 +9e37 8 615 34 +9e3f 1 615 34 +9e4d 15 751 34 +9e62 1a 752 34 +9e7c b 754 34 +9e87 49 755 34 +9ed0 3b 756 34 +9f0b 12 754 34 +9f1d 15 758 34 +9f32 8 759 34 +9f3a 1c 760 34 +9f56 f 761 34 +9f65 41 762 34 +9fb2 7 98 68 +9fb9 1 98 68 +9fc6 1d 85 68 +9fe3 5 86 68 +9fe8 17 88 68 +9fff 1 88 68 +a00c 1d 297 34 +a029 1 297 34 +a037 e 605 34 +a045 9 606 34 +a04e 3c 609 34 +a08a b 610 34 +a095 11 609 34 +a0a6 b 612 34 +a0b1 12 614 34 +a0c3 b 615 34 +a0ce 13 612 34 +a0e1 8 615 34 +a0e9 1 615 34 +a0f7 15 751 34 +a10c 1a 752 34 +a126 b 754 34 +a131 49 755 34 +a17a 3b 756 34 +a1b5 12 754 34 +a1c7 15 758 34 +a1dc 8 759 34 +a1e4 1c 760 34 +a200 f 761 34 +a20f 41 762 34 +FUNC a250 35 0 bool __gnu_cxx::operator!=<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> > >(__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> > > const&, __gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> > > const&) +a250 d 699 26 +a25d 28 700 72 +a285 1 700 72 +a292 d 623 72 +a29f 5 624 72 +FUNC a2a4 4b 0 void std::__fill<true>::fill<__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> > >, __gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*>(__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> > >, __gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> > >, __gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >* const&) +a2a4 c 539 61 +a2b0 8 541 61 +a2b8 2 542 61 +a2ba 12 543 61 +a2cc 21 542 61 +a2ed 2 543 61 +a2ef 1 543 61 +FUNC a2f0 2b 0 void std::fill<__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> > >, __gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*>(__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> > >, __gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> > >, __gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >* const&) +a2f0 c 560 26 +a2fc 4 567 61 +a300 1b 568 61 +a31b 1 568 61 +FUNC a31c 4b 0 void std::_Destroy<__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> > >, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> >(__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> > >, __gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> > >, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*>) +a31c c 171 26 +a328 2 173 73 +a32a 1a 174 73 +a344 21 173 73 +a365 2 174 73 +a367 1 174 73 +a368 c 65 68 +a374 2 65 68 +a376 c 103 69 +a382 d 103 69 +a38f 1 103 69 +FUNC a390 2b 0 std::_List_base<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm>, std::allocator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > >::get_allocator() const +a390 10 322 26 +a3a0 1b 324 66 +a3bb 1 324 66 +FUNC a3bc 7b 0 std::_List_base<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm>, std::allocator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > >::_M_clear() +a3bc d 69 27 +a3c9 8 72 77 +a3d1 2 73 77 +a3d3 6 75 77 +a3d9 8 76 77 +a3e1 35 77 77 +a416 12 78 77 +a428 a 73 77 +a432 5 78 77 +a437 1 78 77 +a438 c 331 66 +a444 18 332 66 +a45c c 392 66 +a468 d 392 66 +a475 1 392 66 +a476 c 211 74 +a482 10 211 74 +a49e d 107 68 +a4ab 1 107 68 +FUNC a4ac 2e 0 void std::_Destroy<dwarf2reader::CompilationUnit::Abbrev*, std::allocator<dwarf2reader::CompilationUnit::Abbrev> >(dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev*, std::allocator<dwarf2reader::CompilationUnit::Abbrev>) +a4ac c 171 27 +a4b8 2 173 73 +a4ba 12 174 73 +a4cc c 173 73 +a4d8 2 174 73 +a4da c 272 71 +a4e6 4b 273 71 +a531 1 273 71 +a532 13 196 74 +a545 10 196 74 +a555 2f 197 74 +a584 1a 198 74 +a59e 13 196 74 +a5b1 10 196 74 +a5c1 2f 197 74 +a5f0 1a 198 74 +a616 7 98 68 +a61d 1 98 68 +a62a 1d 85 68 +a647 5 86 68 +a64c 10 88 68 +FUNC a65c 2a 0 std::_Vector_base<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> >::_M_allocate(unsigned long) +a65c c 116 27 +a668 1e 117 71 +a686 d 100 71 +a693 12 101 71 +a6a5 19 103 71 +a6be b 104 71 +a6c9 3a 105 71 +a703 1 105 71 +a710 7 98 68 +a717 1 98 68 +a724 1d 85 68 +a741 5 86 68 +a746 10 88 68 +FUNC a756 2a 0 std::_Vector_base<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> >::_M_allocate(unsigned long) +a756 c 116 27 +a762 1e 117 71 +a780 d 100 71 +a78d 12 101 71 +a79f 19 103 71 +a7b8 b 104 71 +a7c3 3a 105 71 +a7fd 1 105 71 +a80b 12 424 61 +a81d 2e 425 61 +a84b 13 426 61 +a86a 4 440 61 +a86e 1b 443 61 +a889 1 443 61 +a898 56 482 61 +a8fa 4 514 61 +a8fe 4 515 61 +a902 1b 517 61 +a91d 1 517 61 +a92a 8 616 61 +a932 2 617 61 +a934 8 618 61 +a93c f 617 61 +a94b 5 619 61 +a95c 4 641 61 +a960 1b 642 61 +a97b 1 642 61 +FUNC a97c 27 0 void std::__uninitialized_fill_n_aux<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*>(__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >* const&, __true_type) +a97c c 182 28 +a988 1b 183 79 +a9a3 1 183 79 +FUNC a9a4 2f 0 void std::uninitialized_fill_n<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*>(__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >* const&) +a9a4 c 214 28 +a9b0 23 218 79 +a9d3 1 218 79 +FUNC a9d4 27 0 void std::__uninitialized_fill_n_a<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, __gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*>(__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >* const&, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*>) +a9d4 c 308 28 +a9e0 1b 310 79 +a9fb 1 310 79 +a9fc c 200 71 +aa08 19 201 71 +aa21 42 203 71 +aa63 15 205 71 +aa85 11 992 34 +aa96 c 993 34 +aaa2 15 995 34 +aab7 c 996 34 +aac3 4a 998 34 +ab0d f 1001 34 +ab1c 1c 998 34 +ab38 1a 1003 34 +ab52 5 1004 34 +ab57 1f 1007 34 +ab76 1c 1008 34 +ab92 19 1009 34 +abab 19 1010 34 +abc4 1a 1011 34 +abde a 1004 34 +abe8 11 1001 34 +abf9 15 1014 34 +ac0e 13 1028 34 +ac21 b 1016 34 +ac2c 9 1018 34 +ac35 19 1023 34 +ac4e 23 1024 34 +ac71 19 1025 34 +ac8a 1d 1021 34 +aca7 1a 1018 34 +acc1 b 1028 34 +accc b 1016 34 +acd7 1e 1028 34 +acf5 1 1028 34 +ad06 16 438 34 +ad1c 37 439 34 +ad53 1 439 34 +ad64 37 212 42 +ad9b 1 212 42 +ada8 8 616 61 +adb0 2 617 61 +adb2 8 618 61 +adba f 617 61 +adc9 5 619 61 +adda 4 641 61 +adde 1b 642 61 +adf9 1 642 61 +FUNC adfa 27 0 void std::__uninitialized_fill_n_aux<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*>(__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >* const&, __true_type) +adfa c 182 28 +ae06 1b 183 79 +ae21 1 183 79 +FUNC ae22 2f 0 void std::uninitialized_fill_n<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*>(__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >* const&) +ae22 c 214 28 +ae2e 23 218 79 +ae51 1 218 79 +FUNC ae52 27 0 void std::__uninitialized_fill_n_a<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, __gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*>(__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >* const&, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*>) +ae52 c 308 28 +ae5e 1b 310 79 +ae79 1 310 79 +ae7a c 200 71 +ae86 19 201 71 +ae9f 42 203 71 +aee1 15 205 71 +af03 11 992 34 +af14 c 993 34 +af20 15 995 34 +af35 c 996 34 +af41 4a 998 34 +af8b f 1001 34 +af9a 1c 998 34 +afb6 1a 1003 34 +afd0 5 1004 34 +afd5 1f 1007 34 +aff4 1c 1008 34 +b010 19 1009 34 +b029 19 1010 34 +b042 1a 1011 34 +b05c a 1004 34 +b066 11 1001 34 +b077 15 1014 34 +b08c 13 1028 34 +b09f b 1016 34 +b0aa 9 1018 34 +b0b3 19 1023 34 +b0cc 23 1024 34 +b0ef 19 1025 34 +b108 1d 1021 34 +b125 1a 1018 34 +b13f b 1028 34 +b14a b 1016 34 +b155 1e 1028 34 +b173 1 1028 34 +b184 16 438 34 +b19a 37 439 34 +b1d1 1 439 34 +b1e2 37 212 42 +b219 1 212 42 +b227 12 424 61 +b239 2e 425 61 +b267 13 426 61 +b286 4 440 61 +b28a 1b 443 61 +b2a5 1 443 61 +b2b4 56 482 61 +b316 4 514 61 +b31a 4 515 61 +b31e 1b 517 61 +b339 1 517 61 +b346 8 616 61 +b34e 2 617 61 +b350 12 618 61 +b362 16 617 61 +b378 5 619 61 +b37d 1 619 61 +b38a 4 641 61 +b38e 1b 642 61 +b3a9 1 642 61 +FUNC b3aa 27 0 void std::__uninitialized_fill_n_aux<__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> > >, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*>(__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> > >, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >* const&, __true_type) +b3aa c 182 28 +b3b6 1b 183 79 +b3d1 1 183 79 +FUNC b3d2 2f 0 void std::uninitialized_fill_n<__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> > >, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*>(__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> > >, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >* const&) +b3d2 c 214 28 +b3de 23 218 79 +b401 1 218 79 +FUNC b402 27 0 void std::__uninitialized_fill_n_a<__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> > >, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, __gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*>(__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> > >, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >* const&, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*>) +b402 c 308 28 +b40e 1b 310 79 +b429 1 310 79 +b436 8 616 61 +b43e 2 617 61 +b440 12 618 61 +b452 16 617 61 +b468 5 619 61 +b46d 1 619 61 +b47a 4 641 61 +b47e 1b 642 61 +b499 1 642 61 +FUNC b49a 27 0 void std::__uninitialized_fill_n_aux<__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> > >, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*>(__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> > >, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >* const&, __true_type) +b49a c 182 28 +b4a6 1b 183 79 +b4c1 1 183 79 +FUNC b4c2 2f 0 void std::uninitialized_fill_n<__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> > >, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*>(__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> > >, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >* const&) +b4c2 c 214 28 +b4ce 23 218 79 +b4f1 1 218 79 +FUNC b4f2 27 0 void std::__uninitialized_fill_n_a<__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> > >, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, __gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*>(__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> > >, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >* const&, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*>) +b4f2 c 308 28 +b4fe 1b 310 79 +b519 1 310 79 +b526 22 300 61 +b548 11 301 61 +b559 1 301 61 +b566 4 315 61 +b56a 1b 317 61 +b585 1 317 61 +b592 1b 326 61 +b5ad 1 326 61 +b5ba 4 384 61 +b5be 4 385 61 +b5c2 1b 387 61 +b5dd 1 387 61 +b5ea 1b 74 79 +b605 1 74 79 +b612 23 113 79 +b635 1 113 79 +b642 1b 254 79 +b65d 1 254 79 +b66a 15 763 71 +b67f 40 766 71 +b6bf 3 768 71 +b6c2 2 773 71 +FUNC b6c4 124 0 std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> >::reserve(unsigned long) +b6c4 13 69 29 +b6d7 15 71 78 +b6ec e 72 78 +b6fa 19 73 78 +b713 e 75 78 +b721 28 78 78 +b749 3e 79 78 +b787 30 81 78 +b7b7 8 84 78 +b7bf 11 85 78 +b7d0 18 86 78 +b7f5 33 335 61 +b834 4 384 61 +b838 4 385 61 +b83c 1b 387 61 +b857 1 387 61 +b864 1b 74 79 +b87f 1 74 79 +b88c 23 113 79 +b8af 1 113 79 +b8bc 1b 254 79 +b8d7 1 254 79 +b8e6 56 354 61 +b948 4 384 61 +b94c 4 385 61 +b950 1b 387 61 +b96b 1 387 61 +b978 1b 74 79 +b993 1 74 79 +b9a0 23 113 79 +b9c3 1 113 79 +b9d0 1b 254 79 +b9eb 1 254 79 +FUNC b9ec 46e 0 std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> >::_M_fill_insert(__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> > >, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >* const&) +b9ec 14 311 29 +ba00 b 313 78 +ba0b 24 315 78 +ba2f 8 318 78 +ba37 23 319 78 +ba5a 15 320 78 +ba6f c 321 78 +ba7b 51 323 78 +bacc 14 327 78 +bae0 30 328 78 +bb10 35 330 78 +bb45 48 334 78 +bb8d 17 338 78 +bba4 43 339 78 +bbe7 14 342 78 +bbfb 1e 343 78 +bc19 e 348 78 +bc27 1e 349 78 +bc45 e 350 78 +bc53 1d 353 78 +bc70 8 354 78 +bc78 e 355 78 +bc86 27 357 78 +bcad 6 358 78 +bcb3 4d 361 78 +bd00 40 365 78 +bd40 18 367 78 +bd58 4d 368 78 +bda5 3e 379 78 +bde3 30 381 78 +be13 12 384 78 +be25 13 385 78 +be38 22 386 78 +FUNC be5a 2e 0 std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> >::insert(__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> > >, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >* const&) +be5a c 657 29 +be66 22 658 71 +be94 15 580 34 +bea9 15 581 34 +bebe 37 582 34 +bef5 c 583 34 +bf01 1 583 34 +bf02 d 335 34 +bf0f 4e 337 34 +bf5d 4d 338 34 +bfaa d 134 42 +bfb7 65 135 42 +c028 22 300 61 +c04a 11 301 61 +c05b 1 301 61 +c068 4 315 61 +c06c 1b 317 61 +c087 1 317 61 +c094 1b 326 61 +c0af 1 326 61 +c0bc 4 384 61 +c0c0 4 385 61 +c0c4 1b 387 61 +c0df 1 387 61 +c0ec 1b 74 79 +c107 1 74 79 +c114 23 113 79 +c137 1 113 79 +c144 1b 254 79 +c15f 1 254 79 +c16c 15 763 71 +c181 40 766 71 +c1c1 3 768 71 +c1c4 2 773 71 +FUNC c1c6 124 0 std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> >::reserve(unsigned long) +c1c6 13 69 29 +c1d9 15 71 78 +c1ee e 72 78 +c1fc 19 73 78 +c215 e 75 78 +c223 28 78 78 +c24b 3e 79 78 +c289 30 81 78 +c2b9 8 84 78 +c2c1 11 85 78 +c2d2 18 86 78 +c2f7 33 335 61 +c336 4 384 61 +c33a 4 385 61 +c33e 1b 387 61 +c359 1 387 61 +c366 1b 74 79 +c381 1 74 79 +c38e 23 113 79 +c3b1 1 113 79 +c3be 1b 254 79 +c3d9 1 254 79 +c3e8 56 354 61 +c44a 4 384 61 +c44e 4 385 61 +c452 1b 387 61 +c46d 1 387 61 +c47a 1b 74 79 +c495 1 74 79 +c4a2 23 113 79 +c4c5 1 113 79 +c4d2 1b 254 79 +c4ed 1 254 79 +FUNC c4ee 46e 0 std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> >::_M_fill_insert(__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> > >, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >* const&) +c4ee 14 311 29 +c502 b 313 78 +c50d 24 315 78 +c531 8 318 78 +c539 23 319 78 +c55c 15 320 78 +c571 c 321 78 +c57d 51 323 78 +c5ce 14 327 78 +c5e2 30 328 78 +c612 35 330 78 +c647 48 334 78 +c68f 17 338 78 +c6a6 43 339 78 +c6e9 14 342 78 +c6fd 1e 343 78 +c71b e 348 78 +c729 1e 349 78 +c747 e 350 78 +c755 1d 353 78 +c772 8 354 78 +c77a e 355 78 +c788 27 357 78 +c7af 6 358 78 +c7b5 4d 361 78 +c802 40 365 78 +c842 18 367 78 +c85a 4d 368 78 +c8a7 3e 379 78 +c8e5 30 381 78 +c915 12 384 78 +c927 13 385 78 +c93a 22 386 78 +FUNC c95c 2e 0 std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> >::insert(__gnu_cxx::__normal_iterator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >**, std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >*> > >, unsigned long, __gnu_cxx::_Hashtable_node<std::pair<std::string const, __gnu_cxx::hash_map<std::string, std::pair<char const*, unsigned long long>, __gnu_cxx::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<char const*, unsigned long long> > >*> >* const&) +c95c c 657 29 +c968 22 658 71 +c996 15 580 34 +c9ab 15 581 34 +c9c0 37 582 34 +c9f7 c 583 34 +ca03 1 583 34 +ca04 d 335 34 +ca11 4e 337 34 +ca5f 4d 338 34 +caac d 134 42 +cab9 65 135 42 +FUNC cb1e 44 0 dwarf2reader::CUFunctionInfoHandler::StartCompilationUnit(unsigned long long, unsigned char, unsigned char, unsigned long long, unsigned char) +cb1e 39 135 42 +cb57 5 102 30 +cb5c 6 103 30 +FUNC cb62 41 0 dwarf2reader::CUFunctionInfoHandler::ProcessAttributeString(unsigned long long, dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm, std::string const&) +cb62 18 136 30 +cb7a 10 137 30 +cb8a 17 138 30 +cba1 2 139 30 +cba3 1 139 30 +FUNC cba4 2a5 0 dwarf2reader::CUFunctionInfoHandler::ProcessAttributeUnsigned(unsigned long long, dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm, unsigned long long) +cba4 2d 144 30 +cbd1 a 145 30 +cbdb 58 146 30 +cc33 35 147 30 +cc68 32 146 30 +cc9a 2a 147 30 +ccc4 82 152 30 +cd46 18 153 30 +cd5e 1c 152 30 +cd7a 2f 153 30 +cda9 e 154 30 +cdb7 28 155 30 +cddf 12 157 30 +cdf1 2 158 30 +cdf3 12 160 30 +ce05 2 161 30 +ce07 c 163 30 +ce13 2 164 30 +ce15 2c 166 30 +ce41 8 172 30 +ce49 1 172 30 +FUNC ce4a 19c 0 dwarf2reader::CULineInfoHandler::AddLine(unsigned long long, unsigned int, unsigned int, unsigned int) +ce4a 20 84 30 +ce6a 1c 85 30 +ce86 9c 87 30 +cf22 4f 89 30 +cf71 19 87 30 +cf8a 25 90 30 +cfaf 30 93 30 +cfdf 7 95 30 +FUNC cfe6 9f 0 dwarf2reader::CUFunctionInfoHandler::EndDIE(unsigned long long) +cfe6 19 174 30 +cfff 1c 175 30 +d01b 65 177 30 +d080 5 178 30 +d085 1 178 30 +FUNC d086 164 0 dwarf2reader::CUFunctionInfoHandler::StartDIE(unsigned long long, dwarf2reader::DwarfTag, std::list<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm>, std::allocator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > > const&) +d086 20 111 30 +d0a6 1c 112 30 +d0c2 c 126 30 +d0ce 23 115 30 +d0f1 26 116 30 +d117 1a 117 30 +d131 d 118 30 +d13e 1b 119 30 +d159 5f 120 30 +d1b8 c 124 30 +d1c4 1c 115 30 +d1e0 3 126 30 +d1e3 7 129 30 +FUNC d1ea 73 0 dwarf2reader::CULineInfoHandler::DefineDir(std::string const&, unsigned int) +d1ea 13 52 30 +d1fd 45 54 30 +d242 15 55 30 +d257 6 56 30 +d25d 1 56 30 +FUNC d25e 23b 0 dwarf2reader::CULineInfoHandler::DefineFile(std::string const&, int, unsigned int, unsigned long long, unsigned long long) +d25e 2c 60 30 +d28a 45 62 30 +d2cf 2f 65 30 +d2fe 24 66 30 +d322 b 68 30 +d32d e 69 30 +d33b 19 71 30 +d354 17 72 30 +d36b 93 74 30 +d3fe 64 77 30 +d462 30 79 30 +d492 7 81 30 +d499 1 81 30 +d49a 14 38 30 +d4ae 36 40 30 +d4e4 41 43 30 +d525 41 44 30 +d566 67 45 30 +d5cd 10 46 30 +d5dd 13 45 30 +d5f0 15 47 30 +d605 e 48 30 +d613 3d 49 30 +d650 20 50 30 +d670 14 38 30 +d684 36 40 30 +d6ba 41 43 30 +d6fb 41 44 30 +d73c 67 45 30 +d7a3 10 46 30 +d7b3 13 45 30 +d7c6 15 47 30 +d7db e 48 30 +d7e9 3d 49 30 +d826 20 50 30 +d846 12 125 74 +d858 12 125 74 +d86a 13 55 32 +d87d 35 55 32 +d8b2 13 98 32 +d8c5 35 98 32 +d8fa c 35 32 +d906 d 35 32 +d913 1 35 32 +d914 d 22 32 +d921 40 22 32 +d961 1 22 32 +d962 c 89 70 +d96e 1e 90 70 +d998 14 208 34 +d9ac c 190 67 +d9b8 a 190 67 +d9c2 c 259 67 +d9ce 21 259 67 +d9ef 1 259 67 +FUNC d9f0 13 0 std::auto_ptr<dwarf2reader::LineInfo>::operator->() const +d9f0 c 283 35 +d9fc 7 286 67 +da03 1 286 67 +da11 5c 104 68 +da6d 1 104 68 +FUNC da6e 28 0 bool std::operator==<char, std::char_traits<char>, std::allocator<char> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, char const*) +da6e c 2139 37 +da7a 1c 2140 37 +FUNC da96 5d 0 std::basic_string<char, std::char_traits<char>, std::allocator<char> > std::operator+<char, std::char_traits<char>, std::allocator<char> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, char const*) +da96 d 2081 37 +daa3 12 2083 37 +dab5 1a 2084 37 +dacf 24 2085 37 +daf3 1 2085 37 +FUNC daf4 5d 0 std::basic_string<char, std::char_traits<char>, std::allocator<char> > std::operator+<char, std::char_traits<char>, std::allocator<char> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) +daf4 d 2044 37 +db01 12 2046 37 +db13 1a 2047 37 +db2d 24 2048 37 +db51 1 2048 37 +db52 c 84 70 +db5e 17 85 70 +db75 1 85 70 +FUNC db76 2d 0 std::pair<char const*, unsigned int> std::make_pair<char const*, unsigned int>(char const*, unsigned int) +db76 c 144 37 +db82 21 145 70 +dba3 1 145 70 +dba4 c 84 70 +dbb0 23 85 70 +dbd3 1 85 70 +FUNC dbd4 3c 0 std::pair<unsigned long long, std::pair<char const*, unsigned int> > std::make_pair<unsigned long long, std::pair<char const*, unsigned int> >(unsigned long long, std::pair<char const*, unsigned int>) +dbd4 1c 144 37 +dbf0 20 145 70 +dc10 d 89 70 +dc1d 64 90 70 +dc81 1 90 70 +dc82 c 89 70 +dc8e 2a 90 70 +dcb8 c 84 70 +dcc4 1d 85 70 +dce1 1 85 70 +FUNC dce2 3c 0 std::pair<unsigned long long, dwarf2reader::FunctionInfo*> std::make_pair<unsigned long long, dwarf2reader::FunctionInfo*>(unsigned long long, dwarf2reader::FunctionInfo*) +dce2 1c 144 37 +dcfe 20 145 70 +dd2a a 190 34 +dd40 d 194 34 +dd4d 1 194 34 +dd4e c 603 72 +dd5a c 603 72 +FUNC dd66 2b 0 std::vector<std::string, std::allocator<std::string> >::begin() const +dd66 c 342 39 +dd72 1f 343 71 +dd91 1 343 71 +FUNC dd92 2c 0 std::vector<std::string, std::allocator<std::string> >::end() const +dd92 c 360 39 +dd9e 20 361 71 +ddca 5 666 72 +ddcf 1 666 72 +dddd 2b 759 72 +FUNC de08 3c 0 std::vector<std::string, std::allocator<std::string> >::size() const +de08 c 402 39 +de14 30 403 71 +FUNC de44 2b 0 std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> >::begin() const +de44 c 342 39 +de50 1f 343 71 +de6f 1 343 71 +FUNC de70 2c 0 std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> >::end() const +de70 c 360 39 +de7c 20 361 71 +dea9 31 759 72 +FUNC deda 3c 0 std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> >::size() const +deda c 402 39 +dee6 30 403 71 +df16 c 603 72 +df22 c 603 72 +FUNC df2e 26 0 std::vector<std::string, std::allocator<std::string> >::end() +df2e c 351 39 +df3a 1a 352 71 +df60 7 614 72 +df67 1 614 72 +FUNC df68 13 0 std::vector<std::string, std::allocator<std::string> >::max_size() const +df68 c 407 39 +df74 7 408 71 +df7b 1 408 71 +df88 5 666 72 +df8d 1 666 72 +df9a d 623 72 +dfa7 5 624 72 +FUNC dfac 23 0 std::vector<std::string, std::allocator<std::string> >::begin() +dfac c 333 39 +dfb8 17 334 71 +dfcf 1 334 71 +dfd0 c 35 32 +dfdc 26 35 32 +e00f 5c 104 68 +e06b 1 104 68 +e078 7 614 72 +e07f 1 614 72 +FUNC e080 35 0 dwarf2reader::SourceFileInfo::operator=(dwarf2reader::SourceFileInfo const&) +e080 c 35 39 +e08c 29 35 32 +e0b5 1 35 32 +FUNC e0b6 13 0 std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> >::max_size() const +e0b6 c 407 39 +e0c2 7 408 71 +e0c9 1 408 71 +e0d6 d 623 72 +e0e3 5 624 72 +FUNC e0e8 3c 0 std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> >::_M_range_check(unsigned long) const +e0e8 13 515 39 +e0fb 15 517 71 +e110 14 518 71 +FUNC e124 3c 0 std::vector<std::string, std::allocator<std::string> >::_M_range_check(unsigned long) const +e124 13 515 39 +e137 15 517 71 +e14c 14 518 71 +e16c 2a 654 72 +FUNC e196 42 0 std::vector<std::string, std::allocator<std::string> >::operator[](unsigned long) +e196 c 494 39 +e1a2 36 495 71 +FUNC e1d8 32 0 std::vector<std::string, std::allocator<std::string> >::at(unsigned long) +e1d8 c 534 39 +e1e4 12 536 71 +e1f6 14 537 71 +e216 32 654 72 +FUNC e248 42 0 std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> >::operator[](unsigned long) +e248 c 494 39 +e254 36 495 71 +FUNC e28a 32 0 std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> >::at(unsigned long) +e28a c 534 39 +e296 12 536 71 +e2a8 14 537 71 +FUNC e2bc 14 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, std::pair<std::string, unsigned int> >, std::_Select1st<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > > >::_M_end() +e2bc c 472 40 +e2c8 8 473 40 +FUNC e2d0 11 0 std::_Select1st<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >::operator()(std::pair<unsigned long long const, std::pair<std::string, unsigned int> > const&) const +e2d0 c 550 41 +e2dc 5 551 41 +e2e1 1 551 41 +FUNC e2e2 53 0 std::less<unsigned long long>::operator()(unsigned long long const&, unsigned long long const&) const +e2e2 c 226 41 +e2ee 47 227 41 +e335 1 227 41 +FUNC e336 20 0 std::_Rb_tree_iterator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >::operator==(std::_Rb_tree_iterator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > > const&) const +e336 c 209 41 +e342 14 210 40 +e356 c 84 70 +e362 18 85 70 +FUNC e37a 14 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, dwarf2reader::FunctionInfo*>, std::_Select1st<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >::_M_end() +e37a c 472 41 +e386 8 473 40 +FUNC e38e 11 0 std::_Select1st<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >::operator()(std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> const&) const +e38e c 550 41 +e39a 5 551 41 +e39f 1 551 41 +FUNC e3a0 20 0 std::_Rb_tree_iterator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >::operator==(std::_Rb_tree_iterator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > const&) const +e3a0 c 209 41 +e3ac 14 210 40 +e3c0 c 84 70 +e3cc 18 85 70 +e3e4 c 180 34 +e3f0 13 181 34 +e403 1 181 34 +e410 22 409 34 +e43e d 207 42 +e44b 1 207 42 +FUNC e44c 35 0 bool __gnu_cxx::operator!=<std::string*, std::vector<std::string, std::allocator<std::string> > >(__gnu_cxx::__normal_iterator<std::string*, std::vector<std::string, std::allocator<std::string> > > const&, __gnu_cxx::__normal_iterator<std::string*, std::vector<std::string, std::allocator<std::string> > > const&) +e44c d 699 42 +e459 28 700 72 +e481 1 700 72 +FUNC e482 4b 0 void std::_Destroy<__gnu_cxx::__normal_iterator<std::string*, std::vector<std::string, std::allocator<std::string> > >, std::allocator<std::string> >(__gnu_cxx::__normal_iterator<std::string*, std::vector<std::string, std::allocator<std::string> > >, __gnu_cxx::__normal_iterator<std::string*, std::vector<std::string, std::allocator<std::string> > >, std::allocator<std::string>) +e482 c 171 43 +e48e 2 173 73 +e490 1a 174 73 +e4aa 21 173 73 +e4cb 2 174 73 +e4cd 1 174 73 +FUNC e4ce 35 0 bool __gnu_cxx::operator!=<dwarf2reader::SourceFileInfo*, std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> > >(__gnu_cxx::__normal_iterator<dwarf2reader::SourceFileInfo*, std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> > > const&, __gnu_cxx::__normal_iterator<dwarf2reader::SourceFileInfo*, std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> > > const&) +e4ce d 699 43 +e4db 28 700 72 +e503 1 700 72 +FUNC e504 4b 0 void std::_Destroy<__gnu_cxx::__normal_iterator<dwarf2reader::SourceFileInfo*, std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> > >, std::allocator<dwarf2reader::SourceFileInfo> >(__gnu_cxx::__normal_iterator<dwarf2reader::SourceFileInfo*, std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> > >, __gnu_cxx::__normal_iterator<dwarf2reader::SourceFileInfo*, std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> > >, std::allocator<dwarf2reader::SourceFileInfo>) +e504 c 171 43 +e510 2 173 73 +e512 1a 174 73 +e52c 21 173 73 +e54d 2 174 73 +e54f 1 174 73 +FUNC e550 14 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, std::pair<std::string, unsigned int> >, std::_Select1st<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > > >::_S_value(std::_Rb_tree_node<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > > const*) +e550 c 480 43 +e55c 8 481 40 +FUNC e564 28 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, std::pair<std::string, unsigned int> >, std::_Select1st<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > > >::_S_key(std::_Rb_tree_node<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > > const*) +e564 c 484 43 +e570 1c 485 40 +FUNC e58c 25 0 std::_Rb_tree_iterator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >::operator--() +e58c c 194 43 +e598 14 196 40 +e5ac 5 197 40 +e5b1 1 197 40 +FUNC e5b2 25 0 std::_Rb_tree_iterator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >::operator--() +e5b2 c 194 43 +e5be 14 196 40 +e5d2 5 197 40 +e5d7 1 197 40 +FUNC e5d8 14 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, std::pair<std::string, unsigned int> >, std::_Select1st<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > > >::_S_value(std::_Rb_tree_node_base const*) +e5d8 c 504 43 +e5e4 8 505 40 +FUNC e5ec 28 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, std::pair<std::string, unsigned int> >, std::_Select1st<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > > >::_S_key(std::_Rb_tree_node_base const*) +e5ec c 508 43 +e5f8 1c 509 40 +FUNC e614 14 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, dwarf2reader::FunctionInfo*>, std::_Select1st<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >::_S_value(std::_Rb_tree_node<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > const*) +e614 c 480 43 +e620 8 481 40 +FUNC e628 28 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, dwarf2reader::FunctionInfo*>, std::_Select1st<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >::_S_key(std::_Rb_tree_node<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > const*) +e628 c 484 43 +e634 1c 485 40 +FUNC e650 14 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, dwarf2reader::FunctionInfo*>, std::_Select1st<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >::_S_value(std::_Rb_tree_node_base const*) +e650 c 504 43 +e65c 8 505 40 +FUNC e664 28 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, dwarf2reader::FunctionInfo*>, std::_Select1st<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >::_S_key(std::_Rb_tree_node_base const*) +e664 c 508 43 +e670 1c 509 40 +e698 7 614 72 +e69f 1 614 72 +e6ac 7 98 68 +e6b3 1 98 68 +e6c0 1d 85 68 +e6dd 5 86 68 +e6e2 10 88 68 +FUNC e6f2 2a 0 std::_Vector_base<std::string, std::allocator<std::string> >::_M_allocate(unsigned long) +e6f2 c 116 43 +e6fe 1e 117 71 +e728 7 98 68 +e72f 1 98 68 +e73c 1d 85 68 +e759 5 86 68 +e75e 16 88 68 +FUNC e774 2a 0 std::_Vector_base<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> >::_M_allocate(unsigned long) +e774 c 116 43 +e780 1e 117 71 +e7aa 3a 104 68 +e7f0 2a 654 72 +FUNC e81a 42 0 std::vector<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*, std::allocator<__gnu_cxx::_Hashtable_node<std::pair<std::string const, std::pair<char const*, unsigned long long> > >*> >::operator[](unsigned long) const +e81a c 509 43 +e826 36 510 71 +FUNC e85c 4e 0 std::string* std::__copy_backward<false, std::random_access_iterator_tag>::copy_b<std::string*, std::string*>(std::string*, std::string*, std::string*) +e85c c 408 61 +e868 14 411 61 +e87c 1e 412 61 +e89a b 411 61 +e8a5 5 413 61 +FUNC e8aa 2b 0 std::string* std::__copy_backward_aux<std::string*, std::string*>(std::string*, std::string*, std::string*) +e8aa c 432 44 +e8b6 4 440 61 +e8ba 1b 443 61 +e8d5 1 443 61 +e8e4 56 482 61 +e946 4 514 61 +e94a 4 515 61 +e94e 1b 517 61 +e969 1 517 61 +FUNC e96a 69 0 void std::_Construct<std::string, std::string>(std::string*, std::string const&) +e96a d 77 44 +e977 5c 81 73 +e9d3 1 81 73 +FUNC e9d4 54 0 dwarf2reader::SourceFileInfo* std::__copy_backward<false, std::random_access_iterator_tag>::copy_b<dwarf2reader::SourceFileInfo*, dwarf2reader::SourceFileInfo*>(dwarf2reader::SourceFileInfo*, dwarf2reader::SourceFileInfo*, dwarf2reader::SourceFileInfo*) +e9d4 c 408 61 +e9e0 1a 411 61 +e9fa 1e 412 61 +ea18 b 411 61 +ea23 5 413 61 +FUNC ea28 2b 0 dwarf2reader::SourceFileInfo* std::__copy_backward_aux<dwarf2reader::SourceFileInfo*, dwarf2reader::SourceFileInfo*>(dwarf2reader::SourceFileInfo*, dwarf2reader::SourceFileInfo*, dwarf2reader::SourceFileInfo*) +ea28 c 432 44 +ea34 4 440 61 +ea38 1b 443 61 +ea53 1 443 61 +ea62 56 482 61 +eac4 4 514 61 +eac8 4 515 61 +eacc 1b 517 61 +eae7 1 517 61 +FUNC eae8 69 0 void std::_Construct<dwarf2reader::SourceFileInfo, dwarf2reader::SourceFileInfo>(dwarf2reader::SourceFileInfo*, dwarf2reader::SourceFileInfo const&) +eae8 d 77 44 +eaf5 5c 81 73 +eb51 1 81 73 +eb52 c 69 70 +eb5e 20 69 70 +eb7e c 69 70 +eb8a 2a 69 70 +ebc1 5c 104 68 +ec1d 1 104 68 +ec2a 15 523 34 +ec3f 79 525 34 +ecb8 21 529 34 +ecd9 1 529 34 +ece6 14 229 42 +ed06 7 98 68 +ed0d 1 98 68 +ed1a 1d 85 68 +ed37 5 86 68 +ed3c 10 88 68 +FUNC ed4c 29 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, std::pair<std::string, unsigned int> >, std::_Select1st<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > > >::_M_get_node() +ed4c c 355 44 +ed58 1d 356 40 +ed75 1 356 40 +FUNC ed76 b6 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, std::pair<std::string, unsigned int> >, std::_Select1st<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > > >::_M_create_node(std::pair<unsigned long long const, std::pair<std::string, unsigned int> > const&) +ed76 d 363 44 +ed83 e 365 40 +ed91 3c 367 40 +edcd b 373 40 +edd8 11 367 40 +ede9 b 368 40 +edf4 12 370 40 +ee06 b 371 40 +ee11 13 368 40 +ee24 8 373 40 +FUNC ee2c cd 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, std::pair<std::string, unsigned int> >, std::_Select1st<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > > >::_M_insert(std::_Rb_tree_node_base*, std::_Rb_tree_node_base*, std::pair<unsigned long long const, std::pair<std::string, unsigned int> > const&) +ee2c d 787 44 +ee39 15 789 40 +ee4e 5d 792 40 +eeab 24 796 40 +eecf f 798 40 +eede 1b 799 40 +eef9 1 799 40 +FUNC eefa 1ef 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, std::pair<std::string, unsigned int> >, std::_Select1st<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > > >::insert_unique(std::pair<unsigned long long const, std::pair<std::string, unsigned int> > const&) +eefa d 869 44 +ef07 e 871 40 +ef15 e 872 40 +ef23 4 873 40 +ef27 2 874 40 +ef29 6 876 40 +ef2f 35 877 40 +ef64 2a 878 40 +ef8e 6 874 40 +ef94 12 880 40 +efa6 a 881 40 +efb0 24 882 40 +efd4 51 883 40 +f025 b 885 40 +f030 36 886 40 +f066 4e 887 40 +f0b4 35 888 40 +f0e9 1 888 40 +FUNC f0ea 20 0 std::map<unsigned long long, std::pair<std::string, unsigned int>, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, std::pair<std::string, unsigned int> > > >::insert(std::pair<unsigned long long const, std::pair<std::string, unsigned int> > const&) +f0ea c 359 45 +f0f6 14 360 45 +f116 7 98 68 +f11d 1 98 68 +f12a 1d 85 68 +f147 5 86 68 +f14c 1d 88 68 +f169 1 88 68 +FUNC f16a 29 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, dwarf2reader::FunctionInfo*>, std::_Select1st<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >::_M_get_node() +f16a c 355 45 +f176 1d 356 40 +f193 1 356 40 +FUNC f194 5f 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, dwarf2reader::FunctionInfo*>, std::_Select1st<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >::_M_create_node(std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> const&) +f194 d 363 45 +f1a1 e 365 40 +f1af 3c 367 40 +f1eb 8 373 40 +f1f3 1 373 40 +FUNC f1f4 cd 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, dwarf2reader::FunctionInfo*>, std::_Select1st<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >::_M_insert(std::_Rb_tree_node_base*, std::_Rb_tree_node_base*, std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> const&) +f1f4 d 787 45 +f201 15 789 40 +f216 5d 792 40 +f273 24 796 40 +f297 f 798 40 +f2a6 1b 799 40 +f2c1 1 799 40 +FUNC f2c2 1ef 0 std::_Rb_tree<unsigned long long, std::pair<unsigned long long const, dwarf2reader::FunctionInfo*>, std::_Select1st<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> >, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >::insert_unique(std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> const&) +f2c2 d 869 45 +f2cf e 871 40 +f2dd e 872 40 +f2eb 4 873 40 +f2ef 2 874 40 +f2f1 6 876 40 +f2f7 35 877 40 +f32c 2a 878 40 +f356 6 874 40 +f35c 12 880 40 +f36e a 881 40 +f378 24 882 40 +f39c 51 883 40 +f3ed b 885 40 +f3f8 36 886 40 +f42e 4e 887 40 +f47c 35 888 40 +f4b1 1 888 40 +FUNC f4b2 20 0 std::map<unsigned long long, dwarf2reader::FunctionInfo*, std::less<unsigned long long>, std::allocator<std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> > >::insert(std::pair<unsigned long long const, dwarf2reader::FunctionInfo*> const&) +f4b2 c 359 45 +f4be 14 360 45 +FUNC f4d2 19 0 void std::_Destroy<std::string>(std::string*) +f4d2 c 106 45 +f4de d 107 73 +f4eb 1 107 73 +FUNC f4ec 44 0 void std::__destroy_aux<__gnu_cxx::__normal_iterator<std::string*, std::vector<std::string, std::allocator<std::string> > > >(__gnu_cxx::__normal_iterator<std::string*, std::vector<std::string, std::allocator<std::string> > >, __gnu_cxx::__normal_iterator<std::string*, std::vector<std::string, std::allocator<std::string> > >, __false_type) +f4ec c 119 45 +f4f8 2 121 73 +f4fa 13 122 73 +f50d 21 121 73 +f52e 2 122 73 +FUNC f530 28 0 void std::_Destroy<__gnu_cxx::__normal_iterator<std::string*, std::vector<std::string, std::allocator<std::string> > > >(__gnu_cxx::__normal_iterator<std::string*, std::vector<std::string, std::allocator<std::string> > >, __gnu_cxx::__normal_iterator<std::string*, std::vector<std::string, std::allocator<std::string> > >) +f530 c 148 45 +f53c 1c 155 73 +f565 6 82 79 +f56b 2 85 79 +f56d 24 86 79 +f591 2c 85 79 +f5bd b 87 79 +f5c8 b 89 79 +f5d3 12 91 79 +f5e5 b 92 79 +f5f0 13 89 79 +f603 9 92 79 +f618 23 113 79 +f63b 1 113 79 +f648 1b 254 79 +f663 1 254 79 +FUNC f664 430 0 std::vector<std::string, std::allocator<std::string> >::_M_insert_aux(__gnu_cxx::__normal_iterator<std::string*, std::vector<std::string, std::allocator<std::string> > >, std::string const&) +f664 14 249 47 +f678 14 251 78 +f68c 22 253 78 +f6ae f 255 78 +f6bd 12 256 78 +f6cf 55 257 78 +f724 4b 260 78 +f76f e 264 78 +f77d 15 265 78 +f792 e 266 78 +f7a0 1d 271 78 +f7bd 8 272 78 +f7c5 e 273 78 +f7d3 27 275 78 +f7fa 6 276 78 +f800 55 279 78 +f855 25 284 78 +f87a b 285 78 +f885 4f 286 78 +f8d4 3 284 78 +f8d7 13 279 78 +f8ea e 286 78 +f8f8 4d 298 78 +f945 30 299 78 +f975 12 302 78 +f987 13 303 78 +f99a 23 304 78 +f9bd 3 298 78 +f9c0 13 286 78 +f9d3 b 292 78 +f9de 39 294 78 +fa17 23 295 78 +fa3a 8 296 78 +fa42 16 294 78 +fa58 3 296 78 +fa5b 19 292 78 +fa74 19 298 78 +fa8d 7 304 78 +FUNC fa94 70 0 std::vector<std::string, std::allocator<std::string> >::push_back(std::string const&) +fa94 c 602 47 +faa0 10 604 71 +fab0 1e 606 71 +face 11 607 71 +fadf 25 610 71 +FUNC fb04 19 0 void std::_Destroy<dwarf2reader::SourceFileInfo>(dwarf2reader::SourceFileInfo*) +fb04 c 106 47 +fb10 d 107 73 +fb1d 1 107 73 +FUNC fb1e 44 0 void std::__destroy_aux<__gnu_cxx::__normal_iterator<dwarf2reader::SourceFileInfo*, std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> > > >(__gnu_cxx::__normal_iterator<dwarf2reader::SourceFileInfo*, std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> > >, __gnu_cxx::__normal_iterator<dwarf2reader::SourceFileInfo*, std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> > >, __false_type) +fb1e c 119 47 +fb2a 2 121 73 +fb2c 13 122 73 +fb3f 21 121 73 +fb60 2 122 73 +FUNC fb62 28 0 void std::_Destroy<__gnu_cxx::__normal_iterator<dwarf2reader::SourceFileInfo*, std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> > > >(__gnu_cxx::__normal_iterator<dwarf2reader::SourceFileInfo*, std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> > >, __gnu_cxx::__normal_iterator<dwarf2reader::SourceFileInfo*, std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> > >) +fb62 c 148 47 +fb6e 1c 155 73 +fb97 6 82 79 +fb9d 2 85 79 +fb9f 24 86 79 +fbc3 2c 85 79 +fbef b 87 79 +fbfa b 89 79 +fc05 12 91 79 +fc17 b 92 79 +fc22 13 89 79 +fc35 9 92 79 +fc4a 23 113 79 +fc6d 1 113 79 +fc7a 1b 254 79 +fc95 1 254 79 +FUNC fc96 43d 0 std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> >::_M_insert_aux(__gnu_cxx::__normal_iterator<dwarf2reader::SourceFileInfo*, std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> > >, dwarf2reader::SourceFileInfo const&) +fc96 14 249 47 +fcaa 14 251 78 +fcbe 22 253 78 +fce0 f 255 78 +fcef 12 256 78 +fd01 55 257 78 +fd56 4b 260 78 +fda1 e 264 78 +fdaf 15 265 78 +fdc4 e 266 78 +fdd2 1d 271 78 +fdef 8 272 78 +fdf7 e 273 78 +fe05 27 275 78 +fe2c 6 276 78 +fe32 55 279 78 +fe87 25 284 78 +feac b 285 78 +feb7 4f 286 78 +ff06 3 284 78 +ff09 13 279 78 +ff1c e 286 78 +ff2a 4d 298 78 +ff77 36 299 78 +ffad 12 302 78 +ffbf 13 303 78 +ffd2 2a 304 78 +fffc 3 298 78 +ffff 13 286 78 +10012 b 292 78 +1001d 39 294 78 +10056 23 295 78 +10079 8 296 78 +10081 16 294 78 +10097 3 296 78 +1009a 19 292 78 +100b3 19 298 78 +100cc 7 304 78 +100d3 1 304 78 +FUNC 100d4 70 0 std::vector<dwarf2reader::SourceFileInfo, std::allocator<dwarf2reader::SourceFileInfo> >::push_back(dwarf2reader::SourceFileInfo const&) +100d4 c 602 47 +100e0 10 604 71 +100f0 1e 606 71 +1010e 11 607 71 +1011f 25 610 71 +FUNC 10144 16c 0 Start +10144 17 610 71 +1015b 40 49 48 +1019b 6 51 48 +101a1 3f 53 48 +101e0 7 54 48 +101e7 5 55 48 +101ec 2a 58 48 +10216 61 61 48 +10277 7 62 48 +1027e 2 63 48 +10280 29 66 48 +102a9 7 67 48 +FUNC 102b0 108 0 Usage +102b0 19 70 48 +102c9 30 71 48 +102f9 29 73 48 +10322 30 74 48 +10352 30 75 48 +10382 30 76 48 +103b2 6 77 48 +FUNC 103b8 3af 0 SetupOptions +103b8 21 80 48 +103d9 8 82 48 +103e1 6 85 48 +103e7 10 86 48 +103f7 2e 88 48 +10425 2f 94 48 +10454 2a 91 48 +1047e 23 95 48 +104a1 3e 97 48 +104df 11 98 48 +104f0 7c 99 48 +1056c c 100 48 +10578 5 99 48 +1057d 3e 101 48 +105bb 11 102 48 +105cc 3e 103 48 +1060a 11 104 48 +1061b 37 106 48 +10652 b 107 48 +1065d c 108 48 +10669 b 113 48 +10674 c 114 48 +10680 14 119 48 +10694 30 120 48 +106c4 b 121 48 +106cf c 122 48 +106db 81 127 48 +1075c b 128 48 +10767 1 128 48 +FUNC 10768 a7 0 main +10768 13 131 48 +1077b 37 132 48 +107b2 1e 135 48 +107d0 e 136 48 +107de 8 137 48 +107e6 17 139 48 +107fd c 141 48 +10809 6 142 48 +1080f 1 142 48 +10810 c 47 49 +1081c 1a 48 49 +10836 2 49 49 +10838 c 47 49 +10844 1a 48 49 +1085e 2 49 49 +FUNC 10860 cb 0 google_breakpad::FileID::FileIdentifier(unsigned char*) +10860 f 51 49 +1086f 16 52 49 +10885 6 53 49 +1088b f 54 49 +1089a b 57 49 +108a5 7 62 49 +108ac 2 63 49 +108ae 1c 64 49 +108ca 32 63 49 +108fc b 67 49 +10907 12 68 49 +10919 10 70 49 +10929 2 71 49 +1092b 1 71 49 +FUNC 1092c f2 0 google_breakpad::FileID::MachoIdentifier(int, unsigned char*) +1092c 10 73 49 +1093c 15 74 49 +10951 20 76 49 +10971 f 77 49 +10980 20 79 49 +109a0 c 80 49 +109ac 69 82 49 +10a15 9 83 49 +FUNC 10a1e fb 0 google_breakpad::FileID::ConvertIdentifierToString(unsigned char const*, char*, int) +10a1e c 87 49 +10a2a 7 88 49 +10a31 c 89 49 +10a3d 15 90 49 +10a52 12 91 49 +10a64 18 93 49 +10a7c e 94 49 +10a8a 2b 96 49 +10ab5 2b 97 49 +10ae0 17 89 49 +10af7 20 101 49 +10b17 2 102 49 +10b19 1 102 49 +FUNC 10b1a 13 0 NXHostByteOrder +10b1a c 144 56 +10b26 5 147 56 +10b2b 2 153 56 +10b2d 1 153 56 +10b2e c 56 51 +10b3a 1a 57 51 +10b54 1e 58 51 +10b72 2 59 51 +10b74 c 56 51 +10b80 1a 57 51 +10b9a 1e 58 51 +10bb8 2 59 51 +10bba c 61 51 +10bc6 e 62 51 +10bd4 11 63 51 +10be5 2 64 51 +10be7 1 64 51 +10be8 c 61 51 +10bf4 e 62 51 +10c02 11 63 51 +10c13 2 64 51 +10c15 1 64 51 +FUNC 10c16 477 0 MacFileUtilities::MachoID::UpdateCRC(unsigned char*, unsigned long) +10c16 c 74 51 +10c22 11 82 51 +10c33 14 83 51 +10c47 5 86 51 +10c4c 9 87 51 +10c55 7 88 51 +10c5c 18b 90 51 +10de7 6 91 51 +10ded 14 89 51 +10e01 23 93 51 +10e24 23 94 51 +10e47 d 86 51 +10e54 f 98 51 +10e63 6 100 51 +10e69 18b 101 51 +10ff4 6 102 51 +10ffa c 99 51 +11006 13 105 51 +11019 8 106 51 +11021 10 104 51 +11031 23 108 51 +11054 23 109 51 +11077 14 110 51 +1108b 2 112 51 +1108d 1 112 51 +FUNC 1108e 2c 0 MacFileUtilities::MachoID::UpdateMD5(unsigned char*, unsigned long) +1108e c 114 51 +1109a 1e 115 51 +110b8 2 116 51 +FUNC 110ba 2c 0 MacFileUtilities::MachoID::UpdateSHA1(unsigned char*, unsigned long) +110ba c 118 51 +110c6 1e 119 51 +110e4 2 120 51 +FUNC 110e6 121 0 MacFileUtilities::MachoID::Update(MacFileUtilities::MachoWalker*, unsigned long, unsigned long) +110e6 f 122 51 +110f5 1b 123 51 +11110 e 129 51 +1111e 5 130 51 +11123 9 131 51 +1112c 7 132 51 +11133 a 133 51 +1113d 6 135 51 +11143 7 136 51 +1114a 35 139 51 +1117f 6c 142 51 +111eb 10 143 51 +111fb a 130 51 +11205 2 145 51 +11207 1 145 51 +FUNC 11208 cf 0 MacFileUtilities::MachoID::UUIDCommand(int, unsigned char*) +11208 14 147 51 +1121c 25 149 51 +11241 7 151 51 +11248 19 152 51 +11261 9 153 51 +1126a 8 157 51 +11272 1f 158 51 +11291 9 159 51 +1129a 36 162 51 +112d0 7 163 51 +112d7 1 163 51 +FUNC 112d8 224 0 MacFileUtilities::MachoID::IDCommand(int, unsigned char*) +112d8 15 165 51 +112ed 25 167 51 +11312 7 169 51 +11319 19 170 51 +11332 c 171 51 +1133e c 175 51 +1134a 6 180 51 +11350 7 181 51 +11357 9 182 51 +11360 9 183 51 +11369 28 185 51 +11391 33 186 51 +113c4 1e 185 51 +113e2 10 189 51 +113f2 10 190 51 +11402 10 191 51 +11412 d 192 51 +1141f 10 193 51 +1142f 10 194 51 +1143f 10 195 51 +1144f d 196 51 +1145c 17 197 51 +11473 17 198 51 +1148a 17 199 51 +114a1 14 200 51 +114b5 9 202 51 +114be 36 205 51 +114f4 8 206 51 +FUNC 114fc d1 0 MacFileUtilities::MachoID::Adler32(int) +114fc 14 208 51 +11510 25 209 51 +11535 27 210 51 +1155c d 211 51 +11569 19 213 51 +11582 9 214 51 +1158b 3b 216 51 +115c6 7 217 51 +115cd 1 217 51 +FUNC 115ce f8 0 MacFileUtilities::MachoID::MD5(int, unsigned char*) +115ce 14 219 51 +115e2 25 220 51 +11607 27 221 51 +1162e 19 223 51 +11647 19 224 51 +11660 9 225 51 +11669 17 227 51 +11680 9 228 51 +11689 36 231 51 +116bf 7 232 51 +FUNC 116c6 f8 0 MacFileUtilities::MachoID::SHA1(int, unsigned char*) +116c6 14 234 51 +116da 25 235 51 +116ff 27 236 51 +11726 19 238 51 +1173f 19 239 51 +11758 9 240 51 +11761 17 242 51 +11778 9 243 51 +11781 36 246 51 +117b7 7 247 51 +FUNC 117be 378 0 MacFileUtilities::MachoID::WalkerCB(MacFileUtilities::MachoWalker*, load_command*, long long, bool, void*) +117be 2b 251 51 +117e9 6 252 51 +117ef e 254 51 +117fd 38 257 51 +11835 f 258 51 +11844 9 260 51 +1184d 17 261 51 +11864 20 266 51 +11884 f 267 51 +11893 d 271 51 +118a0 c 273 51 +118ac 38 274 51 +118e4 f 275 51 +118f3 9 277 51 +118fc 1f 278 51 +1191b 14 282 51 +1192f 2b 283 51 +1195a d 285 51 +11967 19 273 51 +11980 e 287 51 +1198e 38 290 51 +119c6 f 291 51 +119d5 9 293 51 +119de 17 294 51 +119f5 20 299 51 +11a15 f 300 51 +11a24 d 304 51 +11a31 c 306 51 +11a3d 38 307 51 +11a75 f 308 51 +11a84 9 310 51 +11a8d 1f 311 51 +11aac 1a 315 51 +11ac6 39 316 51 +11aff d 318 51 +11b0c 11 306 51 +11b1d 10 323 51 +11b2d 9 324 51 +FUNC 11b36 95 0 MacFileUtilities::MachoID::UUIDWalkerCB(MacFileUtilities::MachoWalker*, load_command*, long long, bool, void*) +11b36 1e 328 51 +11b54 a 329 51 +11b5e 6 331 51 +11b64 2f 333 51 +11b93 9 335 51 +11b9c 6 337 51 +11ba2 14 338 51 +11bb6 9 340 51 +11bbf a 344 51 +11bc9 2 345 51 +11bcb 1 345 51 +FUNC 11bcc 95 0 MacFileUtilities::MachoID::IDWalkerCB(MacFileUtilities::MachoWalker*, load_command*, long long, bool, void*) +11bcc 1e 349 51 +11bea a 350 51 +11bf4 6 351 51 +11bfa 2f 353 51 +11c29 9 354 51 +11c32 6 356 51 +11c38 14 357 51 +11c4c 9 359 51 +11c55 a 363 51 +11c5f 2 364 51 +11c61 1 364 51 +FUNC 11c62 1c 0 _OSSwapInt32 +11c62 f 53 55 +11c71 8 55 55 +11c79 3 56 55 +11c7c 2 57 55 +FUNC 11c7e 19 0 NXSwapInt +11c7e f 52 56 +11c8d 8 54 56 +11c95 2 55 56 +11c97 1 55 56 +FUNC 11c98 13 0 NXHostByteOrder +11c98 c 144 56 +11ca4 5 147 56 +11ca9 2 153 56 +11cab 1 153 56 +11cac c 52 54 +11cb8 12 54 54 +11cca 1a 55 54 +11ce4 2 56 54 +11ce6 c 52 54 +11cf2 12 54 54 +11d04 1a 55 54 +11d1e 2 56 54 +11d20 c 58 54 +11d2c a 59 54 +11d36 d 60 54 +11d43 2 61 54 +11d45 1 61 54 +11d46 c 58 54 +11d52 a 59 54 +11d5c d 60 54 +11d69 2 61 54 +11d6b 1 61 54 +FUNC 11d6c 37 0 MacFileUtilities::MachoWalker::ValidateCPUType(int) +11d6c c 63 54 +11d78 6 66 54 +11d7e 8 67 54 +11d86 6 68 54 +11d8c b 69 54 +11d97 7 74 54 +11d9e 3 80 54 +11da1 2 81 54 +11da3 1 81 54 +FUNC 11da4 50 0 MacFileUtilities::MachoWalker::ReadBytes(void*, unsigned long, long long) +11da4 18 96 54 +11dbc 36 97 54 +11df2 2 98 54 +FUNC 11df4 73 0 MacFileUtilities::MachoWalker::CurrentHeader(mach_header_64*, long long*) +11df4 c 100 54 +11e00 a 101 54 +11e0a 37 102 54 +11e41 11 103 54 +11e52 9 104 54 +11e5b a 107 54 +11e65 2 108 54 +11e67 1 108 54 +FUNC 11e68 2a6 0 MacFileUtilities::MachoWalker::FindHeader(int, long long&) +11e68 c 110 54 +11e74 15 111 54 +11e89 31 114 54 +11eba c 115 54 +11ec6 10 117 54 +11ed6 4 120 54 +11eda 14 121 54 +11eee 4 122 54 +11ef2 11 129 54 +11f03 28 124 54 +11f2b c 126 54 +11f37 31 133 54 +11f68 c 134 54 +11f74 14 136 54 +11f88 b 137 54 +11f93 8 139 54 +11f9b c 140 54 +11fa7 10 142 54 +11fb7 c 143 54 +11fc3 10 146 54 +11fd3 31 148 54 +12004 c 149 54 +12010 f 151 54 +1201f 14 152 54 +12033 16 154 54 +12049 c 158 54 +12055 31 159 54 +12086 9 160 54 +1208f f 162 54 +1209e 1c 163 54 +120ba 8 165 54 +120c2 10 166 54 +120d2 9 167 54 +120db 16 170 54 +120f1 11 158 54 +12102 a 174 54 +1210c 2 175 54 +FUNC 1210e 109 0 MacFileUtilities::MachoWalker::WalkHeaderCore(long long, unsigned int, bool) +1210e 1e 224 54 +1212c c 225 54 +12138 2f 227 54 +12167 c 228 54 +12173 6 230 54 +12179 14 231 54 +1218d 5b 234 54 +121e8 12 237 54 +121fa 11 225 54 +1220b a 240 54 +12215 2 241 54 +12217 1 241 54 +FUNC 12218 10e 0 MacFileUtilities::MachoWalker::WalkHeader64AtOffset(long long) +12218 18 203 54 +12230 2f 205 54 +1225f c 206 54 +1226b e 208 54 +12279 6 209 54 +1227f 14 210 54 +12293 9 212 54 +1229c a 213 54 +122a6 f 214 54 +122b5 15 215 54 +122ca 2b 216 54 +122f5 a 217 54 +122ff a 218 54 +12309 11 219 54 +1231a a 220 54 +12324 2 221 54 +FUNC 12326 143 0 MacFileUtilities::MachoWalker::WalkHeaderAtOffset(long long) +12326 18 177 54 +1233e 2f 179 54 +1236d c 180 54 +12379 e 182 54 +12387 6 183 54 +1238d 14 184 54 +123a1 2e 189 54 +123cf 7 190 54 +123d6 9 192 54 +123df a 193 54 +123e9 f 194 54 +123f8 15 195 54 +1240d 2b 196 54 +12438 a 197 54 +12442 a 198 54 +1244c 11 199 54 +1245d a 200 54 +12467 2 201 54 +12469 1 201 54 +FUNC 1246a 99 0 MacFileUtilities::MachoWalker::WalkHeader(int) +1246a c 83 54 +12476 15 84 54 +1248b 1d 86 54 +124a8 d 87 54 +124b5 21 88 54 +124d6 21 90 54 +124f7 a 93 54 +12501 2 94 54 +12503 1 94 54 +FUNC 12504 1c 0 _OSSwapInt32 +12504 f 53 55 +12513 8 55 55 +1251b 3 56 55 +1251e 2 57 55 +FUNC 12520 2b 0 _OSSwapInt64 +12520 12 64 55 +12532 11 69 55 +12543 6 70 55 +12549 2 71 55 +1254b 1 71 55 +FUNC 1254c 19 0 NXSwapLong +1254c f 61 56 +1255b 8 63 56 +12563 2 64 56 +12565 1 64 56 +FUNC 12566 1f 0 NXSwapLongLong +12566 12 70 56 +12578 b 72 56 +12583 2 73 56 +12585 1 73 56 +FUNC 12586 32 0 breakpad_swap_uuid_command(breakpad_uuid_command*, NXByteOrder) +12586 c 37 57 +12592 11 39 57 +125a3 13 40 57 +125b6 2 41 57 +FUNC 125b8 da 0 breakpad_swap_segment_command_64(segment_command_64*, NXByteOrder) +125b8 c 44 57 +125c4 11 46 57 +125d5 13 47 57 +125e8 17 49 57 +125ff 17 50 57 +12616 17 51 57 +1262d 17 52 57 +12644 13 54 57 +12657 13 55 57 +1266a 13 56 57 +1267d 13 57 57 +12690 2 58 57 +FUNC 12692 a4 0 breakpad_swap_mach_header_64(mach_header_64*, NXByteOrder) +12692 c 61 57 +1269e 11 63 57 +126af 13 64 57 +126c2 13 65 57 +126d5 13 66 57 +126e8 13 67 57 +126fb 13 68 57 +1270e 13 69 57 +12721 13 70 57 +12734 2 71 57 +FUNC 12736 1d1 0 breakpad_swap_section_64(section_64*, unsigned int, NXByteOrder) +12736 d 75 57 +12743 c 77 57 +1274f 33 78 57 +12782 33 79 57 +127b5 2d 81 57 +127e2 2d 82 57 +1280f 2d 83 57 +1283c 2d 84 57 +12869 2d 85 57 +12896 2d 86 57 +128c3 2d 87 57 +128f0 11 77 57 +12901 6 89 57 +12907 1 89 57 +12908 12 9 58 +1291a 4f 11 58 +12969 2 12 58 +1296b 1 12 58 +1296c 12 9 58 +1297e 4f 11 58 +129cd 2 12 58 +129cf 1 12 58 +129d0 13 14 58 +129e3 2a 14 58 +12a0d 1 14 58 +12a0e 13 14 58 +12a21 2a 14 58 +12a4b 1 14 58 +12a4c 13 14 58 +12a5f 2a 14 58 +12a89 1 14 58 +FUNC 12a8a bb 0 dwarf2reader::ByteReader::SetOffsetSize(unsigned char) +12a8a 19 16 58 +12aa3 a 17 58 +12aad 48 18 58 +12af5 6 19 58 +12afb 23 20 58 +12b1e 21 22 58 +12b3f 6 24 58 +12b45 1 24 58 +FUNC 12b46 bb 0 dwarf2reader::ByteReader::SetAddressSize(unsigned char) +12b46 19 26 58 +12b5f a 27 58 +12b69 48 28 58 +12bb1 6 29 58 +12bb7 23 30 58 +12bda 21 32 58 +12bfb 6 34 58 +12c01 1 34 58 +FUNC 12c02 a2 0 dwarf2reader::ByteReader::ReadFourBytes(char const*) const +12c02 c 24 59 +12c0e c 25 64 +12c1a d 26 64 +12c27 f 27 64 +12c36 f 28 64 +12c45 b 29 64 +12c50 27 30 64 +12c77 2b 32 64 +12ca2 2 34 64 +FUNC 12ca4 40e 0 dwarf2reader::ByteReader::ReadEightBytes(char const*) const +12ca4 11 36 59 +12cb5 1a 37 64 +12ccf 1b 38 64 +12cea 1d 39 64 +12d07 1d 40 64 +12d24 1d 41 64 +12d41 1d 42 64 +12d5e 1d 43 64 +12d7b 1d 44 64 +12d98 f 45 64 +12da7 18f 47 64 +12f36 172 50 64 +130a8 a 52 64 +130b2 2 52 64 +FUNC 130b4 a6 0 ReadInitialLength +130b4 15 29 60 +130c9 18 30 60 +130e1 6 31 60 +130e7 d 35 60 +130f4 13 36 60 +13107 9 37 60 +13110 1a 38 60 +1312a 13 40 60 +1313d 9 41 60 +13146 12 43 60 +13158 2 44 60 +1315a 1f 47 60 +13179 65 50 60 +131de 1f 47 60 +131fd 65 50 60 +FUNC 13262 393 0 dwarf2reader::CompilationUnit::SkipAttribute(char const*, dwarf2reader::DwarfForm) +13262 14 133 60 +13276 82 136 60 +132f8 1f 139 60 +13317 a 140 60 +13321 21 141 60 +13342 c 147 60 +1334e e 151 60 +1335c e 155 60 +1336a e 159 60 +13378 27 162 60 +1339f 1c 166 60 +133bb 10 167 60 +133cb 1c 171 60 +133e7 10 172 60 +133f7 1e 175 60 +13415 56 180 60 +1346b d 181 60 +13478 1e 182 60 +13496 11 183 60 +134a7 1e 184 60 +134c5 24 189 60 +134e9 26 192 60 +1350f 23 195 60 +13532 22 198 60 +13554 15 199 60 +13569 1b 203 60 +13584 30 206 60 +135b4 30 208 60 +135e4 a 209 60 +135ee 7 210 60 +135f5 1 210 60 +FUNC 135f6 29b 0 dwarf2reader::CompilationUnit::ReadHeader() +135f6 14 217 60 +1360a 9 218 60 +13613 4e 221 60 +13661 17 223 60 +13678 a 224 60 +13682 f 225 60 +13691 4e 227 60 +136df 1e 228 60 +136fd 6 229 60 +13703 5e 231 60 +13761 1e 232 60 +1377f 18 233 60 +13797 4c 235 60 +137e3 1d 236 60 +13800 1c 237 60 +1381c 5 238 60 +13821 9 240 60 +1382a 60 245 60 +1388a 7 247 60 +13891 1 247 60 +FUNC 13892 a57 0 dwarf2reader::CompilationUnit::ProcessAttribute(unsigned long long, char const*, dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm) +13892 24 299 60 +138b6 8a 302 60 +13940 1f 307 60 +1395f a 308 60 +13969 36 309 60 +1399f 5b 316 60 +139fa c 317 60 +13a06 5b 322 60 +13a61 e 323 60 +13a6f 55 328 60 +13ac4 e 329 60 +13ad2 55 334 60 +13b27 e 335 60 +13b35 6 338 60 +13b3b 9a 340 60 +13bd5 33 341 60 +13c08 25 340 60 +13c2d 5c 348 60 +13c89 10 349 60 +13c99 5c 354 60 +13cf5 10 355 60 +13d05 55 359 60 +13d5a 1e 360 60 +13d78 56 365 60 +13dce d 366 60 +13ddb 55 368 60 +13e30 1e 369 60 +13e4e 11 370 60 +13e5f 55 372 60 +13eb4 1e 373 60 +13ed2 29 378 60 +13efb 4a 380 60 +13f45 f 381 60 +13f54 29 385 60 +13f7d 4c 387 60 +13fc9 11 388 60 +13fda 1b 392 60 +13ff5 4c 394 60 +14041 11 395 60 +14052 22 399 60 +14074 4e 401 60 +140c2 15 402 60 +140d7 3c 406 60 +14113 1b 408 60 +1412e 54 409 60 +14182 f 411 60 +14191 9a 413 60 +1422b 24 414 60 +1424f 25 413 60 +14274 30 418 60 +142a4 30 420 60 +142d4 a 421 60 +142de b 422 60 +142e9 1 422 60 +142ea 1f 489 60 +14309 3a 491 60 +14343 a 492 60 +1434d 6 493 60 +14353 1 493 60 +14354 1f 489 60 +14373 3a 491 60 +143ad a 492 60 +143b7 6 493 60 +143bd 1 493 60 +FUNC 143be b5 0 dwarf2reader::CompilationUnit::ProcessDIE(unsigned long long, char const*, dwarf2reader::CompilationUnit::Abbrev const&) +143be 19 426 60 +143d7 13 427 60 +143ea 46 430 60 +14430 3a 427 60 +1446a 3 432 60 +1446d 6 433 60 +14473 1 433 60 +FUNC 14474 85 0 dwarf2reader::CompilationUnit::SkipDIE(char const*, dwarf2reader::CompilationUnit::Abbrev const&) +14474 c 122 60 +14480 13 123 60 +14493 27 126 60 +144ba 3a 123 60 +144f4 3 128 60 +144f7 2 129 60 +144f9 1 129 60 +FUNC 144fa be4 0 dwarf2reader::LineInfo::ProcessOneOpcode(dwarf2reader::ByteReader*, dwarf2reader::LineInfoHandler*, dwarf2reader::LineInfoHeader const&, char const*, dwarf2reader::LineStateMachine*, unsigned long*, unsigned long, bool*) +144fa 18 593 60 +14512 a 594 60 +1451c 18 596 60 +14534 8 597 60 +1453c 5 598 60 +14541 19 602 60 +1455a f 603 60 +14569 50 605 60 +145b9 46 607 60 +145ff e0 610 60 +146df 6 612 60 +146e5 22 615 60 +14707 22 616 60 +14729 7 617 60 +14730 b 618 60 +1473b f 619 60 +1474a 5a 623 60 +147a4 7 625 60 +147ab b 626 60 +147b6 f 627 60 +147c5 28 631 60 +147ed e 632 60 +147fb 144 635 60 +1493f 6 637 60 +14945 9e 640 60 +149e3 5 642 60 +149e8 22 644 60 +14a0a e 645 60 +14a18 1c 646 60 +14a34 2b 652 60 +14a5f b 653 60 +14a6a 22 658 60 +14a8c e 659 60 +14a9a 8 660 60 +14aa2 5 662 60 +14aa7 22 664 60 +14ac9 e 665 60 +14ad7 9 666 60 +14ae0 5 668 60 +14ae5 12 670 60 +14af7 5 672 60 +14afc 7 674 60 +14b03 5 676 60 +14b08 16 678 60 +14b1e 9 679 60 +14b27 d9 682 60 +14c00 6 684 60 +14c06 1f 687 60 +14c25 5 689 60 +14c2a 40 693 60 +14c6a d6 696 60 +14d40 6 698 60 +14d46 1c 701 60 +14d62 5 703 60 +14d67 1f 706 60 +14d86 d 707 60 +14d93 13 708 60 +14da6 26 710 60 +14dcc 5 711 60 +14dd1 50 713 60 +14e21 7 715 60 +14e28 b 716 60 +14e33 f 717 60 +14e42 18 725 60 +14e5a f 726 60 +14e69 5 728 60 +14e6e 6 730 60 +14e74 28 732 60 +14e9c d 733 60 +14ea9 22 735 60 +14ecb e 736 60 +14ed9 22 739 60 +14efb e 740 60 +14f09 22 743 60 +14f2b e 744 60 +14f39 a 746 60 +14f43 fd 748 60 +15040 a 758 60 +1504a 9 759 60 +15053 1c 761 60 +1506f d 762 60 +1507c e 763 60 +1508a 2e 759 60 +150b8 b 769 60 +150c3 10 770 60 +150d3 b 771 60 +FUNC 150de 14b 0 dwarf2reader::LineInfo::ReadLines() +150de e 773 60 +150ec 9 778 60 +150f5 17 782 60 +1510c 8 783 60 +15114 6 785 60 +1511a 9 787 60 +15123 5 788 60 +15128 19 789 60 +15141 5 790 60 +15146 4a 793 60 +15190 6 794 60 +15196 4a 796 60 +151e0 a 797 60 +151ea f 790 60 +151f9 15 788 60 +1520e 14 801 60 +15222 7 802 60 +15229 1 802 60 +FUNC 1522a 4fd 0 dwarf2reader::CompilationUnit::ReadAbbrevs() +1522a 18 60 60 +15242 e 61 60 +15250 58 65 60 +152a8 38 66 60 +152e0 44 65 60 +15324 2a 66 60 +1534e 45 68 60 +15393 16 69 60 +153a9 1d 75 60 +153c6 6 76 60 +153cc 40 77 60 +1540c b 80 60 +15417 1f 82 60 +15436 e 84 60 +15444 6 77 60 +1544a 1f 68 60 +15469 a 84 60 +15473 1d 79 60 +15490 6 86 60 +15496 a 87 60 +154a0 3d 89 60 +154dd 1f 90 60 +154fc a 91 60 +15506 6 92 60 +1550c 3d 94 60 +15549 1d 95 60 +15566 5 96 60 +1556b 3d 98 60 +155a8 1f 101 60 +155c7 a 102 60 +155d1 3d 104 60 +1560e 1f 105 60 +1562d a 106 60 +15637 c 107 60 +15643 6 111 60 +15649 6 112 60 +1564f 32 113 60 +15681 47 115 60 +156c8 30 116 60 +156f8 24 79 60 +1571c b 118 60 +15727 1 118 60 +FUNC 15728 5dc 0 dwarf2reader::LineInfo::ReadHeader() +15728 18 503 60 +15740 9 504 60 +15749 17 508 60 +15760 a 510 60 +1576a f 511 60 +15779 60 512 60 +157d9 44 516 60 +1581d 1e 518 60 +1583b 6 519 60 +15841 1e 521 60 +1585f 18 522 60 +15877 1d 524 60 +15894 5 525 60 +15899 20 527 60 +158b9 5 528 60 +158be c 530 60 +158ca 5 531 60 +158cf 1d 533 60 +158ec 5 534 60 +158f1 1d 536 60 +1590e 5 537 60 +15913 45 539 60 +15958 1f 540 60 +15977 19 541 60 +15990 15 542 60 +159a5 1f 539 60 +159c4 30 543 60 +159f4 5 544 60 +159f9 14 542 60 +15a0d e 548 60 +15a1b 7 549 60 +15a22 5 550 60 +15a27 6 551 60 +15a2d 8b 552 60 +15ab8 28 553 60 +15ae0 5 554 60 +15ae5 16 550 60 +15afb 25 552 60 +15b20 5 557 60 +15b25 e 560 60 +15b33 7 561 60 +15b3a 5 563 60 +15b3f 6 564 60 +15b45 28 565 60 +15b6d 22 567 60 +15b8f a 568 60 +15b99 22 570 60 +15bbb a 571 60 +15bc5 22 573 60 +15be7 a 574 60 +15bf1 ba 576 60 +15cab 5 577 60 +15cb0 16 563 60 +15cc6 25 576 60 +15ceb 5 580 60 +15cf0 9 582 60 +15cf9 b 583 60 +FUNC 15d04 3d 0 dwarf2reader::LineInfo::Start() +15d04 c 495 60 +15d10 b 496 60 +15d1b b 497 60 +15d26 19 498 60 +15d3f 2 499 60 +15d41 1 499 60 +FUNC 15d42 304 0 dwarf2reader::CompilationUnit::ProcessDIEs() +15d42 11 435 60 +15d53 9 436 60 +15d5c 9 441 60 +15d65 17 445 60 +15d7c 8 446 60 +15d84 6 448 60 +15d8a 6c 453 60 +15df6 8 455 60 +15dfe 16 453 60 +15e14 3 455 60 +15e17 2f 453 60 +15e46 29 458 60 +15e6f 22 460 60 +15e91 a 462 60 +15e9b a 465 60 +15ea5 1e 466 60 +15ec3 13 467 60 +15ed6 2b 468 60 +15f01 18 472 60 +15f19 9 473 60 +15f22 42 474 60 +15f64 1e 475 60 +15f82 2a 477 60 +15fac b 480 60 +15fb7 1e 481 60 +15fd5 26 483 60 +15ffb 1d 455 60 +16018 24 485 60 +1603c a 486 60 +FUNC 16046 35f 0 dwarf2reader::CompilationUnit::Start() +16046 18 249 60 +1605e 58 251 60 +160b6 35 252 60 +160eb 32 251 60 +1611d 2a 252 60 +16147 20 255 60 +16167 37 256 60 +1619e b 259 60 +161a9 f 264 60 +161b8 17 265 60 +161cf c 266 60 +161db a 268 60 +161e5 95 271 60 +1627a 11 276 60 +1628b b 279 60 +16296 58 282 60 +162ee 2f 283 60 +1631d 32 282 60 +1634f 14 284 60 +16363 1a 285 60 +1637d b 289 60 +16388 12 291 60 +1639a b 292 60 +163a5 1 292 60 +FUNC 163a6 3a 0 std::fill(unsigned char*, unsigned char*, unsigned char const&) +163a6 c 573 61 +163b2 9 576 61 +163bb 23 577 61 +163de 2 578 61 +FUNC 163e0 33 0 std::__deque_buf_size(unsigned long) +163e0 c 83 62 +163ec 27 84 62 +16413 1 84 62 +FUNC 16414 18 0 dwarf2reader::ByteReader::OffsetSize() const +16414 c 38 63 +16420 c 38 63 +FUNC 1642c 18 0 dwarf2reader::ByteReader::AddressSize() const +1642c c 41 63 +16438 c 41 63 +FUNC 16444 17 0 dwarf2reader::ByteReader::ReadOneByte(char const*) const +16444 c 10 64 +16450 9 11 64 +16459 2 12 64 +1645b 1 12 64 +FUNC 1645c 63 0 dwarf2reader::ByteReader::ReadTwoBytes(char const*) const +1645c c 14 64 +16468 d 15 64 +16475 e 16 64 +16483 b 17 64 +1648e 17 18 64 +164a5 18 20 64 +164bd 2 22 64 +164bf 1 22 64 +FUNC 164c0 98 0 dwarf2reader::ByteReader::ReadUnsignedLEB128(char const*, unsigned long*) const +164c0 e 59 64 +164ce e 60 64 +164dc 7 61 64 +164e3 7 62 64 +164ea e 66 64 +164f8 5 67 64 +164fd 38 69 64 +16535 6 71 64 +1653b 8 65 64 +16543 8 75 64 +1654b 6 77 64 +16551 7 78 64 +FUNC 16558 ee 0 dwarf2reader::ByteReader::ReadSignedLEB128(char const*, unsigned long*) const +16558 e 84 64 +16566 e 85 64 +16574 7 86 64 +1657b 7 87 64 +16582 e 91 64 +16590 5 92 64 +16595 44 93 64 +165d9 6 94 64 +165df 8 90 64 +165e7 14 97 64 +165fb 36 98 64 +16631 8 99 64 +16639 6 100 64 +1663f 7 101 64 +FUNC 16646 a2 0 dwarf2reader::ByteReader::ReadOffset(char const*) const +16646 13 103 64 +16659 3f 104 64 +16698 4a 105 64 +166e2 6 106 64 +FUNC 166e8 a2 0 dwarf2reader::ByteReader::ReadAddress(char const*) const +166e8 13 108 64 +166fb 3f 109 64 +1673a 4a 110 64 +16784 6 111 64 +FUNC 1678a 61 0 dwarf2reader::LineStateMachine::Reset(bool) +1678a 12 12 65 +1679c 9 13 65 +167a5 11 14 65 +167b6 11 15 65 +167c7 a 16 65 +167d1 a 17 65 +167db 7 18 65 +167e2 7 19 65 +167e9 2 20 65 +167eb 1 20 65 +FUNC 167ec 20 0 std::_List_const_iterator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> >::operator!=(std::_List_const_iterator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > const&) const +167ec c 253 66 +167f8 14 254 66 +FUNC 1680c 25 0 std::_List_const_iterator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> >::operator++(int) +1680c c 226 66 +16818 8 228 66 +16820 c 229 66 +1682c 5 230 66 +16831 1 230 66 +FUNC 16832 16 0 std::_List_const_iterator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> >::operator->() const +16832 c 215 66 +1683e a 216 66 +16848 c 190 67 +16854 a 190 67 +FUNC 1685e 13 0 std::auto_ptr<std::stack<unsigned long long, std::deque<unsigned long long, std::allocator<unsigned long long> > > >::operator->() const +1685e c 283 67 +1686a 7 286 67 +16871 1 286 67 +16872 c 65 68 +1687e 2 65 68 +16880 c 97 69 +1688c d 97 69 +16899 1 97 69 +1689a c 99 69 +168a6 14 100 69 +168ba c 97 69 +168c6 d 97 69 +168d3 1 97 69 +168d4 c 84 70 +168e0 17 85 70 +168f7 1 85 70 +FUNC 168f8 2d 0 std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> std::make_pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm>(dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm) +168f8 c 144 70 +16904 21 145 70 +16925 1 145 70 +16926 c 202 66 +16932 a 203 66 +FUNC 1693c 25 0 std::list<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm>, std::allocator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > >::begin() const +1693c c 588 70 +16948 19 589 66 +16961 1 589 66 +FUNC 16962 23 0 std::list<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm>, std::allocator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > >::end() const +16962 c 605 70 +1696e 17 606 66 +16985 1 606 66 +16986 c 65 68 +16992 2 65 68 +16994 c 72 68 +169a0 2 72 68 +169a2 c 97 69 +169ae d 97 69 +169bb 1 97 69 +169bc c 105 69 +169c8 d 105 69 +169d5 1 105 69 +169d6 c 105 69 +169e2 d 105 69 +169ef 1 105 69 +169f0 c 67 68 +169fc 2 67 68 +169fe c 99 69 +16a0a 14 100 69 +16a1e c 99 69 +16a2a 14 100 69 +16a3e c 129 62 +16a4a 30 131 62 +16a7a c 65 68 +16a86 2 65 68 +16a88 c 72 68 +16a94 2 72 68 +16a96 c 97 69 +16aa2 d 97 69 +16aaf 1 97 69 +16ab0 c 105 69 +16abc d 105 69 +16ac9 1 105 69 +16aca c 105 69 +16ad6 d 105 69 +16ae3 1 105 69 +16ae4 c 67 68 +16af0 2 67 68 +16af2 c 99 69 +16afe 14 100 69 +16b12 c 99 69 +16b1e 14 100 69 +FUNC 16b32 2b 0 std::_Vector_base<unsigned char, std::allocator<unsigned char> >::get_allocator() const +16b32 10 93 71 +16b42 1b 94 71 +16b5d 1 94 71 +16b6a 7 614 72 +16b71 1 614 72 +16b72 c 80 71 +16b7e d 80 71 +16b8b 1 80 71 +16b98 2 107 68 +FUNC 16b9a 2d 0 void std::_Destroy<unsigned char*, std::allocator<unsigned char> >(unsigned char*, unsigned char*, std::allocator<unsigned char>) +16b9a c 171 73 +16ba6 2 173 73 +16ba8 12 174 73 +16bba b 173 73 +16bc5 2 174 73 +16bc7 1 174 73 +16bc8 c 84 71 +16bd4 2f 85 71 +16c03 2 86 71 +16c05 1 86 71 +16c06 c 96 71 +16c12 12 97 71 +16c24 2 98 71 +FUNC 16c26 1f 0 std::_List_base<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm>, std::allocator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > >::_M_init() +16c26 c 338 73 +16c32 8 340 66 +16c3a b 341 66 +16c45 1 341 66 +16c46 c 105 69 +16c52 d 105 69 +16c5f 1 105 69 +16c60 c 125 66 +16c6c a 126 66 +FUNC 16c76 25 0 std::list<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm>, std::allocator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > >::begin() +16c76 c 579 73 +16c82 19 580 66 +16c9b 1 580 66 +FUNC 16c9c 23 0 std::list<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm>, std::allocator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > >::end() +16c9c c 597 73 +16ca8 17 597 66 +16cbf 1 597 66 +16cc0 c 603 72 +16ccc c 603 72 +FUNC 16cd8 2b 0 std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> >::begin() const +16cd8 c 342 73 +16ce4 1f 343 71 +16d03 1 343 71 +FUNC 16d04 2c 0 std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> >::end() const +16d04 c 360 73 +16d10 20 361 71 +16d3c 5 666 72 +16d41 1 666 72 +16d4f 31 759 72 +FUNC 16d80 3c 0 std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> >::size() const +16d80 c 402 73 +16d8c 30 403 71 +16dbc c 603 72 +16dc8 c 603 72 +FUNC 16dd4 23 0 std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> >::begin() +16dd4 c 333 73 +16de0 17 334 71 +16df7 1 334 71 +16e04 33 654 72 +16e37 1 654 72 +FUNC 16e38 26 0 std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> >::end() +16e38 c 351 73 +16e44 1a 352 71 +16e6a 7 614 72 +16e71 1 614 72 +FUNC 16e72 42 0 std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> >::operator[](unsigned long) +16e72 c 494 73 +16e7e 36 495 71 +FUNC 16eb4 13 0 std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> >::max_size() const +16eb4 c 407 73 +16ec0 7 408 71 +16ec7 1 408 71 +16ed4 5 666 72 +16ed9 1 666 72 +16ee6 d 623 72 +16ef3 5 624 72 +16ef8 c 382 62 +16f04 d 382 62 +16f11 1 382 62 +FUNC 16f12 2b 0 std::_Deque_base<unsigned long long, std::allocator<unsigned long long> >::get_allocator() const +16f12 10 360 73 +16f22 1b 361 62 +16f3d 1 361 62 +FUNC 16f3e 2d 0 std::deque<unsigned long long, std::allocator<unsigned long long> >::get_allocator() const +16f3e 10 764 73 +16f4e 1d 765 62 +16f6b 1 765 62 +FUNC 16f6c 13 0 std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*>::operator*() const +16f6c c 134 73 +16f78 7 135 62 +16f7f 1 135 62 +16f8c 2 107 68 +16f8e c 129 62 +16f9a 30 131 62 +FUNC 16fca 2c 0 std::deque<unsigned long long, std::allocator<unsigned long long> >::end() const +16fca 10 799 73 +16fda 1c 800 62 +FUNC 16ff6 2c 0 std::deque<unsigned long long, std::allocator<unsigned long long> >::begin() const +16ff6 10 781 73 +17006 1c 782 62 +FUNC 17022 2e 0 std::deque<unsigned long long, std::allocator<unsigned long long> >::end() +17022 10 790 73 +17032 1e 791 62 +FUNC 17050 3c 0 std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> >::_M_range_check(unsigned long) const +17050 13 515 73 +17063 15 517 71 +17078 14 518 71 +FUNC 1708c 32 0 std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> >::at(unsigned long) +1708c c 534 73 +17098 12 536 71 +170aa 14 537 71 +170ca 2e 104 68 +170f8 c 84 71 +17104 2f 85 71 +17133 2 86 71 +17135 1 86 71 +17136 c 96 71 +17142 12 97 71 +17154 2 98 71 +17156 c 603 72 +17162 c 603 72 +FUNC 1716e 23 0 std::vector<unsigned char, std::allocator<unsigned char> >::begin() +1716e c 333 73 +1717a 17 334 71 +17191 1 334 71 +1719e 27 654 72 +171c5 1 654 72 +FUNC 171c6 42 0 std::vector<unsigned char, std::allocator<unsigned char> >::operator[](unsigned long) +171c6 c 494 73 +171d2 36 495 71 +FUNC 17208 26 0 std::vector<unsigned char, std::allocator<unsigned char> >::end() +17208 c 351 73 +17214 1a 352 71 +1723a d 94 68 +17247 1 94 68 +FUNC 17248 2f 0 std::_Vector_base<unsigned char, std::allocator<unsigned char> >::_M_deallocate(unsigned char*, unsigned long) +17248 c 120 73 +17254 6 122 71 +1725a 1d 123 71 +17277 1 123 71 +17278 c 108 71 +17284 3a 109 71 +172be c 188 71 +172ca 12 189 71 +172dc 2 190 71 +172de c 272 71 +172ea 4b 273 71 +17335 1 273 71 +17336 13 62 74 +17349 10 62 74 +17359 a 63 74 +17363 25 64 74 +17388 1a 66 74 +173a2 13 62 74 +173b5 10 62 74 +173c5 a 63 74 +173cf 25 64 74 +173f4 1a 66 74 +1740e c 188 71 +1741a 12 189 71 +1742c 2 190 71 +1743b 31 759 72 +1746c c 65 68 +17478 2 65 68 +1747a c 103 69 +17486 d 103 69 +17493 1 103 69 +FUNC 17494 2d 0 std::list<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm>, std::allocator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > >::get_allocator() const +17494 10 570 74 +174a4 1d 571 66 +174c1 1 571 66 +174ce 2e 104 68 +FUNC 174fc 20 0 std::_List_iterator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> >::operator!=(std::_List_iterator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > const&) const +174fc c 172 74 +17508 14 173 66 +FUNC 1751c 1d 0 std::_List_const_iterator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> >::operator++() +1751c c 219 74 +17528 c 221 66 +17534 5 222 66 +17539 1 222 66 +FUNC 1753a 1d 0 std::_List_iterator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> >::operator++() +1753a c 138 74 +17546 c 140 66 +17552 5 141 66 +17557 1 141 66 +FUNC 17558 16 0 std::_List_const_iterator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> >::operator*() const +17558 c 211 74 +17564 a 212 66 +FUNC 1756e 16 0 std::_List_iterator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> >::operator*() const +1756e c 130 74 +1757a a 131 66 +FUNC 17584 20 0 std::_List_const_iterator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> >::operator==(std::_List_const_iterator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > const&) const +17584 c 249 74 +17590 14 250 66 +FUNC 175a4 35 0 bool __gnu_cxx::operator!=<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >(__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > > const&, __gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > > const&) +175a4 d 699 74 +175b1 28 700 72 +175d9 1 700 72 +FUNC 175da 4b 0 void std::_Destroy<__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, std::allocator<dwarf2reader::CompilationUnit::Abbrev> >(__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, __gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, std::allocator<dwarf2reader::CompilationUnit::Abbrev>) +175da c 171 74 +175e6 2 173 73 +175e8 1a 174 73 +17602 21 173 73 +17623 2 174 73 +17625 1 174 73 +17626 c 127 62 +17632 29 127 62 +1765b 1 127 62 +1765c c 388 62 +17668 41 389 62 +176a9 2 390 62 +176ab 1 390 62 +176b8 d 94 68 +176c5 1 94 68 +FUNC 176c6 20 0 bool std::operator==<unsigned long long, unsigned long long&, unsigned long long*>(std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*> const&, std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*> const&) +176c6 c 243 74 +176d2 14 244 62 +FUNC 176e6 26 0 bool std::operator!=<unsigned long long, unsigned long long&, unsigned long long*>(std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*> const&, std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*> const&) +176e6 c 256 74 +176f2 1a 257 62 +FUNC 1770c 1a 0 std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*>::_S_buffer_size() +1770c c 106 74 +17718 e 107 62 +FUNC 17726 3e 0 std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*>::_M_set_node(unsigned long long**) +17726 d 229 74 +17733 9 231 62 +1773c b 232 62 +17747 1d 233 62 +FUNC 17764 50 0 std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*>::operator++() +17764 c 142 74 +17770 d 144 62 +1777d f 145 62 +1778c 18 147 62 +177a4 b 148 62 +177af 5 150 62 +FUNC 177b4 4b 0 void std::_Destroy<std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*>, std::allocator<unsigned long long> >(std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*>, std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*>, std::allocator<unsigned long long>) +177b4 c 171 74 +177c0 2 173 73 +177c2 1a 174 73 +177dc 21 173 73 +177fd 2 174 73 +177ff 1 174 73 +FUNC 17800 50 0 std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*>::operator--() +17800 c 162 74 +1780c f 164 62 +1781b 18 166 62 +17833 b 167 62 +1783e d 169 62 +1784b 5 170 62 +FUNC 17850 39 0 std::deque<unsigned long long, std::allocator<unsigned long long> >::back() +17850 c 988 74 +1785c 15 990 62 +17871 b 991 62 +1787c d 992 62 +17889 1 992 62 +FUNC 1788a 19 0 std::stack<unsigned long long, std::deque<unsigned long long, std::allocator<unsigned long long> > >::top() +1788a c 163 75 +17896 d 166 75 +178a3 1 166 75 +FUNC 178a4 66 0 std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*>::difference_type std::operator-<unsigned long long, unsigned long long&, unsigned long long*, unsigned long long&, unsigned long long*>(std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*> const&, std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*> const&) +178a4 d 328 75 +178b1 59 333 62 +FUNC 1790a 26 0 std::deque<unsigned long long, std::allocator<unsigned long long> >::size() const +1790a c 840 75 +17916 1a 841 62 +1793c 36 662 72 +1797e 23 650 72 +179a1 1 650 72 +179a2 c 67 68 +179ae 2 67 68 +179b0 c 99 69 +179bc 14 100 69 +179d0 c 303 66 +179dc 12 304 66 +179ee 2 305 66 +179f0 c 326 66 +179fc 2f 327 66 +17a2b d 328 66 +17a38 c 457 66 +17a44 14 458 66 +17a58 c 211 74 +17a64 2d 211 74 +17a91 1 211 74 +17a9e 7 98 68 +17aa5 1 98 68 +17ab2 1d 85 68 +17acf 5 86 68 +17ad4 17 88 68 +17aeb 1 88 68 +FUNC 17aec 2a 0 std::_Vector_base<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> >::_M_allocate(unsigned long) +17aec c 116 75 +17af8 1e 117 71 +17b22 d 94 68 +17b2f 1 94 68 +FUNC 17b30 34 0 std::_Deque_base<unsigned long long, std::allocator<unsigned long long> >::_M_deallocate_node(unsigned long long*) +17b30 c 402 75 +17b3c 28 403 62 +FUNC 17b64 38 0 std::_Deque_base<unsigned long long, std::allocator<unsigned long long> >::_M_destroy_nodes(unsigned long long**, unsigned long long**) +17b64 c 504 75 +17b70 8 506 62 +17b78 14 507 62 +17b8c e 506 62 +17b9a 2 507 62 +FUNC 17b9c 62 0 std::deque<unsigned long long, std::allocator<unsigned long long> >::_M_pop_back_aux() +17b9c c 391 76 +17ba8 15 393 76 +17bbd 1b 394 76 +17bd8 f 395 76 +17be7 17 396 76 +FUNC 17bfe 4f 0 std::deque<unsigned long long, std::allocator<unsigned long long> >::pop_back() +17bfe c 1081 76 +17c0a 10 1083 62 +17c1a f 1086 62 +17c29 17 1087 62 +17c40 d 1090 62 +17c4d 1 1090 62 +FUNC 17c4e 19 0 std::stack<unsigned long long, std::deque<unsigned long long, std::allocator<unsigned long long> > >::pop() +17c4e c 205 76 +17c5a d 208 75 +17c67 1 208 75 +17c68 c 72 68 +17c74 2 72 68 +17c76 c 105 69 +17c82 d 105 69 +17c8f 1 105 69 +17c90 c 603 72 +17c9c c 603 72 +FUNC 17ca8 2b 0 std::vector<unsigned char, std::allocator<unsigned char> >::begin() const +17ca8 c 342 76 +17cb4 1f 343 71 +17cd3 1 343 71 +FUNC 17cd4 2c 0 std::vector<unsigned char, std::allocator<unsigned char> >::end() const +17cd4 c 360 76 +17ce0 20 361 71 +17d0c 5 666 72 +17d11 1 666 72 +17d1f 28 759 72 +17d47 1 759 72 +FUNC 17d48 3c 0 std::vector<unsigned char, std::allocator<unsigned char> >::size() const +17d48 c 402 76 +17d54 30 403 71 +17d90 d 623 72 +17d9d 5 624 72 +17dae 5 666 72 +17db3 1 666 72 +FUNC 17db4 35 0 bool __gnu_cxx::operator!=<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >(__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > > const&, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > > const&) +17db4 d 699 76 +17dc1 28 700 72 +17de9 1 700 72 +FUNC 17dea 4b 0 void std::_Destroy<__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, std::allocator<unsigned char> >(__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, std::allocator<unsigned char>) +17dea c 171 76 +17df6 2 173 73 +17df8 1a 174 73 +17e12 21 173 73 +17e33 2 174 73 +17e35 1 174 73 +17e43 28 759 72 +17e6b 1 759 72 +17e78 2a 662 72 +FUNC 17ea2 13 0 std::vector<unsigned char, std::allocator<unsigned char> >::max_size() const +17ea2 c 407 76 +17eae 7 408 71 +17eb5 1 408 71 +17ec2 16 650 72 +17ee4 7 98 68 +17eeb 1 98 68 +17ef8 1d 85 68 +17f15 5 86 68 +17f1a 10 88 68 +FUNC 17f2a 29 0 std::_List_base<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm>, std::allocator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > >::_M_get_node() +17f2a c 311 76 +17f36 1d 312 66 +17f53 1 312 66 +FUNC 17f54 5f 0 std::list<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm>, std::allocator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > >::_M_create_node(std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> const&) +17f54 d 435 76 +17f61 e 437 66 +17f6f 3c 440 66 +17fab 8 447 66 +17fb3 1 447 66 +FUNC 17fb4 35 0 std::list<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm>, std::allocator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > >::_M_insert(std::_List_iterator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> >, std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> const&) +17fb4 c 1149 76 +17fc0 15 1151 66 +17fd5 14 1152 66 +17fe9 1 1152 66 +FUNC 17fea 52 0 void std::list<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm>, std::allocator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > >::_M_insert_dispatch<std::_List_const_iterator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > >(std::_List_iterator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> >, std::_List_const_iterator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> >, std::_List_const_iterator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> >, __false_type) +17fea c 1126 66 +17ff6 2 1128 66 +17ff8 21 1129 66 +18019 21 1128 66 +1803a 2 1129 66 +FUNC 1803c 36 0 void std::list<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm>, std::allocator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > >::insert<std::_List_const_iterator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > >(std::_List_iterator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> >, std::_List_const_iterator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> >, std::_List_const_iterator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> >) +1803c c 838 66 +18048 2a 842 66 +18072 e 491 66 +18080 32 492 66 +180b2 64 493 66 +18116 c 211 74 +18122 3d 211 74 +1815f 1 211 74 +1816d 5c 104 68 +181c9 1 104 68 +FUNC 181ca 31 0 std::list<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm>, std::allocator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > >::push_back(std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> const&) +181ca c 772 76 +181d6 25 773 66 +181fb 1 773 66 +FUNC 181fc 69 0 void std::_Construct<dwarf2reader::CompilationUnit::Abbrev, dwarf2reader::CompilationUnit::Abbrev>(dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev const&) +181fc d 77 76 +18209 5c 81 73 +18265 1 81 73 +18272 7 98 68 +18279 1 98 68 +18286 1d 85 68 +182a3 5 86 68 +182a8 10 88 68 +182b8 c 65 68 +182c4 2 65 68 +182c6 c 103 69 +182d2 d 103 69 +182df 1 103 69 +FUNC 182e0 4d 0 std::_Deque_base<unsigned long long, std::allocator<unsigned long long> >::_M_get_map_allocator() const +182e0 11 394 76 +182f1 3c 395 62 +1832d 1 395 62 +FUNC 1832e 75 0 std::_Deque_base<unsigned long long, std::allocator<unsigned long long> >::_M_allocate_map(unsigned long) +1832e d 406 76 +1833b 68 407 62 +183a3 1 407 62 +FUNC 183a4 47 0 std::_Deque_base<unsigned long long, std::allocator<unsigned long long> >::_M_deallocate_map(unsigned long long**, unsigned long) +183a4 c 410 76 +183b0 3b 411 62 +183eb 1 411 62 +183ec c 424 62 +183f8 9 426 62 +18401 22 428 62 +18423 2b 430 62 +1844e c 714 62 +1845a 70 715 62 +184ca c 111 75 +184d6 d 111 75 +184e3 1 111 75 +184e4 c 259 67 +184f0 26 259 67 +18522 7 98 68 +18529 1 98 68 +18536 1d 85 68 +18553 5 86 68 +18558 10 88 68 +FUNC 18568 33 0 std::_Deque_base<unsigned long long, std::allocator<unsigned long long> >::_M_allocate_node() +18568 c 398 76 +18574 27 399 62 +1859b 1 399 62 +FUNC 1859c 82 0 std::_Deque_base<unsigned long long, std::allocator<unsigned long long> >::_M_create_nodes(unsigned long long**, unsigned long long**) +1859c d 486 76 +185a9 8 491 62 +185b1 12 492 62 +185c3 13 491 62 +185d6 b 494 62 +185e1 19 496 62 +185fa b 497 62 +18605 13 494 62 +18618 6 497 62 +FUNC 1861e 17b 0 std::_Deque_base<unsigned long long, std::allocator<unsigned long long> >::_M_initialize_map(unsigned long) +1861e d 447 76 +1862b 1e 450 62 +18649 2a 452 62 +18673 1c 454 62 +1868f 19 462 62 +186a8 c 463 62 +186b4 1e 466 62 +186d2 b 467 62 +186dd 1e 469 62 +186fb 9 470 62 +18704 a 471 62 +1870e b 472 62 +18719 13 467 62 +1872c 15 475 62 +18741 18 476 62 +18759 c 477 62 +18765 34 478 62 +18799 1 478 62 +1879a d 366 62 +187a7 12 367 62 +187b9 39 368 62 +187f2 c 645 62 +187fe 1c 646 62 +FUNC 1881a 4d 0 void std::__fill<true>::fill<__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, unsigned char>(__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, unsigned char const&) +1881a c 539 61 +18826 9 541 61 +1882f 2 542 61 +18831 13 543 61 +18844 21 542 61 +18865 2 543 61 +18867 1 543 61 +FUNC 18868 2b 0 void std::fill<__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, unsigned char>(__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, unsigned char const&) +18868 c 560 76 +18874 4 567 61 +18878 1b 568 61 +18893 1 568 61 +FUNC 18894 6a 0 std::list<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm>, std::allocator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > >::_M_erase(std::_List_iterator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> >) +18894 d 1157 76 +188a1 b 1159 66 +188ac 6 1160 66 +188b2 35 1161 66 +188e7 17 1162 66 +FUNC 188fe 37 0 std::list<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm>, std::allocator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > >::erase(std::_List_iterator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> >) +188fe c 95 77 +1890a 14 97 77 +1891e 12 98 77 +18930 5 99 77 +18935 1 99 77 +FUNC 18936 3e 0 std::list<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm>, std::allocator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > >::erase(std::_List_iterator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> >, std::_List_iterator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> >) +18936 c 883 77 +18942 2 885 66 +18944 15 886 66 +18959 16 885 66 +1896f 5 887 66 +FUNC 18974 129 0 std::list<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm>, std::allocator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > >::operator=(std::list<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm>, std::allocator<std::pair<dwarf2reader::DwarfAttribute, dwarf2reader::DwarfForm> > > const&) +18974 e 120 77 +18982 c 122 77 +1898e e 124 77 +1899c e 125 77 +189aa e 126 77 +189b8 e 127 77 +189c6 2 128 77 +189c8 20 130 77 +189e8 5a 128 77 +18a42 16 131 77 +18a58 1b 132 77 +18a73 20 134 77 +18a93 a 136 77 +18a9d 1 136 77 +FUNC 18a9e 4c 0 dwarf2reader::CompilationUnit::Abbrev::operator=(dwarf2reader::CompilationUnit::Abbrev const&) +18a9e c 211 77 +18aaa 40 211 74 +FUNC 18aea 52 0 dwarf2reader::CompilationUnit::Abbrev* std::__copy<false, std::random_access_iterator_tag>::copy<dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev*>(dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev*) +18aea c 280 61 +18af6 1a 283 61 +18b10 12 285 61 +18b22 4 286 61 +18b26 6 287 61 +18b2c b 283 61 +18b37 5 289 61 +FUNC 18b3c 2b 0 dwarf2reader::CompilationUnit::Abbrev* std::__copy_aux<dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev*>(dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev*) +18b3c c 307 77 +18b48 4 315 61 +18b4c 1b 317 61 +18b67 1 317 61 +18b76 56 354 61 +18bd8 4 384 61 +18bdc 4 385 61 +18be0 1b 387 61 +18bfb 1 387 61 +FUNC 18bfc ac 0 std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> >::erase(__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, __gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >) +18bfc d 122 78 +18c09 26 124 78 +18c2f 43 125 78 +18c72 2e 126 78 +18ca0 8 127 78 +FUNC 18ca8 54 0 dwarf2reader::CompilationUnit::Abbrev* std::__copy_backward<false, std::random_access_iterator_tag>::copy_b<dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev*>(dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev*) +18ca8 c 408 61 +18cb4 1a 411 61 +18cce 1e 412 61 +18cec b 411 61 +18cf7 5 413 61 +FUNC 18cfc 2b 0 dwarf2reader::CompilationUnit::Abbrev* std::__copy_backward_aux<dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev*>(dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev*) +18cfc c 432 78 +18d08 4 440 61 +18d0c 1b 443 61 +18d27 1 443 61 +18d36 56 482 61 +18d98 4 514 61 +18d9c 4 515 61 +18da0 1b 517 61 +18dbb 1 517 61 +FUNC 18dbc 4d 0 void std::__fill<false>::fill<__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, dwarf2reader::CompilationUnit::Abbrev>(__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, __gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, dwarf2reader::CompilationUnit::Abbrev const&) +18dbc c 526 61 +18dc8 2 528 61 +18dca 1c 529 61 +18de6 21 528 61 +18e07 2 529 61 +18e09 1 529 61 +FUNC 18e0a 2b 0 void std::fill<__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, dwarf2reader::CompilationUnit::Abbrev>(__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, __gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, dwarf2reader::CompilationUnit::Abbrev const&) +18e0a c 560 78 +18e16 4 567 61 +18e1a 1b 568 61 +18e35 1 568 61 +FUNC 18e36 3f 0 unsigned char* std::__copy<true, std::random_access_iterator_tag>::copy<unsigned char>(unsigned char const*, unsigned char const*, unsigned char*) +18e36 c 298 61 +18e42 22 300 61 +18e64 11 301 61 +18e75 1 301 61 +FUNC 18e76 2b 0 unsigned char* std::__copy_aux<unsigned char*, unsigned char*>(unsigned char*, unsigned char*, unsigned char*) +18e76 c 307 78 +18e82 4 315 61 +18e86 1b 317 61 +18ea1 1 317 61 +18eb0 56 354 61 +18f12 4 384 61 +18f16 4 385 61 +18f1a 1b 387 61 +18f35 1 387 61 +FUNC 18f36 a0 0 std::vector<unsigned char, std::allocator<unsigned char> >::erase(__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >) +18f36 d 122 78 +18f43 26 124 78 +18f69 43 125 78 +18fac 22 126 78 +18fce 8 127 78 +18fe2 7 98 68 +18fe9 1 98 68 +18ff6 1d 85 68 +19013 5 86 68 +19018 d 88 68 +19025 1 88 68 +FUNC 19026 2a 0 std::_Vector_base<unsigned char, std::allocator<unsigned char> >::_M_allocate(unsigned long) +19026 c 116 78 +19032 1e 117 71 +1905c 1b 74 79 +19077 1 74 79 +19084 23 113 79 +190a7 1 113 79 +190b4 1b 254 79 +190cf 1 254 79 +FUNC 190d0 19 0 void std::_Destroy<dwarf2reader::CompilationUnit::Abbrev>(dwarf2reader::CompilationUnit::Abbrev*) +190d0 c 106 79 +190dc d 107 73 +190e9 1 107 73 +FUNC 190ea 44 0 void std::__destroy_aux<__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > > >(__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, __gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, __false_type) +190ea c 119 79 +190f6 2 121 73 +190f8 13 122 73 +1910b 21 121 73 +1912c 2 122 73 +FUNC 1912e 28 0 void std::_Destroy<__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > > >(__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, __gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >) +1912e c 148 79 +1913a 1c 155 73 +FUNC 19156 8d 0 void std::__uninitialized_fill_n_aux<__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, unsigned long, dwarf2reader::CompilationUnit::Abbrev>(__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, unsigned long, dwarf2reader::CompilationUnit::Abbrev const&, __false_type) +19156 d 188 79 +19163 6 190 79 +19169 2 193 79 +1916b 1c 194 79 +19187 1b 193 79 +191a2 b 196 79 +191ad 12 198 79 +191bf b 199 79 +191ca 13 196 79 +191dd 6 199 79 +191e3 1 199 79 +FUNC 191e4 2f 0 void std::uninitialized_fill_n<__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, unsigned long, dwarf2reader::CompilationUnit::Abbrev>(__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, unsigned long, dwarf2reader::CompilationUnit::Abbrev const&) +191e4 c 214 79 +191f0 23 218 79 +19213 1 218 79 +FUNC 19214 27 0 void std::__uninitialized_fill_n_a<__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, unsigned long, dwarf2reader::CompilationUnit::Abbrev, dwarf2reader::CompilationUnit::Abbrev>(__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, unsigned long, dwarf2reader::CompilationUnit::Abbrev const&, std::allocator<dwarf2reader::CompilationUnit::Abbrev>) +19214 c 308 79 +19220 1b 310 79 +1923b 1 310 79 +19249 6 82 79 +1924f 2 85 79 +19251 24 86 79 +19275 2c 85 79 +192a1 b 87 79 +192ac b 89 79 +192b7 12 91 79 +192c9 b 92 79 +192d4 13 89 79 +192e7 9 92 79 +192fc 23 113 79 +1931f 1 113 79 +1932c 1b 254 79 +19347 1 254 79 +FUNC 19348 409 0 std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> >::_M_insert_aux(__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, dwarf2reader::CompilationUnit::Abbrev const&) +19348 14 249 79 +1935c 14 251 78 +19370 22 253 78 +19392 f 255 78 +193a1 12 256 78 +193b3 55 257 78 +19408 4b 260 78 +19453 e 264 78 +19461 15 265 78 +19476 e 266 78 +19484 1d 271 78 +194a1 8 272 78 +194a9 e 273 78 +194b7 27 275 78 +194de 6 276 78 +194e4 55 279 78 +19539 25 284 78 +1955e b 285 78 +19569 4f 286 78 +195b8 3 284 78 +195bb 13 279 78 +195ce e 286 78 +195dc 4d 298 78 +19629 36 299 78 +1965f 12 302 78 +19671 13 303 78 +19684 2e 304 78 +196b2 13 286 78 +196c5 b 292 78 +196d0 39 294 78 +19709 23 295 78 +1972c b 296 78 +19737 13 292 78 +1974a 7 304 78 +19751 1 304 78 +FUNC 19752 70 0 std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> >::push_back(dwarf2reader::CompilationUnit::Abbrev const&) +19752 c 602 79 +1975e 10 604 71 +1976e 1e 606 71 +1978c 11 607 71 +1979d 25 610 71 +FUNC 197c2 50 0 unsigned char* std::__copy_backward<true, std::random_access_iterator_tag>::copy_b<unsigned char>(unsigned char const*, unsigned char const*, unsigned char*) +197c2 d 422 61 +197cf f 424 61 +197de 24 425 61 +19802 10 426 61 +FUNC 19812 2b 0 unsigned char* std::__copy_backward_aux<unsigned char*, unsigned char*>(unsigned char*, unsigned char*, unsigned char*) +19812 c 432 79 +1981e 4 440 61 +19822 1b 443 61 +1983d 1 443 61 +1984c 56 482 61 +198ae 4 514 61 +198b2 4 515 61 +198b6 1b 517 61 +198d1 1 517 61 +FUNC 198d2 32 0 unsigned char* std::fill_n<unsigned long>(unsigned char*, unsigned long, unsigned char const&) +198d2 c 647 79 +198de 1e 649 61 +198fc 8 650 61 +FUNC 19904 27 0 void std::__uninitialized_fill_n_aux<unsigned char*, unsigned long, unsigned char>(unsigned char*, unsigned long, unsigned char const&, __true_type) +19904 c 182 79 +19910 1b 183 79 +1992b 1 183 79 +FUNC 1992c 2f 0 void std::uninitialized_fill_n<unsigned char*, unsigned long, unsigned char>(unsigned char*, unsigned long, unsigned char const&) +1992c c 214 79 +19938 23 218 79 +1995b 1 218 79 +FUNC 1995c 27 0 void std::__uninitialized_fill_n_a<unsigned char*, unsigned long, unsigned char, unsigned char>(unsigned char*, unsigned long, unsigned char const&, std::allocator<unsigned char>) +1995c c 308 79 +19968 1b 310 79 +19983 1 310 79 +FUNC 19984 27 0 void std::__destroy_aux<dwarf2reader::CompilationUnit::Abbrev*>(dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev*, __false_type) +19984 c 119 79 +19990 2 121 73 +19992 b 122 73 +1999d c 121 73 +199a9 2 122 73 +199ab 1 122 73 +FUNC 199ac 28 0 void std::_Destroy<dwarf2reader::CompilationUnit::Abbrev*>(dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev*) +199ac c 148 79 +199b8 1c 155 73 +FUNC 199d4 88 0 dwarf2reader::CompilationUnit::Abbrev* std::__uninitialized_copy_aux<dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev*>(dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev*, __false_type) +199d4 d 80 79 +199e1 6 82 79 +199e7 2 85 79 +199e9 12 86 79 +199fb 12 85 79 +19a0d b 87 79 +19a18 b 89 79 +19a23 12 91 79 +19a35 b 92 79 +19a40 13 89 79 +19a53 9 92 79 +FUNC 19a5c 2f 0 dwarf2reader::CompilationUnit::Abbrev* std::uninitialized_copy<dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev*>(dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev*) +19a5c c 108 79 +19a68 23 113 79 +19a8b 1 113 79 +FUNC 19a8c 27 0 dwarf2reader::CompilationUnit::Abbrev* std::__uninitialized_copy_a<dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev>(dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev*, std::allocator<dwarf2reader::CompilationUnit::Abbrev>) +19a8c c 252 79 +19a98 1b 254 79 +19ab3 1 254 79 +FUNC 19ab4 7e 0 void std::__uninitialized_fill_n_aux<dwarf2reader::CompilationUnit::Abbrev*, unsigned long, dwarf2reader::CompilationUnit::Abbrev>(dwarf2reader::CompilationUnit::Abbrev*, unsigned long, dwarf2reader::CompilationUnit::Abbrev const&, __false_type) +19ab4 d 188 79 +19ac1 6 190 79 +19ac7 2 193 79 +19ac9 12 194 79 +19adb 16 193 79 +19af1 b 196 79 +19afc 12 198 79 +19b0e b 199 79 +19b19 13 196 79 +19b2c 6 199 79 +FUNC 19b32 2f 0 void std::uninitialized_fill_n<dwarf2reader::CompilationUnit::Abbrev*, unsigned long, dwarf2reader::CompilationUnit::Abbrev>(dwarf2reader::CompilationUnit::Abbrev*, unsigned long, dwarf2reader::CompilationUnit::Abbrev const&) +19b32 c 214 79 +19b3e 23 218 79 +19b61 1 218 79 +FUNC 19b62 27 0 void std::__uninitialized_fill_n_a<dwarf2reader::CompilationUnit::Abbrev*, unsigned long, dwarf2reader::CompilationUnit::Abbrev, dwarf2reader::CompilationUnit::Abbrev>(dwarf2reader::CompilationUnit::Abbrev*, unsigned long, dwarf2reader::CompilationUnit::Abbrev const&, std::allocator<dwarf2reader::CompilationUnit::Abbrev>) +19b62 c 308 79 +19b6e 1b 310 79 +19b89 1 310 79 +FUNC 19b8a a5 0 dwarf2reader::CompilationUnit::Abbrev* std::__uninitialized_copy_aux<__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, dwarf2reader::CompilationUnit::Abbrev*>(__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, __gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, dwarf2reader::CompilationUnit::Abbrev*, __false_type) +19b8a d 80 79 +19b97 6 82 79 +19b9d 2 85 79 +19b9f 1a 86 79 +19bb9 27 85 79 +19be0 b 87 79 +19beb b 89 79 +19bf6 12 91 79 +19c08 b 92 79 +19c13 13 89 79 +19c26 9 92 79 +19c2f 1 92 79 +FUNC 19c30 2f 0 dwarf2reader::CompilationUnit::Abbrev* std::uninitialized_copy<__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, dwarf2reader::CompilationUnit::Abbrev*>(__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, __gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, dwarf2reader::CompilationUnit::Abbrev*) +19c30 c 108 79 +19c3c 23 113 79 +19c5f 1 113 79 +FUNC 19c60 27 0 dwarf2reader::CompilationUnit::Abbrev* std::__uninitialized_copy_a<__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, dwarf2reader::CompilationUnit::Abbrev*, dwarf2reader::CompilationUnit::Abbrev>(__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, __gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, dwarf2reader::CompilationUnit::Abbrev*, std::allocator<dwarf2reader::CompilationUnit::Abbrev>) +19c60 c 252 79 +19c6c 1b 254 79 +19c87 1 254 79 +FUNC 19c88 5f8 0 std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> >::_M_fill_insert(__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, unsigned long, dwarf2reader::CompilationUnit::Abbrev const&) +19c88 15 311 79 +19c9d b 313 78 +19ca8 2a 315 78 +19cd2 12 318 78 +19ce4 23 319 78 +19d07 15 320 78 +19d1c c 321 78 +19d28 5a 323 78 +19d82 1c 327 78 +19d9e 35 328 78 +19dd3 16 323 78 +19de9 30 330 78 +19e19 10 343 78 +19e29 48 334 78 +19e71 21 338 78 +19e92 3d 339 78 +19ecf 13 334 78 +19ee2 b 339 78 +19eed 1c 342 78 +19f09 1e 343 78 +19f27 13 339 78 +19f3a 24 343 78 +19f5e e 348 78 +19f6c 1e 349 78 +19f8a e 350 78 +19f98 1d 353 78 +19fb5 8 354 78 +19fbd e 355 78 +19fcb 27 357 78 +19ff2 6 358 78 +19ff8 4d 361 78 +1a045 40 365 78 +1a085 18 367 78 +1a09d 44 368 78 +1a0e1 3 365 78 +1a0e4 19 361 78 +1a0fd 13 365 78 +1a110 e 368 78 +1a11e 3e 379 78 +1a15c 36 381 78 +1a192 12 384 78 +1a1a4 13 385 78 +1a1b7 2e 386 78 +1a1e5 e 368 78 +1a1f3 b 372 78 +1a1fe 39 374 78 +1a237 23 376 78 +1a25a b 377 78 +1a265 13 372 78 +1a278 8 386 78 +FUNC 1a280 2e 0 std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> >::insert(__gnu_cxx::__normal_iterator<dwarf2reader::CompilationUnit::Abbrev*, std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> > >, unsigned long, dwarf2reader::CompilationUnit::Abbrev const&) +1a280 c 657 79 +1a28c 22 658 71 +FUNC 1a2ae ab 0 std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> >::resize(unsigned long, dwarf2reader::CompilationUnit::Abbrev const&) +1a2ae d 422 79 +1a2bb 15 424 71 +1a2d0 48 425 71 +1a318 41 427 71 +1a359 1 427 71 +FUNC 1a35a 63 0 std::vector<dwarf2reader::CompilationUnit::Abbrev, std::allocator<dwarf2reader::CompilationUnit::Abbrev> >::resize(unsigned long) +1a35a d 441 79 +1a367 56 442 71 +1a3bd 1 442 71 +FUNC 1a3be 13 0 std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>::operator*() const +1a3be c 134 79 +1a3ca 7 135 62 +1a3d1 1 135 62 +FUNC 1a3d2 3f 0 unsigned long long** std::__copy<true, std::random_access_iterator_tag>::copy<unsigned long long*>(unsigned long long* const*, unsigned long long* const*, unsigned long long**) +1a3d2 c 298 61 +1a3de 22 300 61 +1a400 11 301 61 +1a411 1 301 61 +FUNC 1a412 2b 0 unsigned long long** std::__copy_aux<unsigned long long**, unsigned long long**>(unsigned long long**, unsigned long long**, unsigned long long**) +1a412 c 307 79 +1a41e 4 315 61 +1a422 1b 317 61 +1a43d 1 317 61 +FUNC 1a43e 27 0 unsigned long long** std::__copy_normal<false, false>::copy_n<unsigned long long**, unsigned long long**>(unsigned long long**, unsigned long long**, unsigned long long**) +1a43e c 325 61 +1a44a 1b 326 61 +1a465 1 326 61 +FUNC 1a466 2f 0 unsigned long long** std::copy<unsigned long long**, unsigned long long**>(unsigned long long**, unsigned long long**, unsigned long long**) +1a466 c 376 79 +1a472 4 384 61 +1a476 4 385 61 +1a47a 1b 387 61 +1a495 1 387 61 +FUNC 1a496 60 0 unsigned long long** std::__copy_backward<true, std::random_access_iterator_tag>::copy_b<unsigned long long*>(unsigned long long* const*, unsigned long long* const*, unsigned long long**) +1a496 d 422 61 +1a4a3 12 424 61 +1a4b5 2e 425 61 +1a4e3 13 426 61 +FUNC 1a4f6 2b 0 unsigned long long** std::__copy_backward_aux<unsigned long long**, unsigned long long**>(unsigned long long**, unsigned long long**, unsigned long long**) +1a4f6 c 432 79 +1a502 4 440 61 +1a506 1b 443 61 +1a521 1 443 61 +FUNC 1a522 27 0 unsigned long long** std::__copy_backward_normal<false, false>::copy_b_n<unsigned long long**, unsigned long long**>(unsigned long long**, unsigned long long**, unsigned long long**) +1a522 c 451 61 +1a52e 1b 452 61 +1a549 1 452 61 +FUNC 1a54a 2f 0 unsigned long long** std::copy_backward<unsigned long long**, unsigned long long**>(unsigned long long**, unsigned long long**, unsigned long long**) +1a54a c 504 79 +1a556 4 514 61 +1a55a 4 515 61 +1a55e 1b 517 61 +1a579 1 517 61 +FUNC 1a57a 1df 0 std::deque<unsigned long long, std::allocator<unsigned long long> >::_M_reallocate_map(unsigned long, bool) +1a57a 13 723 79 +1a58d 1b 726 76 +1a5a8 9 727 76 +1a5b1 13 730 76 +1a5c4 39 732 76 +1a5fd b 735 76 +1a608 27 736 76 +1a62f 2f 740 76 +1a65e 26 748 76 +1a684 15 750 76 +1a699 36 751 76 +1a6cf 22 753 76 +1a6f1 1e 756 76 +1a70f 8 758 76 +1a717 9 759 76 +1a720 15 762 76 +1a735 24 763 76 +1a759 1 763 76 +FUNC 1a75a 59 0 std::deque<unsigned long long, std::allocator<unsigned long long> >::_M_reserve_map_at_back(unsigned long) +1a75a e 1443 79 +1a768 2a 1445 62 +1a792 21 1447 62 +1a7b3 1 1447 62 +FUNC 1a7b4 8c 0 std::deque<unsigned long long, std::allocator<unsigned long long> >::_M_push_back_aux(unsigned long long const&) +1a7b4 c 345 79 +1a7c0 e 347 76 +1a7ce 13 348 76 +1a7e1 18 349 76 +1a7f9 1e 352 76 +1a817 1b 353 76 +1a832 c 355 76 +1a83e 2 360 76 +FUNC 1a840 62 0 std::deque<unsigned long long, std::allocator<unsigned long long> >::push_back(unsigned long long const&) +1a840 c 1039 79 +1a84c 13 1041 62 +1a85f 1e 1044 62 +1a87d 11 1045 62 +1a88e 14 1048 62 +FUNC 1a8a2 20 0 std::stack<unsigned long long, std::deque<unsigned long long, std::allocator<unsigned long long> > >::push(unsigned long long const&) +1a8a2 c 190 79 +1a8ae 14 191 75 +FUNC 1a8c2 27 0 unsigned char* std::__copy_normal<false, false>::copy_n<unsigned char*, unsigned char*>(unsigned char*, unsigned char*, unsigned char*) +1a8c2 c 325 61 +1a8ce 1b 326 61 +1a8e9 1 326 61 +FUNC 1a8ea 2f 0 unsigned char* std::copy<unsigned char*, unsigned char*>(unsigned char*, unsigned char*, unsigned char*) +1a8ea c 376 79 +1a8f6 4 384 61 +1a8fa 4 385 61 +1a8fe 1b 387 61 +1a919 1 387 61 +FUNC 1a91a 27 0 unsigned char* std::__uninitialized_copy_aux<unsigned char*, unsigned char*>(unsigned char*, unsigned char*, unsigned char*, __true_type) +1a91a c 73 79 +1a926 1b 74 79 +1a941 1 74 79 +FUNC 1a942 2f 0 unsigned char* std::uninitialized_copy<unsigned char*, unsigned char*>(unsigned char*, unsigned char*, unsigned char*) +1a942 c 108 79 +1a94e 23 113 79 +1a971 1 113 79 +FUNC 1a972 27 0 unsigned char* std::__uninitialized_copy_a<unsigned char*, unsigned char*, unsigned char>(unsigned char*, unsigned char*, unsigned char*, std::allocator<unsigned char>) +1a972 c 252 79 +1a97e 1b 254 79 +1a999 1 254 79 +FUNC 1a99a 40 0 unsigned char* std::__copy_normal<true, false>::copy_n<__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, unsigned char*>(__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, unsigned char*) +1a99a d 334 61 +1a9a7 33 335 61 +FUNC 1a9da 2f 0 unsigned char* std::copy<__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, unsigned char*>(__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, unsigned char*) +1a9da c 376 79 +1a9e6 4 384 61 +1a9ea 4 385 61 +1a9ee 1b 387 61 +1aa09 1 387 61 +FUNC 1aa0a 27 0 unsigned char* std::__uninitialized_copy_aux<__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, unsigned char*>(__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, unsigned char*, __true_type) +1aa0a c 73 79 +1aa16 1b 74 79 +1aa31 1 74 79 +FUNC 1aa32 2f 0 unsigned char* std::uninitialized_copy<__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, unsigned char*>(__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, unsigned char*) +1aa32 c 108 79 +1aa3e 23 113 79 +1aa61 1 113 79 +FUNC 1aa62 27 0 unsigned char* std::__uninitialized_copy_a<__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, unsigned char*, unsigned char>(__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, __gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, unsigned char*, std::allocator<unsigned char>) +1aa62 c 252 79 +1aa6e 1b 254 79 +1aa89 1 254 79 +1aa96 9 616 61 +1aa9f 2 617 61 +1aaa1 13 618 61 +1aab4 16 617 61 +1aaca 5 619 61 +1aacf 1 619 61 +1aadc 4 641 61 +1aae0 1b 642 61 +1aafb 1 642 61 +FUNC 1aafc 27 0 void std::__uninitialized_fill_n_aux<__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, unsigned long, unsigned char>(__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, unsigned long, unsigned char const&, __true_type) +1aafc c 182 79 +1ab08 1b 183 79 +1ab23 1 183 79 +FUNC 1ab24 2f 0 void std::uninitialized_fill_n<__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, unsigned long, unsigned char>(__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, unsigned long, unsigned char const&) +1ab24 c 214 79 +1ab30 23 218 79 +1ab53 1 218 79 +FUNC 1ab54 27 0 void std::__uninitialized_fill_n_a<__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, unsigned long, unsigned char, unsigned char>(__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, unsigned long, unsigned char const&, std::allocator<unsigned char>) +1ab54 c 308 79 +1ab60 1b 310 79 +1ab7b 1 310 79 +FUNC 1ab7c 45a 0 std::vector<unsigned char, std::allocator<unsigned char> >::_M_fill_insert(__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, unsigned long, unsigned char const&) +1ab7c 14 311 79 +1ab90 b 313 78 +1ab9b 21 315 78 +1abbc 9 318 78 +1abc5 23 319 78 +1abe8 15 320 78 +1abfd c 321 78 +1ac09 4e 323 78 +1ac57 11 327 78 +1ac68 30 328 78 +1ac98 35 330 78 +1accd 48 334 78 +1ad15 14 338 78 +1ad29 43 339 78 +1ad6c 11 342 78 +1ad7d 1e 343 78 +1ad9b e 348 78 +1ada9 1e 349 78 +1adc7 e 350 78 +1add5 1d 353 78 +1adf2 8 354 78 +1adfa e 355 78 +1ae08 27 357 78 +1ae2f 6 358 78 +1ae35 4d 361 78 +1ae82 40 365 78 +1aec2 18 367 78 +1aeda 4d 368 78 +1af27 3e 379 78 +1af65 2d 381 78 +1af92 12 384 78 +1afa4 13 385 78 +1afb7 1f 386 78 +FUNC 1afd6 2e 0 std::vector<unsigned char, std::allocator<unsigned char> >::insert(__gnu_cxx::__normal_iterator<unsigned char*, std::vector<unsigned char, std::allocator<unsigned char> > >, unsigned long, unsigned char const&) +1afd6 c 657 79 +1afe2 22 658 71 +FUNC 1b004 ab 0 std::vector<unsigned char, std::allocator<unsigned char> >::resize(unsigned long, unsigned char const&) +1b004 d 422 79 +1b011 15 424 71 +1b026 48 425 71 +1b06e 41 427 71 +1b0af 1 427 71 +FUNC 1b0b0 2b 0 std::vector<unsigned char, std::allocator<unsigned char> >::resize(unsigned long) +1b0b0 c 441 79 +1b0bc 1f 442 71 +1b0db 1 442 71 +FUNC 1b0dc 1a 0 std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>::_S_buffer_size() +1b0dc c 106 79 +1b0e8 e 107 62 +FUNC 1b0f6 66 0 std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>::difference_type std::operator-<unsigned long long, unsigned long long const&, unsigned long long const*, unsigned long long const&, unsigned long long const*>(std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*> const&, std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*> const&) +1b0f6 d 328 79 +1b103 59 333 62 +FUNC 1b15c 3e 0 std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>::_M_set_node(unsigned long long**) +1b15c d 229 79 +1b169 9 231 62 +1b172 b 232 62 +1b17d 1d 233 62 +FUNC 1b19a 50 0 std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>::operator++() +1b19a c 142 79 +1b1a6 d 144 62 +1b1b3 f 145 62 +1b1c2 18 147 62 +1b1da b 148 62 +1b1e5 5 150 62 +FUNC 1b1ea 84 0 std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*> std::__copy<false, std::random_access_iterator_tag>::copy<std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>, std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*> >(std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>, std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>, std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*>) +1b1ea e 280 61 +1b1f8 17 283 61 +1b20f 20 285 61 +1b22f b 286 61 +1b23a b 287 61 +1b245 b 283 61 +1b250 1e 289 61 +FUNC 1b26e 7e 0 std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*> std::__copy_aux<std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>, std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*> >(std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>, std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>, std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*>) +1b26e 11 307 79 +1b27f 4 315 61 +1b283 69 317 61 +FUNC 1b2ec 7a 0 std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*> std::__copy_normal<false, false>::copy_n<std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>, std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*> >(std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>, std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>, std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*>) +1b2ec 11 325 61 +1b2fd 69 326 61 +FUNC 1b366 82 0 std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*> std::copy<std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>, std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*> >(std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>, std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>, std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*>) +1b366 11 376 79 +1b377 4 384 61 +1b37b 4 385 61 +1b37f 69 387 61 +FUNC 1b3e8 7a 0 std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*> std::__uninitialized_copy_aux<std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>, std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*> >(std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>, std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>, std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*>, __true_type) +1b3e8 11 73 79 +1b3f9 69 74 79 +FUNC 1b462 82 0 std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*> std::uninitialized_copy<std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>, std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*> >(std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>, std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>, std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*>) +1b462 11 108 79 +1b473 71 113 79 +FUNC 1b4e4 7a 0 std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*> std::__uninitialized_copy_a<std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>, std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*>, unsigned long long>(std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>, std::_Deque_iterator<unsigned long long, unsigned long long const&, unsigned long long const*>, std::_Deque_iterator<unsigned long long, unsigned long long&, unsigned long long*>, std::allocator<unsigned long long>) +1b4e4 11 252 79 +1b4f5 69 254 79 +1b55e 10 679 62 +1b56e 64 680 62 +1b5d2 e8 681 62 +1b6ba c 143 75 +1b6c6 14 144 75 +1b6da 6 144 75 +FUNC 1b6e0 4d 0 __eprintf +1b6e0 6 1826 80 +1b6e6 3 1832 80 +1b6e9 c 1826 80 +1b6f5 29 1832 80 +1b71e a 1837 80 +1b728 5 1838 80 +1b72d e8d3 1838 80 diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/handler/ucontext_compat.h b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/ucontext_compat.h new file mode 100644 index 000000000..1e4b752e5 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/handler/ucontext_compat.h @@ -0,0 +1,47 @@ +// Copyright 2013 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_MAC_HANDLER_UCONTEXT_COMPAT_H_ +#define CLIENT_MAC_HANDLER_UCONTEXT_COMPAT_H_ + +#include <sys/ucontext.h> + +// The purpose of this file is to work around the fact that ucontext_t's +// uc_mcontext member is an mcontext_t rather than an mcontext64_t on ARM64. +#if defined(__aarch64__) +// <sys/ucontext.h> doesn't include the below file. +#include <sys/_types/_ucontext64.h> +typedef ucontext64_t breakpad_ucontext_t; +#define breakpad_uc_mcontext uc_mcontext64 +#else +typedef ucontext_t breakpad_ucontext_t; +#define breakpad_uc_mcontext uc_mcontext +#endif // defined(__aarch64__) + +#endif // CLIENT_MAC_HANDLER_UCONTEXT_COMPAT_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/Breakpad.xib b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/Breakpad.xib new file mode 100644 index 000000000..7966f895c --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/Breakpad.xib @@ -0,0 +1,1140 @@ +<?xml version="1.0" encoding="UTF-8"?> +<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10"> + <data> + <int key="IBDocument.SystemTarget">1050</int> + <string key="IBDocument.SystemVersion">10F569</string> + <string key="IBDocument.InterfaceBuilderVersion">762</string> + <string key="IBDocument.AppKitVersion">1038.29</string> + <string key="IBDocument.HIToolboxVersion">461.00</string> + <object class="NSMutableDictionary" key="IBDocument.PluginVersions"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys" id="0"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + </object> + <object class="NSMutableArray" key="IBDocument.EditedObjectIDs"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <reference key="IBDocument.PluginDependencies" ref="0"/> + <object class="NSMutableDictionary" key="IBDocument.Metadata"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference key="dict.sortedKeys" ref="0"/> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + </object> + <object class="NSMutableArray" key="IBDocument.RootObjects" id="504246249"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSCustomObject" id="273934324"> + <string key="NSClassName">Reporter</string> + </object> + <object class="NSCustomObject" id="388635980"> + <string key="NSClassName">FirstResponder</string> + </object> + <object class="NSCustomObject" id="220995958"> + <string key="NSClassName">NSApplication</string> + </object> + <object class="NSWindowTemplate" id="762998835"> + <int key="NSWindowStyleMask">1</int> + <int key="NSWindowBacking">2</int> + <string key="NSWindowRect">{{72, 251}, {490, 489}}</string> + <int key="NSWTFlags">536871936</int> + <string key="NSWindowTitle"/> + <string key="NSWindowClass">NSWindow</string> + <nil key="NSViewClass"/> + <string key="NSWindowContentMaxSize">{1.79769e+308, 1.79769e+308}</string> + <string key="NSWindowContentMinSize">{72, 5}</string> + <object class="NSView" key="NSWindowView" id="197525436"> + <nil key="NSNextResponder"/> + <int key="NSvFlags">264</int> + <object class="NSMutableArray" key="NSSubviews"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSBox" id="469837363"> + <reference key="NSNextResponder" ref="197525436"/> + <int key="NSvFlags">272</int> + <object class="NSMutableArray" key="NSSubviews"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSView" id="966817352"> + <reference key="NSNextResponder" ref="469837363"/> + <int key="NSvFlags">256</int> + <object class="NSMutableArray" key="NSSubviews"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSTextField" id="997378142"> + <reference key="NSNextResponder" ref="966817352"/> + <int key="NSvFlags">290</int> + <string key="NSFrame">{{17, 36}, {456, 70}}</string> + <reference key="NSSuperview" ref="966817352"/> + <bool key="NSEnabled">YES</bool> + <object class="NSTextFieldCell" key="NSCell" id="509794736"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">272760832</int> + <string key="NSContents">Providing your email address is optional and will allow us contact you in case we need more details. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed arcu urna, pulvinar sit amet, tincidunt ac, fermentum ut, ligula. Quisque mi. Duis lectus. Vestibulum velit. Morbi turpis. Nunc at diam consectetur turpis volutpat tristique. Donec quis diam. Suspendisse scelerisque.</string> + <object class="NSFont" key="NSSupport" id="26"> + <string key="NSName">LucidaGrande</string> + <double key="NSSize">11</double> + <int key="NSfFlags">3100</int> + </object> + <reference key="NSControlView" ref="997378142"/> + <object class="NSColor" key="NSBackgroundColor" id="420457920"> + <int key="NSColorSpace">6</int> + <string key="NSCatalogName">System</string> + <string key="NSColorName">controlColor</string> + <object class="NSColor" key="NSColor"> + <int key="NSColorSpace">3</int> + <bytes key="NSWhite">MC42NjY2NjY2NjY3AA</bytes> + </object> + </object> + <object class="NSColor" key="NSTextColor" id="800255527"> + <int key="NSColorSpace">6</int> + <string key="NSCatalogName">System</string> + <string key="NSColorName">controlTextColor</string> + <object class="NSColor" key="NSColor" id="908763363"> + <int key="NSColorSpace">3</int> + <bytes key="NSWhite">MAA</bytes> + </object> + </object> + </object> + </object> + <object class="NSTextField" id="975305147"> + <reference key="NSNextResponder" ref="966817352"/> + <int key="NSvFlags">290</int> + <string key="NSFrame">{{87, 9}, {195, 19}}</string> + <reference key="NSSuperview" ref="966817352"/> + <bool key="NSEnabled">YES</bool> + <object class="NSTextFieldCell" key="NSCell" id="592393645"> + <int key="NSCellFlags">-1804468671</int> + <int key="NSCellFlags2">272761856</int> + <string key="NSContents"/> + <reference key="NSSupport" ref="26"/> + <string key="NSPlaceholderString">optional</string> + <reference key="NSControlView" ref="975305147"/> + <bool key="NSDrawsBackground">YES</bool> + <object class="NSColor" key="NSBackgroundColor" id="128478752"> + <int key="NSColorSpace">6</int> + <string key="NSCatalogName">System</string> + <string key="NSColorName">textBackgroundColor</string> + <object class="NSColor" key="NSColor"> + <int key="NSColorSpace">3</int> + <bytes key="NSWhite">MQA</bytes> + </object> + </object> + <object class="NSColor" key="NSTextColor" id="734930533"> + <int key="NSColorSpace">6</int> + <string key="NSCatalogName">System</string> + <string key="NSColorName">textColor</string> + <reference key="NSColor" ref="908763363"/> + </object> + </object> + </object> + <object class="NSTextField" id="268211031"> + <reference key="NSNextResponder" ref="966817352"/> + <int key="NSvFlags">292</int> + <string key="NSFrame">{{17, 11}, {65, 14}}</string> + <reference key="NSSuperview" ref="966817352"/> + <bool key="NSEnabled">YES</bool> + <object class="NSTextFieldCell" key="NSCell" id="461570326"> + <int key="NSCellFlags">68288064</int> + <int key="NSCellFlags2">71435264</int> + <string key="NSContents">EmailLabel:</string> + <reference key="NSSupport" ref="26"/> + <reference key="NSControlView" ref="268211031"/> + <reference key="NSBackgroundColor" ref="420457920"/> + <reference key="NSTextColor" ref="800255527"/> + </object> + </object> + <object class="NSButton" id="538303250"> + <reference key="NSNextResponder" ref="966817352"/> + <int key="NSvFlags">289</int> + <string key="NSFrame">{{456, 10}, {16, 17}}</string> + <reference key="NSSuperview" ref="966817352"/> + <bool key="NSEnabled">YES</bool> + <object class="NSButtonCell" key="NSCell" id="778004767"> + <int key="NSCellFlags">-2080244224</int> + <int key="NSCellFlags2">0</int> + <string key="NSContents">Privacy Policy</string> + <object class="NSFont" key="NSSupport" id="222882491"> + <string key="NSName">LucidaGrande</string> + <double key="NSSize">13</double> + <int key="NSfFlags">1044</int> + </object> + <reference key="NSControlView" ref="538303250"/> + <int key="NSButtonFlags">-2040250113</int> + <int key="NSButtonFlags2">36</int> + <object class="NSCustomResource" key="NSNormalImage"> + <string key="NSClassName">NSImage</string> + <string key="NSResourceName">goArrow</string> + </object> + <string key="NSAlternateContents"/> + <string key="NSKeyEquivalent"/> + <int key="NSPeriodicDelay">400</int> + <int key="NSPeriodicInterval">75</int> + </object> + </object> + <object class="NSTextField" id="655227981"> + <reference key="NSNextResponder" ref="966817352"/> + <int key="NSvFlags">289</int> + <string key="NSFrame">{{355, 11}, {100, 14}}</string> + <reference key="NSSuperview" ref="966817352"/> + <bool key="NSEnabled">YES</bool> + <object class="NSTextFieldCell" key="NSCell" id="1012850565"> + <int key="NSCellFlags">68288064</int> + <int key="NSCellFlags2">4326400</int> + <string key="NSContents">PrivacyPolicyLabel</string> + <reference key="NSSupport" ref="26"/> + <reference key="NSControlView" ref="655227981"/> + <reference key="NSBackgroundColor" ref="420457920"/> + <reference key="NSTextColor" ref="800255527"/> + </object> + </object> + </object> + <string key="NSFrameSize">{490, 114}</string> + <reference key="NSSuperview" ref="469837363"/> + </object> + </object> + <string key="NSFrame">{{0, 51}, {490, 114}}</string> + <reference key="NSSuperview" ref="197525436"/> + <string key="NSOffsets">{0, 0}</string> + <object class="NSTextFieldCell" key="NSTitleCell"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">0</int> + <string key="NSContents">Title</string> + <object class="NSFont" key="NSSupport" id="668643277"> + <string key="NSName">LucidaGrande</string> + <double key="NSSize">11</double> + <int key="NSfFlags">16</int> + </object> + <reference key="NSBackgroundColor" ref="128478752"/> + <object class="NSColor" key="NSTextColor"> + <int key="NSColorSpace">3</int> + <bytes key="NSWhite">MCAwLjgwMDAwMDAxAA</bytes> + </object> + </object> + <reference key="NSContentView" ref="966817352"/> + <int key="NSBorderType">0</int> + <int key="NSBoxType">3</int> + <int key="NSTitlePosition">0</int> + <bool key="NSTransparent">NO</bool> + </object> + <object class="NSButton" id="219938755"> + <reference key="NSNextResponder" ref="197525436"/> + <int key="NSvFlags">289</int> + <string key="NSFrame">{{330, 12}, {146, 32}}</string> + <reference key="NSSuperview" ref="197525436"/> + <bool key="NSEnabled">YES</bool> + <object class="NSButtonCell" key="NSCell" id="733475259"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">134217728</int> + <string key="NSContents">SendReportLabel</string> + <reference key="NSSupport" ref="222882491"/> + <reference key="NSControlView" ref="219938755"/> + <int key="NSButtonFlags">-2038284033</int> + <int key="NSButtonFlags2">129</int> + <reference key="NSAlternateImage" ref="222882491"/> + <string key="NSAlternateContents"/> + <string type="base64-UTF8" key="NSKeyEquivalent">DQ</string> + <int key="NSPeriodicDelay">200</int> + <int key="NSPeriodicInterval">25</int> + </object> + </object> + <object class="NSButton" id="409721323"> + <reference key="NSNextResponder" ref="197525436"/> + <int key="NSvFlags">289</int> + <string key="NSFrame">{{214, 12}, {116, 32}}</string> + <reference key="NSSuperview" ref="197525436"/> + <bool key="NSEnabled">YES</bool> + <object class="NSButtonCell" key="NSCell" id="586160416"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">134217728</int> + <string key="NSContents">CancelLabel</string> + <reference key="NSSupport" ref="222882491"/> + <reference key="NSControlView" ref="409721323"/> + <int key="NSButtonFlags">-2038284033</int> + <int key="NSButtonFlags2">129</int> + <reference key="NSAlternateImage" ref="222882491"/> + <string key="NSAlternateContents"/> + <string type="base64-UTF8" key="NSKeyEquivalent">Gw</string> + <int key="NSPeriodicDelay">200</int> + <int key="NSPeriodicInterval">25</int> + </object> + </object> + <object class="NSBox" id="468151514"> + <reference key="NSNextResponder" ref="197525436"/> + <int key="NSvFlags">256</int> + <object class="NSMutableArray" key="NSSubviews"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSView" id="1059038623"> + <reference key="NSNextResponder" ref="468151514"/> + <int key="NSvFlags">256</int> + <object class="NSMutableArray" key="NSSubviews"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSTextField" id="375247105"> + <reference key="NSNextResponder" ref="1059038623"/> + <int key="NSvFlags">266</int> + <string key="NSFrame">{{17, 83}, {456, 154}}</string> + <reference key="NSSuperview" ref="1059038623"/> + <bool key="NSEnabled">YES</bool> + <object class="NSTextFieldCell" key="NSCell" id="188082030"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">272760832</int> + <string type="base64-UTF8" key="NSContents">VGhlIHN5c3RlbSBhbmQgb3RoZXIgYXBwbGljYXRpb25zIGhhdmUgbm90IGJlZW4gYWZmZWN0ZWQuIEEg +cmVwb3J0IGhhcyBiZWVuIGNyZWF0ZWQgdGhhdCB5b3UgY2FuIHNlbmQgdG8gPFJlYWxseSBMb25nIENv +bXBhbnkgTmFtZT4gdG8gaGVscCBpZGVudGlmeSB0aGUgcHJvYmxlbS4gTG9yZW0gaXBzdW0gZG9sb3Ig +c2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gU2VkIGFyY3UgdXJuYSwgcHVsdmlu +YXIgc2l0IGFtZXQsIHRpbmNpZHVudCBhYywgZmVybWVudHVtIHV0LCBsaWd1bGEuIFF1aXNxdWUgbWku +IER1aXMgbGVjdHVzLiBWZXN0aWJ1bHVtIHZlbGl0LiBNb3JiaSB0dXJwaXMuIE51bmMgYXQgZGlhbSBj +b25zZWN0ZXR1ciB0dXJwaXMgdm9sdXRwYXQgdHJpc3RpcXVlLiBEb25lYyBxdWlzIGRpYW0uIFN1c3Bl +bmRpc3NlIHNjZWxlcmlzcXVlLiBRdWlzcXVlIHB1bHZpbmFyIG1pIGlkIHB1cnVzLiBFdGlhbSB2aXRh +ZSB0dXJwaXMgdml0YWUgbmVxdWUgcG9ydGEgY29uZ3VlLgoKUGxlYXNlIGhlbHAgdXMgZml4IHRoZSBw +cm9ibGVtIGJ5IGRlc2NyaWJpbmcgd2hhdCBoYXBwZW5lZCBiZWZvcmUgdGhlIGNyYXNoLiBMb3JlbSBp +cHN1bSBkb2xvciBzaXQgYW1ldCwgY29uc2VjdGV0dXIgYWRpcGlzY2luZyBlbGl0LiBTZWQgYXJjdSB1 +cm5hLCBwdWx2aW5hciBzaXQgYW1ldCwgdGluY2lkdW50IGFjLCBmZXJtZW50dW0gdXQsIGxpZ3VsYS4g +UXVpc3F1ZSBtaS4gRHVpcyBsZWN0dXMuA</string> + <reference key="NSSupport" ref="26"/> + <reference key="NSControlView" ref="375247105"/> + <reference key="NSBackgroundColor" ref="420457920"/> + <reference key="NSTextColor" ref="800255527"/> + </object> + </object> + <object class="NSTextField" id="996404163"> + <reference key="NSNextResponder" ref="1059038623"/> + <int key="NSvFlags">274</int> + <string key="NSFrame">{{20, 14}, {450, 61}}</string> + <reference key="NSSuperview" ref="1059038623"/> + <bool key="NSEnabled">YES</bool> + <object class="NSTextFieldCell" key="NSCell" id="242564194"> + <int key="NSCellFlags">341966337</int> + <int key="NSCellFlags2">272760832</int> + <string key="NSContents">Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 </string> + <reference key="NSSupport" ref="26"/> + <reference key="NSControlView" ref="996404163"/> + <bool key="NSDrawsBackground">YES</bool> + <reference key="NSBackgroundColor" ref="128478752"/> + <reference key="NSTextColor" ref="734930533"/> + </object> + </object> + <object class="NSBox" id="667608859"> + <reference key="NSNextResponder" ref="1059038623"/> + <int key="NSvFlags">256</int> + <object class="NSMutableArray" key="NSSubviews"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSView" id="971021844"> + <reference key="NSNextResponder" ref="667608859"/> + <int key="NSvFlags">256</int> + <object class="NSMutableArray" key="NSSubviews"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSTextField" id="1032334641"> + <reference key="NSNextResponder" ref="971021844"/> + <int key="NSvFlags">266</int> + <string key="NSFrame">{{85, 10}, {381, 54}}</string> + <reference key="NSSuperview" ref="971021844"/> + <bool key="NSEnabled">YES</bool> + <object class="NSTextFieldCell" key="NSCell" id="316557784"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">272629760</int> + <string key="NSContents">The application <Really Long App Name Here> has quit unexpectedly.</string> + <object class="NSFont" key="NSSupport"> + <string key="NSName">LucidaGrande-Bold</string> + <double key="NSSize">14</double> + <int key="NSfFlags">16</int> + </object> + <reference key="NSControlView" ref="1032334641"/> + <reference key="NSBackgroundColor" ref="420457920"/> + <reference key="NSTextColor" ref="800255527"/> + </object> + </object> + <object class="NSImageView" id="594334723"> + <reference key="NSNextResponder" ref="971021844"/> + <int key="NSvFlags">268</int> + <object class="NSMutableSet" key="NSDragTypes"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="set.sortedObjects"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>Apple PDF pasteboard type</string> + <string>Apple PICT pasteboard type</string> + <string>Apple PNG pasteboard type</string> + <string>NSFilenamesPboardType</string> + <string>NeXT Encapsulated PostScript v1.2 pasteboard type</string> + <string>NeXT TIFF v4.0 pasteboard type</string> + </object> + </object> + <string key="NSFrame">{{16, 0}, {64, 64}}</string> + <reference key="NSSuperview" ref="971021844"/> + <bool key="NSEnabled">YES</bool> + <object class="NSImageCell" key="NSCell" id="465445685"> + <int key="NSCellFlags">130560</int> + <int key="NSCellFlags2">33554432</int> + <object class="NSCustomResource" key="NSContents"> + <string key="NSClassName">NSImage</string> + <string key="NSResourceName">NSApplicationIcon</string> + </object> + <int key="NSAlign">0</int> + <int key="NSScale">0</int> + <int key="NSStyle">0</int> + <bool key="NSAnimates">NO</bool> + </object> + <bool key="NSEditable">YES</bool> + </object> + </object> + <string key="NSFrameSize">{482, 70}</string> + <reference key="NSSuperview" ref="667608859"/> + </object> + </object> + <string key="NSFrame">{{4, 245}, {482, 70}}</string> + <reference key="NSSuperview" ref="1059038623"/> + <string key="NSOffsets">{0, 0}</string> + <object class="NSTextFieldCell" key="NSTitleCell"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">0</int> + <string key="NSContents">Title</string> + <reference key="NSSupport" ref="668643277"/> + <reference key="NSBackgroundColor" ref="128478752"/> + <object class="NSColor" key="NSTextColor"> + <int key="NSColorSpace">3</int> + <bytes key="NSWhite">MCAwLjgwMDAwMDAxAA</bytes> + </object> + </object> + <reference key="NSContentView" ref="971021844"/> + <int key="NSBorderType">0</int> + <int key="NSBoxType">3</int> + <int key="NSTitlePosition">0</int> + <bool key="NSTransparent">NO</bool> + </object> + </object> + <string key="NSFrameSize">{490, 325}</string> + <reference key="NSSuperview" ref="468151514"/> + </object> + </object> + <string key="NSFrame">{{0, 160}, {490, 325}}</string> + <reference key="NSSuperview" ref="197525436"/> + <string key="NSOffsets">{0, 0}</string> + <object class="NSTextFieldCell" key="NSTitleCell"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">0</int> + <string key="NSContents">Title</string> + <reference key="NSSupport" ref="668643277"/> + <reference key="NSBackgroundColor" ref="128478752"/> + <object class="NSColor" key="NSTextColor"> + <int key="NSColorSpace">3</int> + <bytes key="NSWhite">MCAwLjgwMDAwMDAxAA</bytes> + </object> + </object> + <reference key="NSContentView" ref="1059038623"/> + <int key="NSBorderType">0</int> + <int key="NSBoxType">3</int> + <int key="NSTitlePosition">0</int> + <bool key="NSTransparent">NO</bool> + </object> + <object class="NSTextField" id="149448677"> + <reference key="NSNextResponder" ref="197525436"/> + <int key="NSvFlags">268</int> + <string key="NSFrame">{{17, 20}, {163, 14}}</string> + <reference key="NSSuperview" ref="197525436"/> + <bool key="NSEnabled">YES</bool> + <object class="NSTextFieldCell" key="NSCell" id="690832321"> + <int key="NSCellFlags">68288064</int> + <int key="NSCellFlags2">272630784</int> + <string key="NSContents">xx seconds.</string> + <reference key="NSSupport" ref="668643277"/> + <reference key="NSControlView" ref="149448677"/> + <reference key="NSBackgroundColor" ref="420457920"/> + <reference key="NSTextColor" ref="800255527"/> + </object> + </object> + </object> + <string key="NSFrameSize">{490, 489}</string> + </object> + <string key="NSScreenRect">{{0, 0}, {2560, 1578}}</string> + <string key="NSMinSize">{72, 27}</string> + <string key="NSMaxSize">{1.79769e+308, 1.79769e+308}</string> + </object> + <object class="NSUserDefaultsController" id="626548788"> + <bool key="NSSharedInstance">YES</bool> + </object> + </object> + <object class="IBObjectContainer" key="IBDocument.Objects"> + <object class="NSMutableArray" key="connectionRecords"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">sendReport:</string> + <reference key="source" ref="273934324"/> + <reference key="destination" ref="219938755"/> + </object> + <int key="connectionID">45</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">cancel:</string> + <reference key="source" ref="273934324"/> + <reference key="destination" ref="409721323"/> + </object> + <int key="connectionID">46</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">showPrivacyPolicy:</string> + <reference key="source" ref="273934324"/> + <reference key="destination" ref="538303250"/> + </object> + <int key="connectionID">53</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBBindingConnection" key="connection"> + <string key="label">value: emailValue</string> + <reference key="source" ref="975305147"/> + <reference key="destination" ref="273934324"/> + <object class="NSNibBindingConnector" key="connector"> + <reference key="NSSource" ref="975305147"/> + <reference key="NSDestination" ref="273934324"/> + <string key="NSLabel">value: emailValue</string> + <string key="NSBinding">value</string> + <string key="NSKeyPath">emailValue</string> + <object class="NSDictionary" key="NSOptions"> + <string key="NS.key.0">NSNullPlaceholder</string> + <string key="NS.object.0">optional</string> + </object> + <int key="NSNibBindingConnectorVersion">2</int> + </object> + </object> + <int key="connectionID">90</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">initialFirstResponder</string> + <reference key="source" ref="762998835"/> + <reference key="destination" ref="219938755"/> + </object> + <int key="connectionID">91</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBBindingConnection" key="connection"> + <string key="label">value: commentsValue</string> + <reference key="source" ref="996404163"/> + <reference key="destination" ref="273934324"/> + <object class="NSNibBindingConnector" key="connector"> + <reference key="NSSource" ref="996404163"/> + <reference key="NSDestination" ref="273934324"/> + <string key="NSLabel">value: commentsValue</string> + <string key="NSBinding">value</string> + <string key="NSKeyPath">commentsValue</string> + <object class="NSDictionary" key="NSOptions"> + <string key="NS.key.0">NSNullPlaceholder</string> + <string key="NS.object.0">optional comments</string> + </object> + <int key="NSNibBindingConnectorVersion">2</int> + </object> + </object> + <int key="connectionID">124</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">nextKeyView</string> + <reference key="source" ref="975305147"/> + <reference key="destination" ref="219938755"/> + </object> + <int key="connectionID">125</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">nextKeyView</string> + <reference key="source" ref="996404163"/> + <reference key="destination" ref="975305147"/> + </object> + <int key="connectionID">126</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">nextKeyView</string> + <reference key="source" ref="219938755"/> + <reference key="destination" ref="996404163"/> + </object> + <int key="connectionID">127</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">delegate</string> + <reference key="source" ref="996404163"/> + <reference key="destination" ref="273934324"/> + </object> + <int key="connectionID">128</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">alertWindow_</string> + <reference key="source" ref="273934324"/> + <reference key="destination" ref="762998835"/> + </object> + <int key="connectionID">142</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">preEmailBox_</string> + <reference key="source" ref="273934324"/> + <reference key="destination" ref="468151514"/> + </object> + <int key="connectionID">150</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">headerBox_</string> + <reference key="source" ref="273934324"/> + <reference key="destination" ref="667608859"/> + </object> + <int key="connectionID">151</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">emailSectionBox_</string> + <reference key="source" ref="273934324"/> + <reference key="destination" ref="469837363"/> + </object> + <int key="connectionID">152</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">privacyLinkLabel_</string> + <reference key="source" ref="273934324"/> + <reference key="destination" ref="655227981"/> + </object> + <int key="connectionID">153</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">commentMessage_</string> + <reference key="source" ref="273934324"/> + <reference key="destination" ref="375247105"/> + </object> + <int key="connectionID">154</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">dialogTitle_</string> + <reference key="source" ref="273934324"/> + <reference key="destination" ref="1032334641"/> + </object> + <int key="connectionID">155</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">emailLabel_</string> + <reference key="source" ref="273934324"/> + <reference key="destination" ref="268211031"/> + </object> + <int key="connectionID">156</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">cancelButton_</string> + <reference key="source" ref="273934324"/> + <reference key="destination" ref="409721323"/> + </object> + <int key="connectionID">158</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">sendButton_</string> + <reference key="source" ref="273934324"/> + <reference key="destination" ref="219938755"/> + </object> + <int key="connectionID">159</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">emailEntryField_</string> + <reference key="source" ref="273934324"/> + <reference key="destination" ref="975305147"/> + </object> + <int key="connectionID">161</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">privacyLinkArrow_</string> + <reference key="source" ref="273934324"/> + <reference key="destination" ref="538303250"/> + </object> + <int key="connectionID">162</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">emailMessage_</string> + <reference key="source" ref="273934324"/> + <reference key="destination" ref="997378142"/> + </object> + <int key="connectionID">163</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">commentsEntryField_</string> + <reference key="source" ref="273934324"/> + <reference key="destination" ref="996404163"/> + </object> + <int key="connectionID">176</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBBindingConnection" key="connection"> + <string key="label">value: countdownMessage</string> + <reference key="source" ref="149448677"/> + <reference key="destination" ref="273934324"/> + <object class="NSNibBindingConnector" key="connector"> + <reference key="NSSource" ref="149448677"/> + <reference key="NSDestination" ref="273934324"/> + <string key="NSLabel">value: countdownMessage</string> + <string key="NSBinding">value</string> + <string key="NSKeyPath">countdownMessage</string> + <int key="NSNibBindingConnectorVersion">2</int> + </object> + </object> + <int key="connectionID">194</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">countdownLabel_</string> + <reference key="source" ref="273934324"/> + <reference key="destination" ref="149448677"/> + </object> + <int key="connectionID">208</int> + </object> + </object> + <object class="IBMutableOrderedSet" key="objectRecords"> + <object class="NSArray" key="orderedObjects"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBObjectRecord"> + <int key="objectID">0</int> + <reference key="object" ref="0"/> + <reference key="children" ref="504246249"/> + <nil key="parent"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-2</int> + <reference key="object" ref="273934324"/> + <reference key="parent" ref="0"/> + <string key="objectName">File's Owner</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-1</int> + <reference key="object" ref="388635980"/> + <reference key="parent" ref="0"/> + <string key="objectName">First Responder</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-3</int> + <reference key="object" ref="220995958"/> + <reference key="parent" ref="0"/> + <string key="objectName">Application</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">1</int> + <reference key="object" ref="762998835"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="197525436"/> + </object> + <reference key="parent" ref="0"/> + <string key="objectName">Window</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">2</int> + <reference key="object" ref="197525436"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="219938755"/> + <reference ref="409721323"/> + <reference ref="469837363"/> + <reference ref="468151514"/> + <reference ref="149448677"/> + </object> + <reference key="parent" ref="762998835"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">12</int> + <reference key="object" ref="219938755"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="733475259"/> + </object> + <reference key="parent" ref="197525436"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">14</int> + <reference key="object" ref="409721323"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="586160416"/> + </object> + <reference key="parent" ref="197525436"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">132</int> + <reference key="object" ref="469837363"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="997378142"/> + <reference ref="975305147"/> + <reference ref="268211031"/> + <reference ref="538303250"/> + <reference ref="655227981"/> + </object> + <reference key="parent" ref="197525436"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">145</int> + <reference key="object" ref="468151514"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="375247105"/> + <reference ref="996404163"/> + <reference ref="667608859"/> + </object> + <reference key="parent" ref="197525436"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">189</int> + <reference key="object" ref="149448677"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="690832321"/> + </object> + <reference key="parent" ref="197525436"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">191</int> + <reference key="object" ref="626548788"/> + <reference key="parent" ref="0"/> + <string key="objectName">Shared User Defaults Controller</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">210</int> + <reference key="object" ref="733475259"/> + <reference key="parent" ref="219938755"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">211</int> + <reference key="object" ref="586160416"/> + <reference key="parent" ref="409721323"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">221</int> + <reference key="object" ref="690832321"/> + <reference key="parent" ref="149448677"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">58</int> + <reference key="object" ref="997378142"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="509794736"/> + </object> + <reference key="parent" ref="469837363"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">215</int> + <reference key="object" ref="509794736"/> + <reference key="parent" ref="997378142"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">18</int> + <reference key="object" ref="975305147"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="592393645"/> + </object> + <reference key="parent" ref="469837363"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">212</int> + <reference key="object" ref="592393645"/> + <reference key="parent" ref="975305147"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">20</int> + <reference key="object" ref="268211031"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="461570326"/> + </object> + <reference key="parent" ref="469837363"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">213</int> + <reference key="object" ref="461570326"/> + <reference key="parent" ref="268211031"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">48</int> + <reference key="object" ref="538303250"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="778004767"/> + </object> + <reference key="parent" ref="469837363"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">214</int> + <reference key="object" ref="778004767"/> + <reference key="parent" ref="538303250"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">66</int> + <reference key="object" ref="655227981"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="1012850565"/> + </object> + <reference key="parent" ref="469837363"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">216</int> + <reference key="object" ref="1012850565"/> + <reference key="parent" ref="655227981"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">8</int> + <reference key="object" ref="375247105"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="188082030"/> + </object> + <reference key="parent" ref="468151514"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">217</int> + <reference key="object" ref="188082030"/> + <reference key="parent" ref="375247105"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">116</int> + <reference key="object" ref="996404163"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="242564194"/> + </object> + <reference key="parent" ref="468151514"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">218</int> + <reference key="object" ref="242564194"/> + <reference key="parent" ref="996404163"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">147</int> + <reference key="object" ref="667608859"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="1032334641"/> + <reference ref="594334723"/> + </object> + <reference key="parent" ref="468151514"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">3</int> + <reference key="object" ref="1032334641"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="316557784"/> + </object> + <reference key="parent" ref="667608859"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">219</int> + <reference key="object" ref="316557784"/> + <reference key="parent" ref="1032334641"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">6</int> + <reference key="object" ref="594334723"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="465445685"/> + </object> + <reference key="parent" ref="667608859"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">220</int> + <reference key="object" ref="465445685"/> + <reference key="parent" ref="594334723"/> + </object> + </object> + </object> + <object class="NSMutableDictionary" key="flattenedProperties"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>-3.ImportedFromIB2</string> + <string>1.IBEditorWindowLastContentRect</string> + <string>1.IBWindowTemplateEditedContentRect</string> + <string>1.ImportedFromIB2</string> + <string>1.windowTemplate.hasMinSize</string> + <string>1.windowTemplate.minSize</string> + <string>116.CustomClassName</string> + <string>116.ImportedFromIB2</string> + <string>12.ImportedFromIB2</string> + <string>132.ImportedFromIB2</string> + <string>14.ImportedFromIB2</string> + <string>145.ImportedFromIB2</string> + <string>147.ImportedFromIB2</string> + <string>18.CustomClassName</string> + <string>18.ImportedFromIB2</string> + <string>189.ImportedFromIB2</string> + <string>191.ImportedFromIB2</string> + <string>2.ImportedFromIB2</string> + <string>20.ImportedFromIB2</string> + <string>3.ImportedFromIB2</string> + <string>48.ImportedFromIB2</string> + <string>58.ImportedFromIB2</string> + <string>6.ImportedFromIB2</string> + <string>66.ImportedFromIB2</string> + <string>8.ImportedFromIB2</string> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + <boolean value="YES"/> + <string>{{0, 656}, {490, 489}}</string> + <string>{{0, 656}, {490, 489}}</string> + <boolean value="YES"/> + <boolean value="YES"/> + <string>{72, 5}</string> + <string>LengthLimitingTextField</string> + <boolean value="YES"/> + <boolean value="YES"/> + <boolean value="YES"/> + <boolean value="YES"/> + <boolean value="YES"/> + <boolean value="YES"/> + <string>LengthLimitingTextField</string> + <boolean value="YES"/> + <boolean value="YES"/> + <boolean value="YES"/> + <boolean value="YES"/> + <boolean value="YES"/> + <boolean value="YES"/> + <boolean value="YES"/> + <boolean value="YES"/> + <boolean value="YES"/> + <boolean value="YES"/> + <boolean value="YES"/> + </object> + </object> + <object class="NSMutableDictionary" key="unlocalizedProperties"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference key="dict.sortedKeys" ref="0"/> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + </object> + <nil key="activeLocalization"/> + <object class="NSMutableDictionary" key="localizations"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference key="dict.sortedKeys" ref="0"/> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + </object> + <nil key="sourceID"/> + <int key="maxID">221</int> + </object> + <object class="IBClassDescriber" key="IBDocument.Classes"> + <object class="NSMutableArray" key="referencedPartialClassDescriptions"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBPartialClassDescription"> + <string key="className">LengthLimitingTextField</string> + <string key="superclassName">NSTextField</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBUserSource</string> + <string key="minorKey"/> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">Reporter</string> + <string key="superclassName">NSObject</string> + <object class="NSMutableDictionary" key="actions"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>cancel:</string> + <string>sendReport:</string> + <string>showPrivacyPolicy:</string> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>id</string> + <string>id</string> + <string>id</string> + </object> + </object> + <object class="NSMutableDictionary" key="outlets"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>alertWindow_</string> + <string>cancelButton_</string> + <string>commentMessage_</string> + <string>commentsEntryField_</string> + <string>countdownLabel_</string> + <string>dialogTitle_</string> + <string>emailEntryField_</string> + <string>emailLabel_</string> + <string>emailMessage_</string> + <string>emailSectionBox_</string> + <string>headerBox_</string> + <string>preEmailBox_</string> + <string>privacyLinkArrow_</string> + <string>privacyLinkLabel_</string> + <string>sendButton_</string> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>NSWindow</string> + <string>NSButton</string> + <string>NSTextField</string> + <string>LengthLimitingTextField</string> + <string>NSTextField</string> + <string>NSTextField</string> + <string>LengthLimitingTextField</string> + <string>NSTextField</string> + <string>NSTextField</string> + <string>NSBox</string> + <string>NSBox</string> + <string>NSBox</string> + <string>NSView</string> + <string>NSTextField</string> + <string>NSButton</string> + </object> + </object> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBUserSource</string> + <string key="minorKey"/> + </object> + </object> + </object> + </object> + <int key="IBDocument.localizationMode">0</int> + <string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string> + <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies"> + <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string> + <integer value="1050" key="NS.object.0"/> + </object> + <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies"> + <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3</string> + <integer value="3000" key="NS.object.0"/> + </object> + <bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool> + <string key="IBDocument.LastKnownRelativeProjectPath">../Breakpad.xcodeproj</string> + <int key="IBDocument.defaultPropertyAccessControl">3</int> + <object class="NSMutableDictionary" key="IBDocument.LastKnownImageSizes"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>NSApplicationIcon</string> + <string>goArrow</string> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>{128, 128}</string> + <string>{128, 128}</string> + </object> + </object> + </data> +</archive> diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/English.lproj/InfoPlist.strings b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/English.lproj/InfoPlist.strings Binary files differnew file mode 100644 index 000000000..65d161818 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/English.lproj/InfoPlist.strings diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/English.lproj/Localizable.strings b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/English.lproj/Localizable.strings Binary files differnew file mode 100644 index 000000000..5f1cc8269 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/English.lproj/Localizable.strings diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/ReporterIcon.graffle b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/ReporterIcon.graffle new file mode 100644 index 000000000..14a0e7c4c --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/ReporterIcon.graffle @@ -0,0 +1,2489 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>ActiveLayerIndex</key> + <integer>0</integer> + <key>ApplicationVersion</key> + <array> + <string>com.omnigroup.OmniGrafflePro</string> + <string>137.6.0.106738</string> + </array> + <key>AutoAdjust</key> + <false/> + <key>BackgroundGraphic</key> + <dict> + <key>Bounds</key> + <string>{{0, 0}, {512, 512}}</string> + <key>Class</key> + <string>SolidGraphic</string> + <key>FontInfo</key> + <dict> + <key>Font</key> + <string>CalisMTBol</string> + <key>Size</key> + <real>112</real> + </dict> + <key>ID</key> + <integer>2</integer> + <key>Style</key> + <dict> + <key>fill</key> + <dict> + <key>Color</key> + <dict> + <key>a</key> + <string>0</string> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0.852018</string> + <key>r</key> + <string>0.998962</string> + </dict> + </dict> + <key>shadow</key> + <dict> + <key>Draws</key> + <string>NO</string> + </dict> + <key>stroke</key> + <dict> + <key>Draws</key> + <string>NO</string> + </dict> + </dict> + </dict> + <key>CanvasOrigin</key> + <string>{0, 0}</string> + <key>CanvasSize</key> + <string>{512, 512}</string> + <key>ColumnAlign</key> + <integer>1</integer> + <key>ColumnSpacing</key> + <real>36</real> + <key>CreationDate</key> + <string>2008-11-14 16:58:15 -0700</string> + <key>Creator</key> + <string>John P. Developer</string> + <key>DisplayScale</key> + <string>1 pt = 1 px</string> + <key>FileType</key> + <string>flat</string> + <key>GraphDocumentVersion</key> + <integer>6</integer> + <key>GraphicsList</key> + <array> + <dict> + <key>Bounds</key> + <string>{{33.9443, 35.3885}, {444.111, 437.112}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>FontInfo</key> + <dict> + <key>Font</key> + <string>CalisMTBol</string> + <key>Size</key> + <real>112</real> + </dict> + <key>ID</key> + <integer>31</integer> + <key>Rotation</key> + <real>270</real> + <key>Shape</key> + <string>Bezier</string> + <key>ShapeData</key> + <dict> + <key>UnitPoints</key> + <array> + <string>{-0.5, -0.439247}</string> + <string>{-0.5, -0.485429}</string> + <string>{-0.446294, -0.512626}</string> + <string>{-0.409932, -0.494153}</string> + <string>{-0.373569, -0.47568}</string> + <string>{0.436363, -0.0733799}</string> + <string>{0.472729, -0.0549059}</string> + <string>{0.50909, -0.0364333}</string> + <string>{0.509091, 0.0364345}</string> + <string>{0.472729, 0.0549059}</string> + <string>{0.436368, 0.0733802}</string> + <string>{-0.373569, 0.475681}</string> + <string>{-0.409932, 0.494153}</string> + <string>{-0.446294, 0.512626}</string> + <string>{-0.500001, 0.485429}</string> + <string>{-0.5, 0.439247}</string> + <string>{-0.49998, 0.393072}</string> + <string>{-0.500002, -0.393066}</string> + </array> + </dict> + <key>Style</key> + <dict> + <key>fill</key> + <dict> + <key>Color</key> + <dict> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0.770962</string> + <key>r</key> + <string>0.997971</string> + </dict> + <key>Draws</key> + <string>NO</string> + <key>FillType</key> + <integer>3</integer> + <key>GradientCenter</key> + <string>{-0.609524, 0}</string> + <key>GradientColor</key> + <dict> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0.911574</string> + <key>r</key> + <string>0.998779</string> + </dict> + <key>MiddleFraction</key> + <real>0.6111111044883728</real> + </dict> + <key>shadow</key> + <dict> + <key>Color</key> + <dict> + <key>a</key> + <string>0.43</string> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0</string> + <key>r</key> + <string>0</string> + </dict> + <key>Draws</key> + <string>NO</string> + <key>Fuzziness</key> + <real>7.2213706970214844</real> + <key>ShadowVector</key> + <string>{0, 6}</string> + </dict> + <key>stroke</key> + <dict> + <key>Color</key> + <dict> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0.766903</string> + <key>r</key> + <string>0.997925</string> + </dict> + <key>Width</key> + <real>7</real> + </dict> + </dict> + <key>Text</key> + <dict> + <key>Pad</key> + <integer>0</integer> + <key>VerticalPad</key> + <integer>0</integer> + </dict> + <key>TextPlacement</key> + <integer>0</integer> + <key>TextRelativeArea</key> + <string>{{0.06, 0.17}, {0.88, 0.5}}</string> + <key>TextRotation</key> + <real>90</real> + <key>Wrap</key> + <string>NO</string> + </dict> + <dict> + <key>Bounds</key> + <string>{{3.89085, 67.8908}, {404.218, 332}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>FontInfo</key> + <dict> + <key>Font</key> + <string>CalisMTBol</string> + <key>Size</key> + <real>112</real> + </dict> + <key>ID</key> + <integer>30</integer> + <key>Rotation</key> + <real>270</real> + <key>Shape</key> + <string>Bezier</string> + <key>ShapeData</key> + <dict> + <key>UnitPoints</key> + <array> + <string>{-0.5, -0.5}</string> + <string>{-0.459695, -0.475464}</string> + <string>{0.429465, 0.0537758}</string> + <string>{0.469773, 0.0783133}</string> + <string>{0.510074, 0.102849}</string> + <string>{0.510077, 0.198357}</string> + <string>{0.469773, 0.222892}</string> + <string>{0.429473, 0.247428}</string> + <string>{-0.00521517, 0.499998}</string> + <string>{-0.00521785, 0.5}</string> + <string>{-0.00521713, -0.113381}</string> + <string>{-0.44962, -0.458615}</string> + </array> + </dict> + <key>Style</key> + <dict> + <key>fill</key> + <dict> + <key>Color</key> + <dict> + <key>a</key> + <string>0</string> + <key>b</key> + <string>1</string> + <key>g</key> + <string>1</string> + <key>r</key> + <string>1</string> + </dict> + <key>FillType</key> + <integer>2</integer> + <key>GradientAngle</key> + <real>180</real> + <key>GradientCenter</key> + <string>{-0.609524, 0}</string> + <key>GradientColor</key> + <dict> + <key>a</key> + <string>0.5</string> + <key>w</key> + <string>1</string> + </dict> + <key>MiddleFraction</key> + <real>0.6111111044883728</real> + </dict> + <key>shadow</key> + <dict> + <key>Color</key> + <dict> + <key>a</key> + <string>0.51</string> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0</string> + <key>r</key> + <string>0</string> + </dict> + <key>Draws</key> + <string>NO</string> + <key>Fuzziness</key> + <real>3.3371961116790771</real> + <key>ShadowVector</key> + <string>{0, 2}</string> + </dict> + <key>stroke</key> + <dict> + <key>Color</key> + <dict> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0.766903</string> + <key>r</key> + <string>0.997925</string> + </dict> + <key>Draws</key> + <string>NO</string> + <key>Width</key> + <real>2</real> + </dict> + </dict> + <key>Text</key> + <dict> + <key>Pad</key> + <integer>0</integer> + <key>VerticalPad</key> + <integer>0</integer> + </dict> + <key>TextPlacement</key> + <integer>0</integer> + <key>TextRelativeArea</key> + <string>{{0.06, 0.17}, {0.88, 0.5}}</string> + <key>TextRotation</key> + <real>90</real> + <key>Wrap</key> + <string>NO</string> + </dict> + <dict> + <key>Bounds</key> + <string>{{33.9443, 35.3886}, {444.112, 437.111}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>FontInfo</key> + <dict> + <key>Font</key> + <string>CalisMTBol</string> + <key>Size</key> + <real>112</real> + </dict> + <key>ID</key> + <integer>29</integer> + <key>Rotation</key> + <real>270</real> + <key>Shape</key> + <string>Bezier</string> + <key>ShapeData</key> + <dict> + <key>UnitPoints</key> + <array> + <string>{-0.5, -0.439247}</string> + <string>{-0.500001, -0.485429}</string> + <string>{-0.446295, -0.512626}</string> + <string>{-0.409932, -0.494153}</string> + <string>{-0.373568, -0.475681}</string> + <string>{0.436363, -0.0733802}</string> + <string>{0.472729, -0.0549062}</string> + <string>{0.509089, -0.0364334}</string> + <string>{0.509092, 0.0364341}</string> + <string>{0.472729, 0.0549056}</string> + <string>{0.436369, 0.0733803}</string> + <string>{-0.373568, 0.475681}</string> + <string>{-0.409932, 0.494153}</string> + <string>{-0.446294, 0.512626}</string> + <string>{-0.500001, 0.485428}</string> + <string>{-0.5, 0.439248}</string> + <string>{-0.499978, 0.39307}</string> + <string>{-0.500003, -0.393066}</string> + </array> + </dict> + <key>Style</key> + <dict> + <key>fill</key> + <dict> + <key>Color</key> + <dict> + <key>a</key> + <string>0.2</string> + <key>b</key> + <string>1</string> + <key>g</key> + <string>1</string> + <key>r</key> + <string>1</string> + </dict> + <key>FillType</key> + <integer>2</integer> + <key>GradientAngle</key> + <real>90</real> + <key>GradientCenter</key> + <string>{-0.609524, 0}</string> + <key>GradientColor</key> + <dict> + <key>a</key> + <string>0</string> + <key>w</key> + <string>1</string> + </dict> + <key>MiddleFraction</key> + <real>0.6111111044883728</real> + </dict> + <key>shadow</key> + <dict> + <key>Color</key> + <dict> + <key>a</key> + <string>0.51</string> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0</string> + <key>r</key> + <string>0</string> + </dict> + <key>Draws</key> + <string>NO</string> + <key>Fuzziness</key> + <real>3.3371961116790771</real> + <key>ShadowVector</key> + <string>{0, 2}</string> + </dict> + <key>stroke</key> + <dict> + <key>Color</key> + <dict> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0.766903</string> + <key>r</key> + <string>0.997925</string> + </dict> + <key>Draws</key> + <string>NO</string> + <key>Width</key> + <real>2</real> + </dict> + </dict> + <key>Text</key> + <dict> + <key>Pad</key> + <integer>0</integer> + <key>VerticalPad</key> + <integer>0</integer> + </dict> + <key>TextPlacement</key> + <integer>0</integer> + <key>TextRelativeArea</key> + <string>{{0.06, 0.17}, {0.88, 0.5}}</string> + <key>TextRotation</key> + <real>90</real> + <key>Wrap</key> + <string>NO</string> + </dict> + <dict> + <key>Bounds</key> + <string>{{176, 102.384}, {158.841, 537.616}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>FontInfo</key> + <dict> + <key>Font</key> + <string>CalisMTBol</string> + <key>Size</key> + <real>425</real> + </dict> + <key>ID</key> + <integer>26</integer> + <key>Shape</key> + <string>Rectangle</string> + <key>Style</key> + <dict> + <key>fill</key> + <dict> + <key>Draws</key> + <string>NO</string> + </dict> + <key>shadow</key> + <dict> + <key>Draws</key> + <string>NO</string> + </dict> + <key>stroke</key> + <dict> + <key>Draws</key> + <string>NO</string> + </dict> + </dict> + <key>Text</key> + <dict> + <key>Pad</key> + <integer>0</integer> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf350 +{\fonttbl\f0\fnil\fcharset0 CalistoMT;} +{\colortbl;\red255\green255\blue255;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural + +\f0\b\fs850 \cf1 !}</string> + <key>VerticalPad</key> + <integer>0</integer> + </dict> + <key>Wrap</key> + <string>NO</string> + </dict> + <dict> + <key>Bounds</key> + <string>{{176, 104}, {158.841, 537.616}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>FontInfo</key> + <dict> + <key>Color</key> + <dict> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0.749523</string> + <key>r</key> + <string>0.997726</string> + </dict> + <key>Font</key> + <string>CalisMTBol</string> + <key>Size</key> + <real>425</real> + </dict> + <key>ID</key> + <integer>27</integer> + <key>Shape</key> + <string>Rectangle</string> + <key>Style</key> + <dict> + <key>fill</key> + <dict> + <key>Draws</key> + <string>NO</string> + </dict> + <key>shadow</key> + <dict> + <key>Draws</key> + <string>NO</string> + </dict> + <key>stroke</key> + <dict> + <key>Draws</key> + <string>NO</string> + </dict> + </dict> + <key>Text</key> + <dict> + <key>Pad</key> + <integer>0</integer> + <key>RTFD</key> + <data> + BAtzdHJlYW10eXBlZIHoA4QBQISEhBJOU0F0dHJpYnV0 + ZWRTdHJpbmcAhIQITlNPYmplY3QAhZKEhIQITlNTdHJp + bmcBlIQBKwEhhoQCaUkBAZKEhIQMTlNEaWN0aW9uYXJ5 + AJSEAWkEkoSWlhBOU1BhcmFncmFwaFN0eWxlhpKEhIQQ + TlNQYXJhZ3JhcGhTdHlsZQCUhARDQ0BTAgCEhIQHTlNB + cnJheQCUmQyShISECU5TVGV4dFRhYgCUhAJDZgAchpKE + n54AOIaShJ+eAFSGkoSfngBwhpKEn54AgYwAhpKEn54A + gagAhpKEn54AgcQAhpKEn54AgeAAhpKEn54AgfwAhpKE + n54AgRgBhpKEn54AgTQBhpKEn54AgVABhoYAhpKElpYG + TlNGb250hpKEhIQGTlNGb250HpSZIIQFWzMyY10GAAAA + FgAAAP/+QwBhAGwAaQBzAE0AVABCAG8AbAAAAIQBZoGp + AYQBYwCiAaIAogCGkoSWlg1OU1N0cm9rZVdpZHRohpKE + hIQITlNOdW1iZXIAhIQHTlNWYWx1ZQCUhAEqhIQBZKYD + hpKElpYHTlNDb2xvcoaShISEB05TQ29sb3IAlKIChARm + ZmZmAYN4dz8/AAGGhoY= + </data> + <key>Text</key> + <string>{\rtf1\ansi\ansicpg1252\cocoartf949\cocoasubrtf350 +{\fonttbl\f0\fnil\fcharset0 CalistoMT;} +{\colortbl;\red255\green255\blue255;\red254\green191\blue0;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural + +\f0\b\fs850 \cf2 \outl\strokewidth60 \strokec2 !}</string> + <key>VerticalPad</key> + <integer>0</integer> + </dict> + <key>Wrap</key> + <string>NO</string> + </dict> + <dict> + <key>Bounds</key> + <string>{{33.9441, 35.3884}, {444.112, 437.111}}</string> + <key>Class</key> + <string>ShapedGraphic</string> + <key>FontInfo</key> + <dict> + <key>Font</key> + <string>CalisMTBol</string> + <key>Size</key> + <real>112</real> + </dict> + <key>ID</key> + <integer>16</integer> + <key>Rotation</key> + <real>270</real> + <key>Shape</key> + <string>Bezier</string> + <key>ShapeData</key> + <dict> + <key>UnitPoints</key> + <array> + <string>{-0.5, -0.439247}</string> + <string>{-0.5, -0.485429}</string> + <string>{-0.446295, -0.512626}</string> + <string>{-0.409933, -0.494153}</string> + <string>{-0.373569, -0.47568}</string> + <string>{0.436363, -0.073379}</string> + <string>{0.472729, -0.0549049}</string> + <string>{0.50909, -0.0364324}</string> + <string>{0.509091, 0.0364344}</string> + <string>{0.472729, 0.0549058}</string> + <string>{0.436368, 0.0733801}</string> + <string>{-0.373569, 0.47568}</string> + <string>{-0.409933, 0.494153}</string> + <string>{-0.446295, 0.512626}</string> + <string>{-0.500001, 0.485429}</string> + <string>{-0.5, 0.439247}</string> + <string>{-0.49998, 0.393072}</string> + <string>{-0.500002, -0.393066}</string> + </array> + </dict> + <key>Style</key> + <dict> + <key>fill</key> + <dict> + <key>Color</key> + <dict> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0.770962</string> + <key>r</key> + <string>0.997971</string> + </dict> + <key>FillType</key> + <integer>3</integer> + <key>GradientCenter</key> + <string>{-0.609524, 0}</string> + <key>GradientColor</key> + <dict> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0.911574</string> + <key>r</key> + <string>0.998779</string> + </dict> + <key>MiddleFraction</key> + <real>0.6111111044883728</real> + </dict> + <key>shadow</key> + <dict> + <key>Color</key> + <dict> + <key>a</key> + <string>0.9</string> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0</string> + <key>r</key> + <string>0</string> + </dict> + <key>Fuzziness</key> + <real>8.0632610321044922</real> + <key>ShadowVector</key> + <string>{0, 9}</string> + </dict> + <key>stroke</key> + <dict> + <key>Color</key> + <dict> + <key>b</key> + <string>0</string> + <key>g</key> + <string>0.766903</string> + <key>r</key> + <string>0.997925</string> + </dict> + <key>Draws</key> + <string>NO</string> + <key>Width</key> + <real>2</real> + </dict> + </dict> + <key>Text</key> + <dict> + <key>Pad</key> + <integer>0</integer> + <key>VerticalPad</key> + <integer>0</integer> + </dict> + <key>TextPlacement</key> + <integer>0</integer> + <key>TextRelativeArea</key> + <string>{{0.06, 0.17}, {0.88, 0.5}}</string> + <key>TextRotation</key> + <real>90</real> + <key>Wrap</key> + <string>NO</string> + </dict> + </array> + <key>GridInfo</key> + <dict> + <key>GridSpacing</key> + <real>4</real> + <key>ShowsGrid</key> + <string>YES</string> + <key>SnapsToGrid</key> + <string>YES</string> + </dict> + <key>GuidesLocked</key> + <string>NO</string> + <key>GuidesVisible</key> + <string>YES</string> + <key>HPages</key> + <integer>1</integer> + <key>ImageCounter</key> + <integer>2</integer> + <key>KeepToScale</key> + <false/> + <key>Layers</key> + <array> + <dict> + <key>Lock</key> + <string>NO</string> + <key>Name</key> + <string>Layer 1</string> + <key>Print</key> + <string>YES</string> + <key>View</key> + <string>YES</string> + </dict> + </array> + <key>LayoutInfo</key> + <dict> + <key>Animate</key> + <string>NO</string> + <key>circoMinDist</key> + <real>18</real> + <key>circoSeparation</key> + <real>0.0</real> + <key>layoutEngine</key> + <string>dot</string> + <key>neatoSeparation</key> + <real>0.0</real> + <key>twopiSeparation</key> + <real>0.0</real> + </dict> + <key>LinksVisible</key> + <string>NO</string> + <key>MagnetsVisible</key> + <string>NO</string> + <key>MasterSheets</key> + <array/> + <key>ModificationDate</key> + <string>2008-11-17 11:41:28 -0700</string> + <key>Modifier</key> + <string>Preston Jackson</string> + <key>NotesVisible</key> + <string>NO</string> + <key>Orientation</key> + <integer>2</integer> + <key>OriginVisible</key> + <string>NO</string> + <key>PageBreaks</key> + <string>YES</string> + <key>PrintInfo</key> + <dict> + <key>NSBottomMargin</key> + <array> + <string>float</string> + <string>41</string> + </array> + <key>NSLeftMargin</key> + <array> + <string>float</string> + <string>18</string> + </array> + <key>NSPaperSize</key> + <array> + <string>size</string> + <string>{612, 792}</string> + </array> + <key>NSRightMargin</key> + <array> + <string>float</string> + <string>18</string> + </array> + <key>NSTopMargin</key> + <array> + <string>float</string> + <string>18</string> + </array> + </dict> + <key>PrintOnePage</key> + <false/> + <key>QuickLookPreview</key> + <data> + JVBERi0xLjMKJcTl8uXrp/Og0MTGCjQgMCBvYmoKPDwgL0xlbmd0aCA1IDAgUiAvRmls + dGVyIC9GbGF0ZURlY29kZSA+PgpzdHJlYW0KeAGVlktvJEUQhO/9K5IbHFyuR9brioEV + nFh5JM5o5MWsPIA9IP4+X2b12gPrBbHWSt3lzkdFREb6Ud7Ko0R+asr+/+lOfpBfJIYW + /Z/8LNc35yTHsyROh/ZYxyTgfJTrN/zip7NcxVD3r1+e9oQbCd/J9fd3T8e7337/48cH + eSLjm3O2wEfR2ry8tiJ5hDJSVEk9pF7jkONJrr89JfnqV/p8uz1KK1YpJynNHlKVk2gJ + JZWqdqZz9iklBc21ZmkxlJoin0/vsEonTo6b9lC79iQzh6azSc7FYjINZWIkk4MCKtrG + ejhyRpZeRYcSNIbk7oXmtk5m4mRaD/NvYcOK1bKnpnkuu4qt6jqEVujLmtuOvFgdrXv7 + mcjWQxut71ds5LcbAIADIcTkDgpt4TKmyigWoXNzVAYtfYzdUe5fOT25ACxMtZAQiPeX + xEVTSCkNeaBLDbXWDhwfnxH1QHJX0sfiulDDhhpMgMnUJZAGDLkCea3T6b+9T3K+N/pf + F6qL8+ZW0hYDjM4ESlFubyTlAFd/kvfwRKilj3IFRdTQHJsk6EwzW5UvDwBY1xf2cNVL + SDWiyTa2AyL8JgXr8fBOPv/sCzm8l68PNERtwm0wGIb4yTrK2LiYt3+rI5+uY1df7JW8 + CD9tS/XNGUdxFSUs1e+yiQPuXPUMyVI9lL2qeh2bq16Rjet+qRdVLcWrceySz8+S30+A + zyTPTNiYWMQSe10Z64vY+/OoudZNus9dudwRqE+rVVty97v63bZd7iZHL7PkjnfYe5xw + vvTOAJtW5+gMv3vFB8RetF6yzYQ5x3/L08wKeQZ3t1pin5Fp1GpD0ORKy7AnlLN/kbPS + 2ofZwIlqwA1G35aT5d3JyGncLARwMKZb0Tt2gIAHLOBGpTJExgtaxZ/MjxbK+B8mYdQ0 + 5QuYoSumBgvBBXEsP0n9khlidnI8xrK6LZqBzVm2bFzEhIjMiIwcPyGGeQqjdjrwT7h+ + LYHiADxbwGHg6+Uux+3+4u1/I6yj5DSiaKw0CBBXpRSDluldCFM4zgHvPa9zujJygMR3 + RXlB+JWt1t0PvmNg35PwHxsOE4mw1Weu0cykNci2JJjJhX+sVUm1pt4BgIOOr6HBGsLd + eYUt0uRFYFIEgAl4n6yrBqw6QuzKxtA0wdf4g/OZ2QWMAd4DfUgXOqHaYjtc4/Gjshmh + y/PP/YQ62VDzj4dlZttYGh2ZHAwCzaCeVcoaJty3VGm2b4bnZwuhC2LommlOA9lxF2ub + WDS6QrjdWjcjNZJ3Uzh/OyA6IjK7cIVwj0t8fPwuD05ya6b+F7C1v1cKZW5kc3RyZWFt + CmVuZG9iago1IDAgb2JqCjk4MwplbmRvYmoKMiAwIG9iago8PCAvVHlwZSAvUGFnZSAv + UGFyZW50IDMgMCBSIC9SZXNvdXJjZXMgNiAwIFIgL0NvbnRlbnRzIDQgMCBSIC9NZWRp + YUJveCBbMCAwIDUxMiA1MTJdCj4+CmVuZG9iago2IDAgb2JqCjw8IC9Qcm9jU2V0IFsg + L1BERiAvVGV4dCAvSW1hZ2VCIC9JbWFnZUMgL0ltYWdlSSBdIC9Db2xvclNwYWNlIDw8 + IC9DczIgMTIgMCBSCi9DczEgNyAwIFIgPj4gL0V4dEdTdGF0ZSA8PCAvR3MxIDE3IDAg + UiAvR3MyIDE4IDAgUiA+PiAvRm9udCA8PCAvRjEuMCAxMSAwIFIKPj4gL1hPYmplY3Qg + PDwgL0ltMiAxMyAwIFIgL0ltMSA4IDAgUiAvSW0zIDE1IDAgUiA+PiAvU2hhZGluZyA8 + PCAvU2gxIDEwIDAgUgo+PiA+PgplbmRvYmoKMTAgMCBvYmoKPDwgL0NvbG9yU3BhY2Ug + NyAwIFIgL1NoYWRpbmdUeXBlIDMgL0Nvb3JkcyBbIC0yNzEuMzA2MyAwIDAgLTI3MS4z + MDYzIDAgNTQwLjI2NApdIC9Eb21haW4gWyAwIDEgXSAvRXh0ZW5kIFsgZmFsc2UgZmFs + c2UgXSAvRnVuY3Rpb24gMTkgMCBSID4+CmVuZG9iagoxMyAwIG9iago8PCAvTGVuZ3Ro + IDE0IDAgUiAvVHlwZSAvWE9iamVjdCAvU3VidHlwZSAvSW1hZ2UgL1dpZHRoIDI1NiAv + SGVpZ2h0IDI1NiAvQ29sb3JTcGFjZQo3IDAgUiAvU01hc2sgMjAgMCBSIC9CaXRzUGVy + Q29tcG9uZW50IDggL0ZpbHRlciAvRmxhdGVEZWNvZGUgPj4Kc3RyZWFtCngB7dABAQAA + CAKg/p+2Bx4QJpBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIAB + AwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBg + wIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYM + GDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIAB + AwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBg + wIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYM + GDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIAB + AwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBg + wIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYM + GDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIAB + AwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBg + wIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYM + GDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIAB + AwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBg + wIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYM + GDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIAB + AwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDDQBg4DBgwYMGDA + wNjAA65NNU0KZW5kc3RyZWFtCmVuZG9iagoxNCAwIG9iago4ODMKZW5kb2JqCjggMCBv + YmoKPDwgL0xlbmd0aCA5IDAgUiAvVHlwZSAvWE9iamVjdCAvU3VidHlwZSAvSW1hZ2Ug + L1dpZHRoIDkxMiAvSGVpZ2h0IDkyNiAvQ29sb3JTcGFjZQoyMiAwIFIgL1NNYXNrIDIz + IDAgUiAvQml0c1BlckNvbXBvbmVudCA4IC9GaWx0ZXIgL0ZsYXRlRGVjb2RlID4+CnN0 + cmVhbQp4Ae3QgQAAAADDoPlTH+SFUGHAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + gAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwY + MGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAED + BgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDA + wMvAAKraAAEKZW5kc3RyZWFtCmVuZG9iago5IDAgb2JqCjExMDcwCmVuZG9iagoxNSAw + IG9iago8PCAvTGVuZ3RoIDE2IDAgUiAvVHlwZSAvWE9iamVjdCAvU3VidHlwZSAvSW1h + Z2UgL1dpZHRoIDI1NiAvSGVpZ2h0IDI1NiAvQ29sb3JTcGFjZQo3IDAgUiAvU01hc2sg + MjUgMCBSIC9CaXRzUGVyQ29tcG9uZW50IDggL0ZpbHRlciAvRmxhdGVEZWNvZGUgPj4K + c3RyZWFtCngB7dKBDQAgDMMw/n+6SHBGvA+aeXMKFAucd8XlNiuw8U9BuQD/5e/bzj8D + 5QL8l79vO/8MlAvwX/6+7fwzUC7Af/n7tvPPQLkA/+Xv284/A+UC/Je/bzv/DJQL8F/+ + vu38M1AuwH/5+7bzz0C5AP/l79vOPwPlAvyXv287/wyUC/Bf/r7t/DNQLsB/+fu2889A + uQD/5e/bzj8D5QL8l79vO/8MlAvwX/6+7fwzUC7Af/n7tvPPQLkA/+Xv284/A+UC/Je/ + bzv/DJQL8F/+vu38M1AuwH/5+7bzz0C5AP/l79vOPwPlAvyXv287/wyUC/Bf/r7t/DNQ + LsB/+fu2889AuQD/5e/bzj8D5QL8l79vO/8MlAvwX/6+7fwzUC7Af/n7tvPPQLkA/+Xv + 284/A+UC/Je/bzv/DJQL8F/+vu38M1AuwH/5+7bzz0C5AP/l79vOPwPlAvyXv287/wyU + C/Bf/r7t/DNQLsB/+fu2889AuQD/5e/bzj8D5QL8l79vO/8MlAvwX/6+7fwzUC7Af/n7 + tvPPQLkA/+Xv284/A+UC/Je/bzv/DJQL8F/+vu38M1AuwH/5+7bzz0C5AP/l79vOPwPl + AvyXv287/wyUC/Bf/r7t/DNQLsB/+fu2889AuQD/5e/bzj8D5QL8l79vO/8MlAvwX/6+ + 7fwzUC7Af/n7tvPPQLkA/+Xv284/A+UC/Je/bzv/DJQL8F/+vu38M1AuwH/5+7bzz0C5 + AP/l79vOPwPlAvyXv287/wyUC/Bf/r7t/DNQLsB/+fu2889AuQD/5e/bzj8D5QL8l79v + O/8MlAvwX/6+7fwzUC7Af/n7tvPPQLkA/+Xv284/A+UC/Je/bzv/DJQL8F/+vu38M1Au + wH/5+7bzz0C5AP/l79vOPwPlAvyXv287/wyUC/Bf/r7t/DNQLsB/+fu2889AuQD/5e/b + zj8D5QL8l79vO/8MlAvwX/6+7fwzUC7Af/n7tvPPQLkA/+Xv284/A+UC/Je/bzv/DJQL + 8F/+vu38M1AuwH/5+7bzz0C5AP/l79vOPwPlAvyXv287/wyUC/Bf/r7t/DNQLsB/+fu2 + 889AuQD/5e/bzj8D5QL8l79vO/8MlAvwX/6+7fwzUC7Af/n7tvPPQLkA/+Xv284/A+UC + /Je/bzv/DJQL8F/+vu38M1AuwH/5+7bzz0C5AP/l79vOPwPlAvyXv287/wyUC/Bf/r7t + /DNQLsB/+fu2889AuQD/5e/bzj8D5QL8l79vO/8MlAvwX/6+7fwzUC7Af/n7tvPPQLkA + /+Xv284/A+UC/Je/bzv/DJQL8F/+vu38M1AuwH/5+7bzz0C5AP/l79vOPwPlAvyXv287 + /wyUC/Bf/r7t/DNQLsB/+fu2889AuQD/5e/bzj8D5QL8l79vO/8MlAvwX/6+7fwzUC7A + f/n7tvPPQLkA/+Xv284/A+UC/Je/bzv/DJQL8F/+vu38M1AuwH/5+7bzz0C5AP/l79vO + PwPlAvyXv287/wyUC/Bf/r7t/DNQLsB/+fu2889AuQD/5e/bzj8D5QL8l79vO/8MlAvw + X/6+7fwzUC7Af/n7tvPPQLkA/+Xv284/A+UC/Je/bzv/DJQL8F/+vu3f/wUgwjJ6CmVu + ZHN0cmVhbQplbmRvYmoKMTYgMCBvYmoKMTIxNAplbmRvYmoKMjMgMCBvYmoKPDwgL0xl + bmd0aCAyNCAwIFIgL1R5cGUgL1hPYmplY3QgL1N1YnR5cGUgL0ltYWdlIC9XaWR0aCA5 + MTIgL0hlaWdodCA5MjYgL0NvbG9yU3BhY2UKL0RldmljZUdyYXkgL0JpdHNQZXJDb21w + b25lbnQgOCAvRmlsdGVyIC9GbGF0ZURlY29kZSA+PgpzdHJlYW0KeAHsnYlfTV37/288 + j4xJUVJRGtCEoggliZCxyFQUlUiGNCgJIXOFyFTKlCljmUMlUaZK4XY/N31fv//jd621 + 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 + 5IiRegajjceajhs/cbKF5RRriMrp021nzJg5c6YdPmgByVkAHH/GDFvb6dOnTbWZYmUx + edIEM1OTMYYGo3R1ZNFIdjl6BI4sJGlEwtbHwEGwGzlcR1ffYLTRGIhJs/ETJk6abGFh + YWlpZWU1BR+0gOQsAI5vZWkJMTB50sTx5uMgFoGMo/RGjtCmuhHY2NPRCEEJi1aGkb8P + GjwUQhIoCTFpCEFpYmoKcWlmZm5uPh4ftIDkLACOb25mNm6cqanJ2DHGRqMNRunr6kAw + wkJ1IFmpqiAaCSXZiKSQJCEJmBypq6c/ygDC0tDIyMgYnjH4oAUkZgHi98ZGRoaGo0cb + QCjqQSwOBzIOgZQqKcdRUTSSiKS7kb/++huEJFByyFCtYRCUI3QgLHX19PT0uWcUPmgB + aViAc3lwf13dkSMhEodr01gEMrJo7FndSMOQ9y8MJJmQBDEJQQlRCWFJn+H4oAWkaAHG + /YcN09ICLA5mYpGsU3s4icOLQ94faUhCGR1gEjj5+8CBgwYNGjx48BD5MxQfTbSA/APh + n2QW+JkPJXvJEIhCiMNBA4GKvw8Y0B9iEZapqiUjLyJh3UqyO/36wdr1t/79IS5JYOKD + FpCyBSAKIBZ+I6FIY7Entzf4wdfdn2lQsmH5Kzy/4YMWkKgFiP+TOCSRCFjs0a3G7uKv + m39Ofh0eGAc+aAHpWoAJA3VDsZugxH+MFkALoAXQAmgBtABaAC2AFkALoAXQAmgBtABa + AC2AFkALoAXQAmgBtABaoFct8P/b5umcCmVuZHN0cmVhbQplbmRvYmoKMjQgMCBvYmoK + NDQ4NjUKZW5kb2JqCjIwIDAgb2JqCjw8IC9MZW5ndGggMjEgMCBSIC9UeXBlIC9YT2Jq + ZWN0IC9TdWJ0eXBlIC9JbWFnZSAvV2lkdGggMjU2IC9IZWlnaHQgMjU2IC9Db2xvclNw + YWNlCi9EZXZpY2VHcmF5IC9CaXRzUGVyQ29tcG9uZW50IDggL0ZpbHRlciAvRmxhdGVE + ZWNvZGUgPj4Kc3RyZWFtCngB7dCFQQIAAABBsTswEAxsBQWD/XdzkL8b4Vartj8MGDBg + IDnwiwEDBgwkB34wYMCAgeTANwYMGDCQHFhiwIABA8mBBQYMGDCQHPjCgAEDBpIDnxgw + YMCAgeDAHAMGDBhIDswwYMCAgeTABwYMGDCQHHjHgAEDBpIDbxgwYMBAcuAVAwYMGEgO + vGDAgAEDBoIDzxgwYMBAcuAJAwYMGEgOPGLAgAEDyYEHDBgwYCA5MMWAAQMGkgP3GDBg + wEBy4A4DBgwYSA7cYsCAAQMGggM3GDBgwEByYIIBAwYMJAfGGDBgIDpwjQED2YERBgxk + B64wYCA7cIkBA9mBCwyEB84xYCA7MMSAgezAGQYMZAdOMWAgO3CCAQPZgWMMGMgOHGHA + QHbgEAMGsgMHGDCQHdjHQHhgDwMGsgO7GDCQHdjBgIHswDYGDGQHtjBgIDuwiQED2YEN + DBjIDqxjwEB2YIABA9mBNQyEB/4BLgXylgplbmRzdHJlYW0KZW5kb2JqCjIxIDAgb2Jq + CjM2NgplbmRvYmoKMjUgMCBvYmoKPDwgL0xlbmd0aCAyNiAwIFIgL1R5cGUgL1hPYmpl + Y3QgL1N1YnR5cGUgL0ltYWdlIC9XaWR0aCAyNTYgL0hlaWdodCAyNTYgL0NvbG9yU3Bh + Y2UKL0RldmljZUdyYXkgL0JpdHNQZXJDb21wb25lbnQgOCAvRmlsdGVyIC9GbGF0ZURl + Y29kZSA+PgpzdHJlYW0KeAHt3Qs/k2EYB+A5j2HOZpjz+TxzNkQSSSJJJJEkkkgStq/e + B+n6CPtte9/nuQ//K51KPT483N//vbv7c3v7++bm1/X11dXPy8sfFxffz8/Pzr6dnn49 + OflyfPz56Ojw8NPBwcf9/Q97e7u773d23m1vv93aerO5ubHxen391dray9XVFysry8vP + l5aeLS4+XViYn38yNzc7OzOTTCanp6empiYnJyYmxsfHxsZGR0dGEonE8HA8Hh8aGhwc + HBjo7+/v6+vt7e3p6e7u6urq7Ozo6Ghvb2tra21taWlpbm5qampsbGiIxWL19XV1dbW1 + 0Wi0piYSiVRXV1VVVVZWVJSXl5eVlZaWlpSEw+Hi4qKiosLCUChUUJCfHwwG8/Jyc3Nz + crKzs7OyMjMzMzICgbTP7/v3+/f/9/zz/Pf+8/53/nH+c/51/nf/cf9z/3X/V/9Q/1H/ + Uv9T/1T/Vf9W/9f/0P/R/9L/0//U/9X/1v83/2D+w/yL+R/zT+a/zL+Z/zP/aP7T/Kv5 + X/PP5r/Nv5v/t/9g/8P+i/0f+0/2v+y/2f+z/2j/0/6r/V/7z/a/7b/b/5d/IP9B/oX8 + D/kn8l/k38j/kX8k/0n+lfwv+Wfy3+Tfyf+Tfyj/Uf6l/E/5p/Jf5d/K/5V/LP9Z/rX8 + b/nn8t/l38v/5x/wH/gX/A/+Cf+Ff8P/4R/xn/hX/C/+Gf+Nf8f/4x/yH/mX/E/+Kf+V + f8v/5R/zn/nX/G/+Of89IxBIp1L8d/47/53/zn/nv/Pf+e/8d/47/53/zn/nv/Pf+e/8 + d/47/53/zn/nv/Pf+e/8d/47/53/zn/nv/Pf+e/8d/47/53/zn/nv/Pf+e/8d/47/53/ + zn/nv/Pf+e/8d/47/53/zn/nv/Pf+e/8d/47/53/zn/nv/Pf+e/8d/47/53/zn/nv/Pf + +e/8d/47/53/zn/nv/Pf+e/8d/47/53/zn/nv/Pf+e/8d/47/53/zn/nv/Pf+e/8d/47 + /53/zn/nv/Pf+e/8d/47/53/zn/nv/Pf+e/8d/47/53/zn/nv/Pf+e/8d/47/53/zn/n + vwfSqdTjw3/sv/8DK6twSQplbmRzdHJlYW0KZW5kb2JqCjI2IDAgb2JqCjgxNwplbmRv + YmoKMTcgMCBvYmoKPDwgL1R5cGUgL0V4dEdTdGF0ZSAvY2EgMCA+PgplbmRvYmoKMTgg + MCBvYmoKPDwgL1R5cGUgL0V4dEdTdGF0ZSAvY2EgMSA+PgplbmRvYmoKMjcgMCBvYmoK + PDwgL0xlbmd0aCAyOCAwIFIgL04gMSAvQWx0ZXJuYXRlIC9EZXZpY2VHcmF5IC9GaWx0 + ZXIgL0ZsYXRlRGVjb2RlID4+CnN0cmVhbQp4AYVST0gUURz+zTYShIhBhXiIdwoJlSms + rKDadnVZlW1bldKiGGffuqOzM9Ob2TXFkwRdojx1D6JjdOzQoZuXosCsS9cgqSAIPHXo + +83s6iiEb3k73/v9/X7fe0RtnabvOylBVHNDlSulp25OTYuDHylFHdROWKYV+OlicYyx + 67mSv7vX1mfS2LLex7V2+/Y9tZVlYCHqLba3EPohkWYAH5mfKGWAs8Adlq/YPgE8WA6s + GvAjogMPmrkw09GcdKWyLZFT5qIoKq9iO0mu+/m5xr6LtYmD/lyPZtaOvbPqqtFM1LT3 + RKG8D65EGc9fVPZsNRSnDeOcSEMaKfKu1d8rTMcRkSsQSgZSNWS5n2pOnXXgdRi7XbqT + 4/j2EKU+yWCoibXpspkdhX0AdirL7BDwBejxsmIP54F7Yf9bUcOTwCdhP2SHedatH/YX + rlPge4Q9NeDOFK7F8dqKH14tAUP3VCNojHNNxNPXOXOkiO8x1BmY90Y5pgsxd5aqEzeA + O2EfWapmCrFd+67qJe57AnfT4zvRmzkLXKAcSXKxFdkU0DwJWBR9i7BJDjw+zh5V4Heo + mMAcuYnczSj3HtURG2ejUoFWeo1Xxk/jufHF+GVsGM+Afqx213t8/+njFXXXtj48+Y16 + 3DmuvZ0bVWFWcWUL3f/HMoSP2Sc5psHToVlYa9h25A+azEywDCjEfwU+l/qSE1Xc1e7t + uEUSzFA+LGwluktUbinU6j2DSqwcK9gAdnCSxCxaHLhTa7o5eHfYInpt+U1XsuuG/vr2 + evva8h5tyqgpKBPNs0RmlLFbo+TdeNv9ZpERnzg6vue9ilrJ/klFED+FOVoq8hRV9FZQ + 1sRvZw5+G7Z+XD+l5/VB/TwJPa2f0a/ooxG+DHRJz8JzUR+jSfCwaSHiEqCKgzPUTlRj + jQPiKfHytFtkkf0PQBn9ZgplbmRzdHJlYW0KZW5kb2JqCjI4IDAgb2JqCjcwNAplbmRv + YmoKMTIgMCBvYmoKWyAvSUNDQmFzZWQgMjcgMCBSIF0KZW5kb2JqCjI5IDAgb2JqCjw8 + IC9MZW5ndGggMzAgMCBSIC9OIDMgL0FsdGVybmF0ZSAvRGV2aWNlUkdCIC9GaWx0ZXIg + L0ZsYXRlRGVjb2RlID4+CnN0cmVhbQp4Aa2TzWsTQRjGn02QCtZQi0jx4oJSPERdkhbb + W9t8SOwSlySlfhw02d1soslm3d1ErR561H+gFEQQPOjBmxc9tSeR4ieC9OBdUU9a6qGU + 9Z0Zd4Ng8eK7zMxvH555Z+adXSC+UXWcVgxA2/bd0qkZ+ey58/LAOiQcQgJJyFXdc6Y1 + TSXLDrH5gdwU74+xXF9iz7c+fdtz58Li7bXDSw9bO0wK5YRLCwJSkoT9luApxjXBFcbX + fMcnT4Ox3qgaxLeIk26llCF+RJywBD9lXBP8gnFPt9jcdWLFNpo2ENtNPGGYnk48RWwZ + nt4mpjwS2u0O5Y+znEd1x6W58bfER1hdaKS4mgAmv5J+r69dPAE8WQMOaH1tdBkYvgQ8 + m+xrP97wWkkjNa+eTvF00uBBYNdqEHwPgAHyb78Ogq3lINheojU2gJWi3nV73EsblF4B + /3oXZxbZgXd0Bgp+R39nURfuUoAHq0BlEVBpvEvj6E9g301AA+lUpnQ6bKKGJAN7szlV + lVNjynhem+fKf+zarS7dFY9h6gftWvEMjSPUPju+RpsS7PXKuZDrzXwhZKOanQ15oZEp + hlx386WQL1dPswPynKY9Vw7ZafFv//daM5Hf9HKRZ6FRYWfmHrdbmgv5Smc28htmNtqb + 3Sqy/4n7m34h2j+yyEGlR0YKY1AwjjyVfV58kzQDQ4+B+0PKyXR55eNLJvwRvnmd33Wm + 49xwm1bDl6fpjzSTcsHWjyfllKJM4BeDarM/CmVuZHN0cmVhbQplbmRvYmoKMzAgMCBv + YmoKNTY1CmVuZG9iagoyMiAwIG9iagpbIC9JQ0NCYXNlZCAyOSAwIFIgXQplbmRvYmoK + MzEgMCBvYmoKPDwgL0xlbmd0aCAzMiAwIFIgL04gMyAvQWx0ZXJuYXRlIC9EZXZpY2VS + R0IgL0ZpbHRlciAvRmxhdGVEZWNvZGUgPj4Kc3RyZWFtCngBhZRNSBRhGMf/s40EsQbR + lwjF0MEkVCYLUgLT9StTtmXVTAlinX13nRxnp5ndLUUihOiYdYwuVkSHiE7hoUOnOkQE + mXWJoKNFEAVeIrb/O5O7Y1S+MDO/eZ7/+3y9wwBVj1KOY0U0YMrOu8nemHZ6dEzb/BpV + qEYUXCnDczoSiQGfqZXP9Wv1LRRpWWqUsdb7NnyrdpkQUDQqd2QDPix5PODjki/knTw1 + ZyQbE6k02SE3uEPJTvIt8tZsiMdDnBaeAVS1U5MzHJdxIjvILUUjK2M+IOt22rTJ76U9 + 7RlT1LDfyDc5C9q48v1A2x5g04uKbcwDHtwDdtdVbPU1wM4RYPFQxfY96c9H2fXKyxxq + 9sMp0Rhr+lAqfa8DNt8Afl4vlX7cLpV+3mEO1vHUMgpu0deyMOUlENQb7Gb85Br9i4Oe + fFULsMA5jmwB+q8ANz8C+x8C2x8DiWpgqBWRy2w3uPLiIucCdOacadfMTuS1Zl0/onXw + aIXWZxtNDVrKsjTf5Wmu8IRbFOkmTFkFztlf23iPCnt4kE/2F7kkvO7frMylU12cJZrY + 1qe06OomN5DvZ8yePnI9r/cZt2c4YOWAme8bCjhyyrbiPBepidTY4/GTZMZXVCcfk/OQ + POcVB2VM334udSJBrqU9OZnrl5pd3Ns+MzHEM5KsWDMTnfHf/MYtJGXefdTcdSz/m2dt + kWcYhQUBEzbvNjQk0YsYGuHARQ4ZekwqTFqlX9BqwsPkX5UWEuVdFhW9WOGeFX/PeRS4 + W8Y/hVgccw3lCJr+Tv+iL+sL+l3983xtob7imXPPmsara18ZV2aW1ci4QY0yvqwpiG+w + 2g56LWRpneIV9OSV9Y3h6jL2fG3Zo8kc4mp8NdSlCGVqxDjjya5l90WyxTfh51vL9q/p + Uft89klNJdeyunhmKfp8NlwNa/+zq2DSsqvw5I2QLjxroe5VD6p9aovaCk09prarbWoX + 346qA+Udw5yViQus22X1KfZgY5reyklXZovg38Ivhv+lXmEL1zQ0+Q9NuLmMaQnfEdw2 + cIeU/8NfswMN3gplbmRzdHJlYW0KZW5kb2JqCjMyIDAgb2JqCjc5MgplbmRvYmoKNyAw + IG9iagpbIC9JQ0NCYXNlZCAzMSAwIFIgXQplbmRvYmoKMTkgMCBvYmoKPDwgL0xlbmd0 + aCAzMyAwIFIgL0Z1bmN0aW9uVHlwZSAwIC9CaXRzUGVyU2FtcGxlIDggL1NpemUgWyAx + MzY1IF0gL0RvbWFpbgpbIDAgMSBdIC9SYW5nZSBbIDAgMSAwIDEgMCAxIF0gL0ZpbHRl + ciAvRmxhdGVEZWNvZGUgPj4Kc3RyZWFtCngBzcLXcQJAEAXByT8vjLDCO4HwIoobfRPA + K7arW4f22V1akT1anX3ae/t85hfWOcAih1jnCIscY50TLHKKdX5jkTMsco51LrDIJda5 + wiLXWOcGQ7cYusPEPYYeMPEHQ4+YeMLQXww9Y+IFQ6+YeMPQO4Y+MPGJoX+Y+MLAf+tF + vN4KZW5kc3RyZWFtCmVuZG9iagozMyAwIG9iagoxMzAKZW5kb2JqCjMgMCBvYmoKPDwg + L1R5cGUgL1BhZ2VzIC9NZWRpYUJveCBbMCAwIDUxMiA1MTJdIC9Db3VudCAxIC9LaWRz + IFsgMiAwIFIgXSA+PgplbmRvYmoKMzQgMCBvYmoKPDwgL1R5cGUgL0NhdGFsb2cgL1Bh + Z2VzIDMgMCBSIC9WZXJzaW9uIC8xLjQgPj4KZW5kb2JqCjM1IDAgb2JqCjw8IC9MZW5n + dGggMzYgMCBSIC9MZW5ndGgxIDUwMzIgL0ZpbHRlciAvRmxhdGVEZWNvZGUgPj4Kc3Ry + ZWFtCngB7TiNfxNVtufcOzOZNkDT0pbQAJl0aJWm2QIKlEJpaJKWNkILVDepsCb9kADF + Flo+n9Iqq2L4sK7a1d+u6K6rFaw6bZENRZevxdUVfn6ivLfig4X14z0R9Af7VoHOO5Nq + BZd9f8Gbk3vn3nPOveeej3vu3AACwBBoAw7u2mXhJpBhGmEOU7HWrmpR3tm342NqnwaQ + V9zetGhZ6hcziZ4QABDKFzWsvT0yatrbAEN/RzyvR+rDdWeXqscBhjVQf3KEEMPGCEQf + ZtDHRpa1rJH3Yxf1X6e+3NBYG4Y6SKc+8YC0LLymiT0q3k39Y9RX7ggvqx+59bl11P8H + 9dWmxuYWvQpuB0gaRf3sphX1TWeOuN6jvpfWEyYcEhjPEJCoADgMjNgLILbBGCojeA9k + 6hf0v1L5zCj9s4j2NCT31wDwEmpvi4+/qhJP0OyeAZT+L56rBlyzMzAQDE2Ncu2nHMxQ + AltABAsI8Az1zsJxuA8klGEyJEM+vIAfgQcq4R4shJ8TTwaUwQuwGz7ARfolEPSgfgCW + QidOIv0zoAAWwF44T3x/gp9CGNZAK96j30XWSYQxkAk3kDVXwDo4SRwJkAY2yMQsNo0f + gxE0sgHaYAPsFir0Z/XjNCaBoAKeheegH6sxou/STxEmH6aCD+bDHcT7AIqYJo7R/6Kf + 18/DRFrnQlhE8yyHx2AXmnA4OvEPrIh3wDjSsxGaIAod8Ev4M2bjm7xS3w4uggkwh+a7 + FWpozi3QB2/ANyixGraGvcB28RX8ogDCUfFtyae/ph+hyBXBSmMKaMRtFE+Lad4H4R34 + EP4KX8HXmIaZOAEn4wwsw6WsQTJLP9X30BgzjAaFbJADebTSAigmmEe2uBMehS54BV4l + OABfokJwHcE4vBWfwR48xZKYwg6zd9g3PJ/7+aOCVVgo3Ck+Jp6UcnSPvosslQJ2UGEK + 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 + Q1aLMVmjIaszPu5HsroMcqchq8uQ1WXI6nR3xmWhb/H8YrJVoFuG4qBnwcC7l5kTyT0h + myNYHPfTNId1va2PXcACMNMpMoQ+PoZSMVzomumaSSQP88RJw4zvku9I1vXTHLY+LGAX + 4iQLoZMpaPAHLxmtZmdzc/PKFqpaVkLLSoJm4miOU5zQMsBMwfi/LcjbTgplbmRzdHJl + YW0KZW5kb2JqCjM2IDAgb2JqCjM0NTYKZW5kb2JqCjM3IDAgb2JqCjw8IC9UeXBlIC9G + b250RGVzY3JpcHRvciAvQXNjZW50IDc1MSAvQ2FwSGVpZ2h0IDY5NSAvRGVzY2VudCAt + MzE5IC9GbGFncyAzMgovRm9udEJCb3ggWy0xNzYgLTIyNyAxMDc2IDkxM10gL0ZvbnRO + YW1lIC9EUUJQSVYrQ2FsaXNNVEJvbCAvSXRhbGljQW5nbGUgMAovU3RlbVYgMCAvTGVh + ZGluZyAxMTEgL01heFdpZHRoIDEwOTQgL1hIZWlnaHQgNDcxIC9Gb250RmlsZTIgMzUg + MCBSID4+CmVuZG9iagozOCAwIG9iagpbIDM0NCBdCmVuZG9iagoxMSAwIG9iago8PCAv + VHlwZSAvRm9udCAvU3VidHlwZSAvVHJ1ZVR5cGUgL0Jhc2VGb250IC9EUUJQSVYrQ2Fs + aXNNVEJvbCAvRm9udERlc2NyaXB0b3IKMzcgMCBSIC9XaWR0aHMgMzggMCBSIC9GaXJz + dENoYXIgMzMgL0xhc3RDaGFyIDMzIC9FbmNvZGluZyAvTWFjUm9tYW5FbmNvZGluZwo+ + PgplbmRvYmoKMSAwIG9iago8PCAvVGl0bGUgKFVudGl0bGVkKSAvQXV0aG9yIChQcmVz + dG9uIEphY2tzb24pIC9DcmVhdG9yIChPbW5pR3JhZmZsZSBQcm9mZXNzaW9uYWwpCi9Q + cm9kdWNlciAoTWFjIE9TIFggMTAuNS41IFF1YXJ0eiBQREZDb250ZXh0KSAvQ3JlYXRp + b25EYXRlIChEOjIwMDgxMTE3MTg0NzE0WjAwJzAwJykKL01vZERhdGUgKEQ6MjAwODEx + MTcxODQ3MTRaMDAnMDAnKSA+PgplbmRvYmoKeHJlZgowIDM5CjAwMDAwMDAwMDAgNjU1 + MzUgZiAKMDAwMDA2OTExNCAwMDAwMCBuIAowMDAwMDAxMDk4IDAwMDAwIG4gCjAwMDAw + NjQ5NjMgMDAwMDAgbiAKMDAwMDAwMDAyMiAwMDAwMCBuIAowMDAwMDAxMDc5IDAwMDAw + IG4gCjAwMDAwMDEyMDIgMDAwMDAgbiAKMDAwMDA2NDYxNCAwMDAwMCBuIAowMDAwMDAy + Njk4IDAwMDAwIG4gCjAwMDAwMTM5NDkgMDAwMDAgbiAKMDAwMDAwMTQ1NSAwMDAwMCBu + IAowMDAwMDY4OTM5IDAwMDAwIG4gCjAwMDAwNjI5MzcgMDAwMDAgbiAKMDAwMDAwMTYx + MyAwMDAwMCBuIAowMDAwMDAyNjc4IDAwMDAwIG4gCjAwMDAwMTM5NzAgMDAwMDAgbiAK + MDAwMDAxNTM2NiAwMDAwMCBuIAowMDAwMDYyMDE5IDAwMDAwIG4gCjAwMDAwNjIwNjQg + MDAwMDAgbiAKMDAwMDA2NDY1MCAwMDAwMCBuIAowMDAwMDYwNDQ4IDAwMDAwIG4gCjAw + MDAwNjA5ODggMDAwMDAgbiAKMDAwMDA2MzY2MiAwMDAwMCBuIAowMDAwMDE1Mzg3IDAw + MDAwIG4gCjAwMDAwNjA0MjYgMDAwMDAgbiAKMDAwMDA2MTAwOCAwMDAwMCBuIAowMDAw + MDYxOTk5IDAwMDAwIG4gCjAwMDAwNjIxMDkgMDAwMDAgbiAKMDAwMDA2MjkxNyAwMDAw + MCBuIAowMDAwMDYyOTc0IDAwMDAwIG4gCjAwMDAwNjM2NDIgMDAwMDAgbiAKMDAwMDA2 + MzY5OSAwMDAwMCBuIAowMDAwMDY0NTk0IDAwMDAwIG4gCjAwMDAwNjQ5NDMgMDAwMDAg + biAKMDAwMDA2NTA0NiAwMDAwMCBuIAowMDAwMDY1MTEwIDAwMDAwIG4gCjAwMDAwNjg2 + NTYgMDAwMDAgbiAKMDAwMDA2ODY3NyAwMDAwMCBuIAowMDAwMDY4OTE1IDAwMDAwIG4g + CnRyYWlsZXIKPDwgL1NpemUgMzkgL1Jvb3QgMzQgMCBSIC9JbmZvIDEgMCBSIC9JRCBb + IDw0OWU2MjQzZGUwYzBiMTQ0NmRmMDQzNjRjNzc1ZGNlZj4KPDQ5ZTYyNDNkZTBjMGIx + NDQ2ZGYwNDM2NGM3NzVkY2VmPiBdID4+CnN0YXJ0eHJlZgo2OTMzNgolJUVPRgoxIDAg + b2JqCjw8L0F1dGhvciAoUHJlc3RvbiBKYWNrc29uKS9DcmVhdGlvbkRhdGUgKEQ6MjAw + ODExMTQyMzU4MDBaKS9DcmVhdG9yIChPbW5pR3JhZmZsZSBQcm9mZXNzaW9uYWwgNS4x + IHJjIDEpL01vZERhdGUgKEQ6MjAwODExMTcxODQxMDBaKS9Qcm9kdWNlciAoTWFjIE9T + IFggMTAuNS41IFF1YXJ0eiBQREZDb250ZXh0KS9UaXRsZSAoUmVwb3J0ZXJJY29uLmdy + YWZmbGUpPj4KZW5kb2JqCnhyZWYKMSAxCjAwMDAwNzAyNzQgMDAwMDAgbiAKdHJhaWxl + cgo8PC9JRCBbPDQ5ZTYyNDNkZTBjMGIxNDQ2ZGYwNDM2NGM3NzVkY2VmPiA8NDllNjI0 + M2RlMGMwYjE0NDZkZjA0MzY0Yzc3NWRjZWY+XSAvSW5mbyAxIDAgUiAvUHJldiA2OTMz + NiAvUm9vdCAzNCAwIFIgL1NpemUgMzk+PgpzdGFydHhyZWYKNzA0OTgKJSVFT0YK + </data> + <key>QuickLookThumbnail</key> + <data> + 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 + </data> + <key>ReadOnly</key> + <string>NO</string> + <key>RowAlign</key> + <integer>1</integer> + <key>RowSpacing</key> + <real>36</real> + <key>SheetTitle</key> + <string>Canvas 1</string> + <key>SmartAlignmentGuidesActive</key> + <string>NO</string> + <key>SmartDistanceGuidesActive</key> + <string>NO</string> + <key>UniqueID</key> + <integer>1</integer> + <key>UseEntirePage</key> + <false/> + <key>VPages</key> + <integer>1</integer> + <key>WindowInfo</key> + <dict> + <key>CurrentSheet</key> + <integer>0</integer> + <key>ExpandedCanvases</key> + <array> + <dict> + <key>name</key> + <string>Canvas 1</string> + </dict> + </array> + <key>Frame</key> + <string>{{2002, 20}, {1215, 1180}}</string> + <key>ListView</key> + <true/> + <key>OutlineWidth</key> + <integer>142</integer> + <key>RightSidebar</key> + <false/> + <key>Sidebar</key> + <true/> + <key>SidebarWidth</key> + <integer>157</integer> + <key>VisibleRegion</key> + <string>{{-4.5, 0.5}, {522, 535.5}}</string> + <key>Zoom</key> + <real>2</real> + <key>ZoomValues</key> + <array> + <array> + <string>Canvas 1</string> + <real>2</real> + <real>4</real> + </array> + </array> + </dict> + <key>saveQuickLookFiles</key> + <string>YES</string> +</dict> +</plist> diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/crash_report_sender-Info.plist b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/crash_report_sender-Info.plist new file mode 100644 index 000000000..976687e11 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/crash_report_sender-Info.plist @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleDisplayName</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleExecutable</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleIconFile</key> + <string>crash_report_sender</string> + <key>CFBundleIdentifier</key> + <string>com.Breakpad.${PRODUCT_NAME:identifier}</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1.0</string> + <key>LSHasLocalizedDisplayName</key> + <true/> + <key>NSMainNibFile</key> + <string>MainMenu</string> + <key>NSPrincipalClass</key> + <string>NSApplication</string> +</dict> +</plist> diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/crash_report_sender.h b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/crash_report_sender.h new file mode 100644 index 000000000..6a29d48a1 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/crash_report_sender.h @@ -0,0 +1,117 @@ +// Copyright (c) 2006, 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. +// +// This component uses the HTTPMultipartUpload of the breakpad project to send +// the minidump and associated data to the crash reporting servers. +// It will perform throttling based on the parameters passed to it and will +// prompt the user to send the minidump. + +#import <Cocoa/Cocoa.h> + +#include "client/mac/sender/uploader.h" +#import "GTMDefines.h" + +// We're sublcassing NSTextField in order to override a particular +// method (see the implementation) that lets us reject changes if they +// are longer than a particular length. Bindings would normally solve +// this problem, but when we implemented a validation method, and +// returned NO for strings that were too long, the UI was not updated +// right away, which was a poor user experience. The UI would be +// updated as soon as the text field lost first responder status, +// which isn't soon enough. It is a known bug that the UI KVO didn't +// work in the middle of a validation. +@interface LengthLimitingTextField : NSTextField { + @private + NSUInteger maximumLength_; +} + +- (void)setMaximumLength:(NSUInteger)maxLength; +@end + +@interface Reporter : NSObject { + @public + IBOutlet NSWindow *alertWindow_; // The alert window + + // Grouping boxes used for resizing. + IBOutlet NSBox *headerBox_; + IBOutlet NSBox *preEmailBox_; + IBOutlet NSBox *emailSectionBox_; + // Localized elements (or things that need to be moved during localization). + IBOutlet NSTextField *dialogTitle_; + IBOutlet NSTextField *commentMessage_; + IBOutlet NSTextField *emailMessage_; + IBOutlet NSTextField *emailLabel_; + IBOutlet NSTextField *privacyLinkLabel_; + IBOutlet NSButton *sendButton_; + IBOutlet NSButton *cancelButton_; + IBOutlet LengthLimitingTextField *emailEntryField_; + IBOutlet LengthLimitingTextField *commentsEntryField_; + IBOutlet NSTextField *countdownLabel_; + IBOutlet NSView *privacyLinkArrow_; + + // Text field bindings, for user input. + NSString *commentsValue_; // Comments from the user + NSString *emailValue_; // Email from the user + NSString *countdownMessage_; // Message indicating time + // left for input. + @private + NSTimeInterval remainingDialogTime_; // Keeps track of how long + // we have until we cancel + // the dialog + NSTimer *messageTimer_; // Timer we use to update + // the dialog + Uploader* uploader_; // Uploader we use to send the data. +} + +// Stops the modal panel with an NSAlertDefaultReturn value. This is the action +// invoked by the "Send Report" button. +- (IBAction)sendReport:(id)sender; +// Stops the modal panel with an NSAlertAlternateReturn value. This is the +// action invoked by the "Cancel" button. +- (IBAction)cancel:(id)sender; +// Opens the Privacy Policy url in the default web browser. +- (IBAction)showPrivacyPolicy:(id)sender; + +// Delegate methods for the NSTextField for comments. We want to capture the +// Return key and use it to send the message when no text has been entered. +// Otherwise, we want Return to add a carriage return to the comments field. +- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView + doCommandBySelector:(SEL)commandSelector; + +// Accessors to make bindings work +- (NSString *)commentsValue; +- (void)setCommentsValue:(NSString *)value; + +- (NSString *)emailValue; +- (void)setEmailValue:(NSString *)value; + +- (NSString *)countdownMessage; +- (void)setCountdownMessage:(NSString *)value; + +@end diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/crash_report_sender.icns b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/crash_report_sender.icns Binary files differnew file mode 100644 index 000000000..e8c21242b --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/crash_report_sender.icns diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/crash_report_sender.m b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/crash_report_sender.m new file mode 100644 index 000000000..88d26fb03 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/crash_report_sender.m @@ -0,0 +1,755 @@ +// Copyright (c) 2006, 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 "client/mac/sender/crash_report_sender.h" + +#import <Cocoa/Cocoa.h> +#import <pwd.h> +#import <sys/stat.h> +#import <SystemConfiguration/SystemConfiguration.h> +#import <unistd.h> + +#import "client/apple/Framework/BreakpadDefines.h" +#import "common/mac/GTMLogger.h" +#import "common/mac/HTTPMultipartUpload.h" + + +#define kLastSubmission @"LastSubmission" +const int kUserCommentsMaxLength = 1500; +const int kEmailMaxLength = 64; + +#define kApplePrefsSyncExcludeAllKey \ + @"com.apple.PreferenceSync.ExcludeAllSyncKeys" + +#pragma mark - + +@interface NSView (ResizabilityExtentions) +// Shifts the view vertically by the given amount. +- (void)breakpad_shiftVertically:(CGFloat)offset; + +// Shifts the view horizontally by the given amount. +- (void)breakpad_shiftHorizontally:(CGFloat)offset; +@end + +@implementation NSView (ResizabilityExtentions) +- (void)breakpad_shiftVertically:(CGFloat)offset { + NSPoint origin = [self frame].origin; + origin.y += offset; + [self setFrameOrigin:origin]; +} + +- (void)breakpad_shiftHorizontally:(CGFloat)offset { + NSPoint origin = [self frame].origin; + origin.x += offset; + [self setFrameOrigin:origin]; +} +@end + +@interface NSWindow (ResizabilityExtentions) +// Adjusts the window height by heightDelta relative to its current height, +// keeping all the content at the same size. +- (void)breakpad_adjustHeight:(CGFloat)heightDelta; +@end + +@implementation NSWindow (ResizabilityExtentions) +- (void)breakpad_adjustHeight:(CGFloat)heightDelta { + [[self contentView] setAutoresizesSubviews:NO]; + + NSRect windowFrame = [self frame]; + windowFrame.size.height += heightDelta; + [self setFrame:windowFrame display:YES]; + // For some reason the content view is resizing, but not adjusting its origin, + // so correct it manually. + [[self contentView] setFrameOrigin:NSMakePoint(0, 0)]; + + [[self contentView] setAutoresizesSubviews:YES]; +} +@end + +@interface NSTextField (ResizabilityExtentions) +// Grows or shrinks the height of the field to the minimum required to show the +// current text, preserving the existing width and origin. +// Returns the change in height. +- (CGFloat)breakpad_adjustHeightToFit; + +// Grows or shrinks the width of the field to the minimum required to show the +// current text, preserving the existing height and origin. +// Returns the change in width. +- (CGFloat)breakpad_adjustWidthToFit; +@end + +@implementation NSTextField (ResizabilityExtentions) +- (CGFloat)breakpad_adjustHeightToFit { + NSRect oldFrame = [self frame]; + // Starting with the 10.5 SDK, height won't grow, so make it huge to start. + NSRect presizeFrame = oldFrame; + presizeFrame.size.height = MAXFLOAT; + // sizeToFit will blow out the width rather than making the field taller, so + // we do it manually. + NSSize newSize = [[self cell] cellSizeForBounds:presizeFrame]; + NSRect newFrame = NSMakeRect(oldFrame.origin.x, oldFrame.origin.y, + NSWidth(oldFrame), newSize.height); + [self setFrame:newFrame]; + + return newSize.height - NSHeight(oldFrame); +} + +- (CGFloat)breakpad_adjustWidthToFit { + NSRect oldFrame = [self frame]; + [self sizeToFit]; + return NSWidth([self frame]) - NSWidth(oldFrame); +} +@end + +@interface NSButton (ResizabilityExtentions) +// Resizes to fit the label using IB-style size-to-fit metrics and enforcing a +// minimum width of 70, while preserving the right edge location. +// Returns the change in width. +- (CGFloat)breakpad_smartSizeToFit; +@end + +@implementation NSButton (ResizabilityExtentions) +- (CGFloat)breakpad_smartSizeToFit { + NSRect oldFrame = [self frame]; + [self sizeToFit]; + NSRect newFrame = [self frame]; + // sizeToFit gives much worse results that IB's Size to Fit option. This is + // the amount of padding IB adds over a sizeToFit, empirically determined. + const float kExtraPaddingAmount = 12; + const float kMinButtonWidth = 70; // The default button size in IB. + newFrame.size.width = NSWidth(newFrame) + kExtraPaddingAmount; + if (NSWidth(newFrame) < kMinButtonWidth) + newFrame.size.width = kMinButtonWidth; + // Preserve the right edge location. + newFrame.origin.x = NSMaxX(oldFrame) - NSWidth(newFrame); + [self setFrame:newFrame]; + return NSWidth(newFrame) - NSWidth(oldFrame); +} +@end + +#pragma mark - + +@interface Reporter(PrivateMethods) +- (id)initWithConfigFile:(const char *)configFile; + +// Returns YES if it has been long enough since the last report that we should +// submit a report for this crash. +- (BOOL)reportIntervalElapsed; + +// Returns YES if we should send the report without asking the user first. +- (BOOL)shouldSubmitSilently; + +// Returns YES if the minidump was generated on demand. +- (BOOL)isOnDemand; + +// Returns YES if we should ask the user to provide comments. +- (BOOL)shouldRequestComments; + +// Returns YES if we should ask the user to provide an email address. +- (BOOL)shouldRequestEmail; + +// Shows UI to the user to ask for permission to send and any extra information +// we've been instructed to request. Returns YES if the user allows the report +// to be sent. +- (BOOL)askUserPermissionToSend; + +// Returns the short description of the crash, suitable for use as a dialog +// title (e.g., "The application Foo has quit unexpectedly"). +- (NSString*)shortDialogMessage; + +// Return explanatory text about the crash and the reporter, suitable for the +// body text of a dialog. +- (NSString*)explanatoryDialogText; + +// Returns the amount of time the UI should be shown before timing out. +- (NSTimeInterval)messageTimeout; + +// Preps the comment-prompting alert window for display: +// * localizes all the elements +// * resizes and adjusts layout as necessary for localization +// * removes the email section if includeEmail is NO +- (void)configureAlertWindowIncludingEmail:(BOOL)includeEmail; + +// Rmevoes the email section of the dialog, adjusting the rest of the window +// as necessary. +- (void)removeEmailPrompt; + +// Run an alert window with the given timeout. Returns +// NSRunStoppedResponse if the timeout is exceeded. A timeout of 0 +// queues the message immediately in the modal run loop. +- (NSInteger)runModalWindow:(NSWindow*)window + withTimeout:(NSTimeInterval)timeout; + +// This method is used to periodically update the UI with how many +// seconds are left in the dialog display. +- (void)updateSecondsLeftInDialogDisplay:(NSTimer*)theTimer; + +// When we receive this notification, it means that the user has +// begun editing the email address or comments field, and we disable +// the timers so that the user has as long as they want to type +// in their comments/email. +- (void)controlTextDidBeginEditing:(NSNotification *)aNotification; + +- (void)report; + +@end + +@implementation Reporter +//============================================================================= +- (id)initWithConfigFile:(const char *)configFile { + if ((self = [super init])) { + remainingDialogTime_ = 0; + uploader_ = [[Uploader alloc] initWithConfigFile:configFile]; + if (!uploader_) { + [self release]; + return nil; + } + } + return self; +} + +//============================================================================= +- (BOOL)askUserPermissionToSend { + // Initialize Cocoa, needed to display the alert + NSApplicationLoad(); + + // Get the timeout value for the notification. + NSTimeInterval timeout = [self messageTimeout]; + + NSInteger buttonPressed = NSAlertAlternateReturn; + // Determine whether we should create a text box for user feedback. + if ([self shouldRequestComments]) { + BOOL didLoadNib = [NSBundle loadNibNamed:@"Breakpad" owner:self]; + if (!didLoadNib) { + return NO; + } + + [self configureAlertWindowIncludingEmail:[self shouldRequestEmail]]; + + buttonPressed = [self runModalWindow:alertWindow_ withTimeout:timeout]; + + // Extract info from the user into the uploader_. + if ([self commentsValue]) { + [[uploader_ parameters] setObject:[self commentsValue] + forKey:@BREAKPAD_COMMENTS]; + } + if ([self emailValue]) { + [[uploader_ parameters] setObject:[self emailValue] + forKey:@BREAKPAD_EMAIL]; + } + } else { + // Create an alert panel to tell the user something happened + NSPanel* alert = + NSGetAlertPanel([self shortDialogMessage], + @"%@", + NSLocalizedString(@"sendReportButton", @""), + NSLocalizedString(@"cancelButton", @""), + nil, + [self explanatoryDialogText]); + + // Pop the alert with an automatic timeout, and wait for the response + buttonPressed = [self runModalWindow:alert withTimeout:timeout]; + + // Release the panel memory + NSReleaseAlertPanel(alert); + } + return buttonPressed == NSAlertDefaultReturn; +} + +- (void)configureAlertWindowIncludingEmail:(BOOL)includeEmail { + // Swap in localized values, making size adjustments to impacted elements as + // we go. Remember that the origin is in the bottom left, so elements above + // "fall" as text areas are shrunk from their overly-large IB sizes. + + // Localize the header. No resizing needed, as it has plenty of room. + [dialogTitle_ setStringValue:[self shortDialogMessage]]; + + // Localize the explanatory text field. + [commentMessage_ setStringValue:[NSString stringWithFormat:@"%@\n\n%@", + [self explanatoryDialogText], + NSLocalizedString(@"commentsMsg", @"")]]; + CGFloat commentHeightDelta = [commentMessage_ breakpad_adjustHeightToFit]; + [headerBox_ breakpad_shiftVertically:commentHeightDelta]; + [alertWindow_ breakpad_adjustHeight:commentHeightDelta]; + + // Either localize the email explanation field or remove the whole email + // section depending on whether or not we are asking for email. + if (includeEmail) { + [emailMessage_ setStringValue:NSLocalizedString(@"emailMsg", @"")]; + CGFloat emailHeightDelta = [emailMessage_ breakpad_adjustHeightToFit]; + [preEmailBox_ breakpad_shiftVertically:emailHeightDelta]; + [alertWindow_ breakpad_adjustHeight:emailHeightDelta]; + } else { + [self removeEmailPrompt]; // Handles necessary resizing. + } + + // Localize the email label, and shift the associated text field. + [emailLabel_ setStringValue:NSLocalizedString(@"emailLabel", @"")]; + CGFloat emailLabelWidthDelta = [emailLabel_ breakpad_adjustWidthToFit]; + [emailEntryField_ breakpad_shiftHorizontally:emailLabelWidthDelta]; + + // Localize the privacy policy label, and keep it right-aligned to the arrow. + [privacyLinkLabel_ setStringValue:NSLocalizedString(@"privacyLabel", @"")]; + CGFloat privacyLabelWidthDelta = + [privacyLinkLabel_ breakpad_adjustWidthToFit]; + [privacyLinkLabel_ breakpad_shiftHorizontally:(-privacyLabelWidthDelta)]; + + // Ensure that the email field and the privacy policy link don't overlap. + CGFloat kMinControlPadding = 8; + CGFloat maxEmailFieldWidth = NSMinX([privacyLinkLabel_ frame]) - + NSMinX([emailEntryField_ frame]) - + kMinControlPadding; + if (NSWidth([emailEntryField_ bounds]) > maxEmailFieldWidth && + maxEmailFieldWidth > 0) { + NSSize emailSize = [emailEntryField_ frame].size; + emailSize.width = maxEmailFieldWidth; + [emailEntryField_ setFrameSize:emailSize]; + } + + // Localize the placeholder text. + [[commentsEntryField_ cell] + setPlaceholderString:NSLocalizedString(@"commentsPlaceholder", @"")]; + [[emailEntryField_ cell] + setPlaceholderString:NSLocalizedString(@"emailPlaceholder", @"")]; + + // Localize the buttons, and keep the cancel button at the right distance. + [sendButton_ setTitle:NSLocalizedString(@"sendReportButton", @"")]; + CGFloat sendButtonWidthDelta = [sendButton_ breakpad_smartSizeToFit]; + [cancelButton_ breakpad_shiftHorizontally:(-sendButtonWidthDelta)]; + [cancelButton_ setTitle:NSLocalizedString(@"cancelButton", @"")]; + [cancelButton_ breakpad_smartSizeToFit]; +} + +- (void)removeEmailPrompt { + [emailSectionBox_ setHidden:YES]; + CGFloat emailSectionHeight = NSHeight([emailSectionBox_ frame]); + [preEmailBox_ breakpad_shiftVertically:(-emailSectionHeight)]; + [alertWindow_ breakpad_adjustHeight:(-emailSectionHeight)]; +} + +- (NSInteger)runModalWindow:(NSWindow*)window + withTimeout:(NSTimeInterval)timeout { + // Queue a |stopModal| message to be performed in |timeout| seconds. + if (timeout > 0.001) { + remainingDialogTime_ = timeout; + SEL updateSelector = @selector(updateSecondsLeftInDialogDisplay:); + messageTimer_ = [NSTimer scheduledTimerWithTimeInterval:1.0 + target:self + selector:updateSelector + userInfo:nil + repeats:YES]; + } + + // Run the window modally and wait for either a |stopModal| message or a + // button click. + [NSApp activateIgnoringOtherApps:YES]; + NSInteger returnMethod = [NSApp runModalForWindow:window]; + + return returnMethod; +} + +- (IBAction)sendReport:(id)sender { + // Force the text fields to end editing so text for the currently focused + // field will be commited. + [alertWindow_ makeFirstResponder:alertWindow_]; + + [alertWindow_ orderOut:self]; + // Use NSAlertDefaultReturn so that the return value of |runModalWithWindow| + // matches the AppKit function NSRunAlertPanel() + [NSApp stopModalWithCode:NSAlertDefaultReturn]; +} + +// UI Button Actions +//============================================================================= +- (IBAction)cancel:(id)sender { + [alertWindow_ orderOut:self]; + // Use NSAlertDefaultReturn so that the return value of |runModalWithWindow| + // matches the AppKit function NSRunAlertPanel() + [NSApp stopModalWithCode:NSAlertAlternateReturn]; +} + +- (IBAction)showPrivacyPolicy:(id)sender { + // Get the localized privacy policy URL and open it in the default browser. + NSURL* privacyPolicyURL = + [NSURL URLWithString:NSLocalizedString(@"privacyPolicyURL", @"")]; + [[NSWorkspace sharedWorkspace] openURL:privacyPolicyURL]; +} + +// Text Field Delegate Methods +//============================================================================= +- (BOOL) control:(NSControl*)control + textView:(NSTextView*)textView +doCommandBySelector:(SEL)commandSelector { + BOOL result = NO; + // If the user has entered text on the comment field, don't end + // editing on "return". + if (control == commentsEntryField_ && + commandSelector == @selector(insertNewline:) + && [[textView string] length] > 0) { + [textView insertNewlineIgnoringFieldEditor:self]; + result = YES; + } + return result; +} + +- (void)controlTextDidBeginEditing:(NSNotification *)aNotification { + [messageTimer_ invalidate]; + [self setCountdownMessage:@""]; +} + +- (void)updateSecondsLeftInDialogDisplay:(NSTimer*)theTimer { + remainingDialogTime_ -= 1; + + NSString *countdownMessage; + NSString *formatString; + + int displayedTimeLeft; // This can be either minutes or seconds. + + if (remainingDialogTime_ > 59) { + // calculate minutes remaining for UI purposes + displayedTimeLeft = (int)(remainingDialogTime_ / 60); + + if (displayedTimeLeft == 1) { + formatString = NSLocalizedString(@"countdownMsgMinuteSingular", @""); + } else { + formatString = NSLocalizedString(@"countdownMsgMinutesPlural", @""); + } + } else { + displayedTimeLeft = (int)remainingDialogTime_; + if (displayedTimeLeft == 1) { + formatString = NSLocalizedString(@"countdownMsgSecondSingular", @""); + } else { + formatString = NSLocalizedString(@"countdownMsgSecondsPlural", @""); + } + } + countdownMessage = [NSString stringWithFormat:formatString, + displayedTimeLeft]; + if (remainingDialogTime_ <= 30) { + [countdownLabel_ setTextColor:[NSColor redColor]]; + } + [self setCountdownMessage:countdownMessage]; + if (remainingDialogTime_ <= 0) { + [messageTimer_ invalidate]; + [NSApp stopModal]; + } +} + + + +#pragma mark Accessors +#pragma mark - +//============================================================================= + +- (NSString *)commentsValue { + return [[commentsValue_ retain] autorelease]; +} + +- (void)setCommentsValue:(NSString *)value { + if (commentsValue_ != value) { + [commentsValue_ release]; + commentsValue_ = [value copy]; + } +} + +- (NSString *)emailValue { + return [[emailValue_ retain] autorelease]; +} + +- (void)setEmailValue:(NSString *)value { + if (emailValue_ != value) { + [emailValue_ release]; + emailValue_ = [value copy]; + } +} + +- (NSString *)countdownMessage { + return [[countdownMessage_ retain] autorelease]; +} + +- (void)setCountdownMessage:(NSString *)value { + if (countdownMessage_ != value) { + [countdownMessage_ release]; + countdownMessage_ = [value copy]; + } +} + +#pragma mark - +//============================================================================= +- (BOOL)reportIntervalElapsed { + float interval = [[[uploader_ parameters] + objectForKey:@BREAKPAD_REPORT_INTERVAL] floatValue]; + NSString *program = [[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT]; + NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; + NSMutableDictionary *programDict = + [NSMutableDictionary dictionaryWithDictionary:[ud dictionaryForKey:program]]; + NSNumber *lastTimeNum = [programDict objectForKey:kLastSubmission]; + NSTimeInterval lastTime = lastTimeNum ? [lastTimeNum floatValue] : 0; + NSTimeInterval now = CFAbsoluteTimeGetCurrent(); + NSTimeInterval spanSeconds = (now - lastTime); + + [programDict setObject:[NSNumber numberWithDouble:now] + forKey:kLastSubmission]; + [ud setObject:programDict forKey:program]; + [ud synchronize]; + + // If we've specified an interval and we're within that time, don't ask the + // user if we should report + GTMLoggerDebug(@"Reporter Interval: %f", interval); + if (interval > spanSeconds) { + GTMLoggerDebug(@"Within throttling interval, not sending report"); + return NO; + } + return YES; +} + +- (BOOL)isOnDemand { + return [[[uploader_ parameters] objectForKey:@BREAKPAD_ON_DEMAND] + isEqualToString:@"YES"]; +} + +- (BOOL)shouldSubmitSilently { + return [[[uploader_ parameters] objectForKey:@BREAKPAD_SKIP_CONFIRM] + isEqualToString:@"YES"]; +} + +- (BOOL)shouldRequestComments { + return [[[uploader_ parameters] objectForKey:@BREAKPAD_REQUEST_COMMENTS] + isEqualToString:@"YES"]; +} + +- (BOOL)shouldRequestEmail { + return [[[uploader_ parameters] objectForKey:@BREAKPAD_REQUEST_EMAIL] + isEqualToString:@"YES"]; +} + +- (NSString*)shortDialogMessage { + NSString *displayName = + [[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT_DISPLAY]; + if (![displayName length]) + displayName = [[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT]; + + if ([self isOnDemand]) { + // Local variable to pacify clang's -Wformat-extra-args. + NSString* format = NSLocalizedString(@"noCrashDialogHeader", @""); + return [NSString stringWithFormat:format, displayName]; + } else { + // Local variable to pacify clang's -Wformat-extra-args. + NSString* format = NSLocalizedString(@"crashDialogHeader", @""); + return [NSString stringWithFormat:format, displayName]; + } +} + +- (NSString*)explanatoryDialogText { + NSString *displayName = + [[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT_DISPLAY]; + if (![displayName length]) + displayName = [[uploader_ parameters] objectForKey:@BREAKPAD_PRODUCT]; + + NSString *vendor = [[uploader_ parameters] objectForKey:@BREAKPAD_VENDOR]; + if (![vendor length]) + vendor = @"unknown vendor"; + + if ([self isOnDemand]) { + // Local variable to pacify clang's -Wformat-extra-args. + NSString* format = NSLocalizedString(@"noCrashDialogMsg", @""); + return [NSString stringWithFormat:format, vendor, displayName]; + } else { + // Local variable to pacify clang's -Wformat-extra-args. + NSString* format = NSLocalizedString(@"crashDialogMsg", @""); + return [NSString stringWithFormat:format, vendor]; + } +} + +- (NSTimeInterval)messageTimeout { + // Get the timeout value for the notification. + NSTimeInterval timeout = [[[uploader_ parameters] + objectForKey:@BREAKPAD_CONFIRM_TIMEOUT] floatValue]; + // Require a timeout of at least a minute (except 0, which means no timeout). + if (timeout > 0.001 && timeout < 60.0) { + timeout = 60.0; + } + return timeout; +} + +- (void)report { + [uploader_ report]; +} + +//============================================================================= +- (void)dealloc { + [uploader_ release]; + [super dealloc]; +} + +- (void)awakeFromNib { + [emailEntryField_ setMaximumLength:kEmailMaxLength]; + [commentsEntryField_ setMaximumLength:kUserCommentsMaxLength]; +} + +@end + +//============================================================================= +@implementation LengthLimitingTextField + +- (void)setMaximumLength:(NSUInteger)maxLength { + maximumLength_ = maxLength; +} + +// This is the method we're overriding in NSTextField, which lets us +// limit the user's input if it makes the string too long. +- (BOOL) textView:(NSTextView *)textView +shouldChangeTextInRange:(NSRange)affectedCharRange + replacementString:(NSString *)replacementString { + + // Sometimes the range comes in invalid, so reject if we can't + // figure out if the replacement text is too long. + if (affectedCharRange.location == NSNotFound) { + return NO; + } + // Figure out what the new string length would be, taking into + // account user selections. + NSUInteger newStringLength = + [[textView string] length] - affectedCharRange.length + + [replacementString length]; + if (newStringLength > maximumLength_) { + return NO; + } else { + return YES; + } +} + +// Cut, copy, and paste have to be caught specifically since there is no menu. +- (BOOL)performKeyEquivalent:(NSEvent*)event { + // Only handle the key equivalent if |self| is the text field with focus. + NSText* fieldEditor = [self currentEditor]; + if (fieldEditor != nil) { + // Check for a single "Command" modifier + NSUInteger modifiers = [event modifierFlags]; + modifiers &= NSDeviceIndependentModifierFlagsMask; + if (modifiers == NSCommandKeyMask) { + // Now, check for Select All, Cut, Copy, or Paste key equivalents. + NSString* characters = [event characters]; + // Select All is Command-A. + if ([characters isEqualToString:@"a"]) { + [fieldEditor selectAll:self]; + return YES; + // Cut is Command-X. + } else if ([characters isEqualToString:@"x"]) { + [fieldEditor cut:self]; + return YES; + // Copy is Command-C. + } else if ([characters isEqualToString:@"c"]) { + [fieldEditor copy:self]; + return YES; + // Paste is Command-V. + } else if ([characters isEqualToString:@"v"]) { + [fieldEditor paste:self]; + return YES; + } + } + } + // Let the super class handle the rest (e.g. Command-Period will cancel). + return [super performKeyEquivalent:event]; +} + +@end + +//============================================================================= +int main(int argc, const char *argv[]) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; +#if DEBUG + // Log to stderr in debug builds. + [GTMLogger setSharedLogger:[GTMLogger standardLoggerWithStderr]]; +#endif + GTMLoggerDebug(@"Reporter Launched, argc=%d", argc); + // The expectation is that there will be one argument which is the path + // to the configuration file + if (argc != 2) { + exit(1); + } + + Reporter *reporter = [[Reporter alloc] initWithConfigFile:argv[1]]; + if (!reporter) { + GTMLoggerDebug(@"reporter initialization failed"); + exit(1); + } + + // only submit a report if we have not recently crashed in the past + BOOL shouldSubmitReport = [reporter reportIntervalElapsed]; + BOOL okayToSend = NO; + + // ask user if we should send + if (shouldSubmitReport) { + if ([reporter shouldSubmitSilently]) { + GTMLoggerDebug(@"Skipping confirmation and sending report"); + okayToSend = YES; + } else { + okayToSend = [reporter askUserPermissionToSend]; + } + } + + // If we're running as root, switch over to nobody + if (getuid() == 0 || geteuid() == 0) { + struct passwd *pw = getpwnam("nobody"); + + // If we can't get a non-root uid, don't send the report + if (!pw) { + GTMLoggerDebug(@"!pw - %s", strerror(errno)); + exit(0); + } + + if (setgid(pw->pw_gid) == -1) { + GTMLoggerDebug(@"setgid(pw->pw_gid) == -1 - %s", strerror(errno)); + exit(0); + } + + if (setuid(pw->pw_uid) == -1) { + GTMLoggerDebug(@"setuid(pw->pw_uid) == -1 - %s", strerror(errno)); + exit(0); + } + } + else { + GTMLoggerDebug(@"getuid() !=0 || geteuid() != 0"); + } + + if (okayToSend && shouldSubmitReport) { + GTMLoggerDebug(@"Sending Report"); + [reporter report]; + GTMLoggerDebug(@"Report Sent!"); + } else { + GTMLoggerDebug(@"Not sending crash report okayToSend=%d, "\ + "shouldSubmitReport=%d", okayToSend, shouldSubmitReport); + } + + GTMLoggerDebug(@"Exiting with no errors"); + // Cleanup + [reporter release]; + [pool release]; + return 0; +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/da.lproj/InfoPlist.strings b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/da.lproj/InfoPlist.strings Binary files differnew file mode 100644 index 000000000..4cfd32c54 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/da.lproj/InfoPlist.strings diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/da.lproj/Localizable.strings b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/da.lproj/Localizable.strings Binary files differnew file mode 100644 index 000000000..2b8bb9693 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/da.lproj/Localizable.strings diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/de.lproj/InfoPlist.strings b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/de.lproj/InfoPlist.strings Binary files differnew file mode 100644 index 000000000..3180973ea --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/de.lproj/InfoPlist.strings diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/de.lproj/Localizable.strings b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/de.lproj/Localizable.strings Binary files differnew file mode 100644 index 000000000..73da36f48 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/de.lproj/Localizable.strings diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/es.lproj/InfoPlist.strings b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/es.lproj/InfoPlist.strings Binary files differnew file mode 100644 index 000000000..a82c013e0 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/es.lproj/InfoPlist.strings diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/es.lproj/Localizable.strings b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/es.lproj/Localizable.strings Binary files differnew file mode 100644 index 000000000..c31d6f48b --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/es.lproj/Localizable.strings diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/fr.lproj/InfoPlist.strings b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/fr.lproj/InfoPlist.strings Binary files differnew file mode 100644 index 000000000..4cfd32c54 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/fr.lproj/InfoPlist.strings diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/fr.lproj/Localizable.strings b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/fr.lproj/Localizable.strings Binary files differnew file mode 100644 index 000000000..c32f8ff7a --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/fr.lproj/Localizable.strings diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/goArrow.png b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/goArrow.png Binary files differnew file mode 100644 index 000000000..f318a5671 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/goArrow.png diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/it.lproj/InfoPlist.strings b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/it.lproj/InfoPlist.strings Binary files differnew file mode 100644 index 000000000..4cfd32c54 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/it.lproj/InfoPlist.strings diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/it.lproj/Localizable.strings b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/it.lproj/Localizable.strings Binary files differnew file mode 100644 index 000000000..a4b7fd3ac --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/it.lproj/Localizable.strings diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/ja.lproj/InfoPlist.strings b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/ja.lproj/InfoPlist.strings Binary files differnew file mode 100644 index 000000000..4cfd32c54 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/ja.lproj/InfoPlist.strings diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/ja.lproj/Localizable.strings b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/ja.lproj/Localizable.strings Binary files differnew file mode 100644 index 000000000..1f1a4fee5 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/ja.lproj/Localizable.strings diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/nl.lproj/InfoPlist.strings b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/nl.lproj/InfoPlist.strings Binary files differnew file mode 100644 index 000000000..4cfd32c54 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/nl.lproj/InfoPlist.strings diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/nl.lproj/Localizable.strings b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/nl.lproj/Localizable.strings Binary files differnew file mode 100644 index 000000000..a4a54f759 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/nl.lproj/Localizable.strings diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/no.lproj/InfoPlist.strings b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/no.lproj/InfoPlist.strings Binary files differnew file mode 100644 index 000000000..4cfd32c54 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/no.lproj/InfoPlist.strings diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/no.lproj/Localizable.strings b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/no.lproj/Localizable.strings Binary files differnew file mode 100644 index 000000000..725ef5a26 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/no.lproj/Localizable.strings diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/sl.lproj/InfoPlist.strings b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/sl.lproj/InfoPlist.strings Binary files differnew file mode 100644 index 000000000..585f6221d --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/sl.lproj/InfoPlist.strings diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/sl.lproj/Localizable.strings b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/sl.lproj/Localizable.strings Binary files differnew file mode 100644 index 000000000..b7dfeeceb --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/sl.lproj/Localizable.strings diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/sv.lproj/InfoPlist.strings b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/sv.lproj/InfoPlist.strings Binary files differnew file mode 100644 index 000000000..4cfd32c54 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/sv.lproj/InfoPlist.strings diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/sv.lproj/Localizable.strings b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/sv.lproj/Localizable.strings Binary files differnew file mode 100644 index 000000000..f7d125166 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/sv.lproj/Localizable.strings diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/tr.lproj/InfoPlist.strings b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/tr.lproj/InfoPlist.strings Binary files differnew file mode 100644 index 000000000..088fb4f6c --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/tr.lproj/InfoPlist.strings diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/tr.lproj/Localizable.strings b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/tr.lproj/Localizable.strings Binary files differnew file mode 100644 index 000000000..a82f990ed --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/tr.lproj/Localizable.strings diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/uploader.h b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/uploader.h new file mode 100644 index 000000000..5f6aa464d --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/uploader.h @@ -0,0 +1,89 @@ +// 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. +// +// This component uses the HTTPMultipartUpload of the breakpad project to send +// the minidump and associated data to the crash reporting servers. +// It will perform throttling based on the parameters passed to it and will +// prompt the user to send the minidump. + +#include <Foundation/Foundation.h> + +#import "common/mac/GTMDefines.h" + +#define kClientIdPreferenceKey @"clientid" + +extern NSString *const kGoogleServerType; +extern NSString *const kSocorroServerType; +extern NSString *const kDefaultServerType; + +@interface Uploader : NSObject { + @private + NSMutableDictionary *parameters_; // Key value pairs of data (STRONG) + NSData *minidumpContents_; // The data in the minidump (STRONG) + NSData *logFileData_; // An NSdata for the tar, + // bz2'd log file. + NSMutableDictionary *serverDictionary_; // The dictionary mapping a + // server type name to a + // dictionary of server + // parameter names. + NSMutableDictionary *socorroDictionary_; // The dictionary for + // Socorro. + NSMutableDictionary *googleDictionary_; // The dictionary for + // Google. + NSMutableDictionary *extraServerVars_; // A dictionary containing + // extra key/value pairs + // that are uploaded to the + // crash server with the + // minidump. +} + +- (id)initWithConfigFile:(const char *)configFile; + +- (id)initWithConfig:(NSDictionary *)config; + +// Reads the file |configFile| and returns the corresponding NSDictionary. +// |configFile| will be deleted after reading. ++ (NSDictionary *)readConfigurationDataFromFile:(NSString *)configFile; + +- (NSMutableDictionary *)parameters; + +- (void)report; + +// Upload the given data to the crash server. +- (void)uploadData:(NSData *)data name:(NSString *)name; + +// This method adds a key/value pair to the dictionary that +// will be uploaded to the crash server. +- (void)addServerParameter:(id)value forKey:(NSString *)key; + +// This method process the HTTP response and renames the minidump file with the +// new ID. +- (void)handleNetworkResponse:(NSData *)data withError:(NSError *)error; + +@end diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/sender/uploader.mm b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/uploader.mm new file mode 100644 index 000000000..42a43bfc3 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/sender/uploader.mm @@ -0,0 +1,636 @@ +// 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. + +#import <fcntl.h> +#import <sys/stat.h> +#include <TargetConditionals.h> +#import <unistd.h> + +#import <SystemConfiguration/SystemConfiguration.h> + +#import "common/mac/HTTPMultipartUpload.h" + +#import "client/apple/Framework/BreakpadDefines.h" +#import "client/mac/sender/uploader.h" +#import "common/mac/GTMLogger.h" + +const int kMinidumpFileLengthLimit = 2 * 1024 * 1024; // 2MB + +#define kApplePrefsSyncExcludeAllKey \ + @"com.apple.PreferenceSync.ExcludeAllSyncKeys" + +NSString *const kGoogleServerType = @"google"; +NSString *const kSocorroServerType = @"socorro"; +NSString *const kDefaultServerType = @"google"; + +#pragma mark - + +namespace { +// Read one line from the configuration file. +NSString *readString(int fileId) { + NSMutableString *str = [NSMutableString stringWithCapacity:32]; + char ch[2] = { 0 }; + + while (read(fileId, &ch[0], 1) == 1) { + if (ch[0] == '\n') { + // Break if this is the first newline after reading some other string + // data. + if ([str length]) + break; + } else { + [str appendString:[NSString stringWithUTF8String:ch]]; + } + } + + return str; +} + +//============================================================================= +// Read |length| of binary data from the configuration file. This method will +// returns |nil| in case of error. +NSData *readData(int fileId, ssize_t length) { + NSMutableData *data = [NSMutableData dataWithLength:length]; + char *bytes = (char *)[data bytes]; + + if (read(fileId, bytes, length) != length) + return nil; + + return data; +} + +//============================================================================= +// Read the configuration from the config file. +NSDictionary *readConfigurationData(const char *configFile) { + int fileId = open(configFile, O_RDONLY, 0600); + if (fileId == -1) { + GTMLoggerDebug(@"Couldn't open config file %s - %s", + configFile, + strerror(errno)); + } + + // we want to avoid a build-up of old config files even if they + // have been incorrectly written by the framework + if (unlink(configFile)) { + GTMLoggerDebug(@"Couldn't unlink config file %s - %s", + configFile, + strerror(errno)); + } + + if (fileId == -1) { + return nil; + } + + NSMutableDictionary *config = [NSMutableDictionary dictionary]; + + while (1) { + NSString *key = readString(fileId); + + if (![key length]) + break; + + // Read the data. Try to convert to a UTF-8 string, or just save + // the data + NSString *lenStr = readString(fileId); + ssize_t len = [lenStr intValue]; + NSData *data = readData(fileId, len); + id value = [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding]; + + [config setObject:(value ? value : data) forKey:key]; + [value release]; + } + + close(fileId); + return config; +} +} // namespace + +#pragma mark - + +@interface Uploader(PrivateMethods) + +// Update |parameters_| as well as the server parameters using |config|. +- (void)translateConfigurationData:(NSDictionary *)config; + +// Read the minidump referenced in |parameters_| and update |minidumpContents_| +// with its content. +- (BOOL)readMinidumpData; + +// Read the log files referenced in |parameters_| and update |logFileData_| +// with their content. +- (BOOL)readLogFileData; + +// Returns a unique client id (user-specific), creating a persistent +// one in the user defaults, if necessary. +- (NSString*)clientID; + +// Returns a dictionary that can be used to map Breakpad parameter names to +// URL parameter names. +- (NSMutableDictionary *)dictionaryForServerType:(NSString *)serverType; + +// Helper method to set HTTP parameters based on server type. This is +// called right before the upload - crashParameters will contain, on exit, +// URL parameters that should be sent with the minidump. +- (BOOL)populateServerDictionary:(NSMutableDictionary *)crashParameters; + +// Initialization helper to create dictionaries mapping Breakpad +// parameters to URL parameters +- (void)createServerParameterDictionaries; + +// Accessor method for the URL parameter dictionary +- (NSMutableDictionary *)urlParameterDictionary; + +// Records the uploaded crash ID to the log file. +- (void)logUploadWithID:(const char *)uploadID; +@end + +@implementation Uploader + +//============================================================================= +- (id)initWithConfigFile:(const char *)configFile { + NSDictionary *config = readConfigurationData(configFile); + if (!config) + return nil; + + return [self initWithConfig:config]; +} + +//============================================================================= +- (id)initWithConfig:(NSDictionary *)config { + if ((self = [super init])) { + // Because the reporter is embedded in the framework (and many copies + // of the framework may exist) its not completely certain that the OS + // will obey the com.apple.PreferenceSync.ExcludeAllSyncKeys in our + // Info.plist. To make sure, also set the key directly if needed. + NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; + if (![ud boolForKey:kApplePrefsSyncExcludeAllKey]) { + [ud setBool:YES forKey:kApplePrefsSyncExcludeAllKey]; + } + + [self createServerParameterDictionaries]; + + [self translateConfigurationData:config]; + + // Read the minidump into memory. + [self readMinidumpData]; + [self readLogFileData]; + } + return self; +} + +//============================================================================= ++ (NSDictionary *)readConfigurationDataFromFile:(NSString *)configFile { + return readConfigurationData([configFile fileSystemRepresentation]); +} + +//============================================================================= +- (void)translateConfigurationData:(NSDictionary *)config { + parameters_ = [[NSMutableDictionary alloc] init]; + + NSEnumerator *it = [config keyEnumerator]; + while (NSString *key = [it nextObject]) { + // If the keyname is prefixed by BREAKPAD_SERVER_PARAMETER_PREFIX + // that indicates that it should be uploaded to the server along + // with the minidump, so we treat it specially. + if ([key hasPrefix:@BREAKPAD_SERVER_PARAMETER_PREFIX]) { + NSString *urlParameterKey = + [key substringFromIndex:[@BREAKPAD_SERVER_PARAMETER_PREFIX length]]; + if ([urlParameterKey length]) { + id value = [config objectForKey:key]; + if ([value isKindOfClass:[NSString class]]) { + [self addServerParameter:(NSString *)value + forKey:urlParameterKey]; + } else { + [self addServerParameter:(NSData *)value + forKey:urlParameterKey]; + } + } + } else { + [parameters_ setObject:[config objectForKey:key] forKey:key]; + } + } + + // generate a unique client ID based on this host's MAC address + // then add a key/value pair for it + NSString *clientID = [self clientID]; + [parameters_ setObject:clientID forKey:@"guid"]; +} + +// Per user per machine +- (NSString *)clientID { + NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; + NSString *crashClientID = [ud stringForKey:kClientIdPreferenceKey]; + if (crashClientID) { + return crashClientID; + } + + // Otherwise, if we have no client id, generate one! + srandom((int)[[NSDate date] timeIntervalSince1970]); + long clientId1 = random(); + long clientId2 = random(); + long clientId3 = random(); + crashClientID = [NSString stringWithFormat:@"%lx%lx%lx", + clientId1, clientId2, clientId3]; + + [ud setObject:crashClientID forKey:kClientIdPreferenceKey]; + [ud synchronize]; + return crashClientID; +} + +//============================================================================= +- (BOOL)readLogFileData { +#if TARGET_OS_IPHONE + return NO; +#else + unsigned int logFileCounter = 0; + + NSString *logPath; + size_t logFileTailSize = + [[parameters_ objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE] intValue]; + + NSMutableArray *logFilenames; // An array of NSString, one per log file + logFilenames = [[NSMutableArray alloc] init]; + + char tmpDirTemplate[80] = "/tmp/CrashUpload-XXXXX"; + char *tmpDir = mkdtemp(tmpDirTemplate); + + // Construct key names for the keys we expect to contain log file paths + for(logFileCounter = 0;; logFileCounter++) { + NSString *logFileKey = [NSString stringWithFormat:@"%@%d", + @BREAKPAD_LOGFILE_KEY_PREFIX, + logFileCounter]; + + logPath = [parameters_ objectForKey:logFileKey]; + + // They should all be consecutive, so if we don't find one, assume + // we're done + + if (!logPath) { + break; + } + + NSData *entireLogFile = [[NSData alloc] initWithContentsOfFile:logPath]; + + if (entireLogFile == nil) { + continue; + } + + NSRange fileRange; + + // Truncate the log file, only if necessary + + if ([entireLogFile length] <= logFileTailSize) { + fileRange = NSMakeRange(0, [entireLogFile length]); + } else { + fileRange = NSMakeRange([entireLogFile length] - logFileTailSize, + logFileTailSize); + } + + char tmpFilenameTemplate[100]; + + // Generate a template based on the log filename + sprintf(tmpFilenameTemplate,"%s/%s-XXXX", tmpDir, + [[logPath lastPathComponent] fileSystemRepresentation]); + + char *tmpFile = mktemp(tmpFilenameTemplate); + + NSData *logSubdata = [entireLogFile subdataWithRange:fileRange]; + NSString *tmpFileString = [NSString stringWithUTF8String:tmpFile]; + [logSubdata writeToFile:tmpFileString atomically:NO]; + + [logFilenames addObject:[tmpFileString lastPathComponent]]; + [entireLogFile release]; + } + + if ([logFilenames count] == 0) { + [logFilenames release]; + logFileData_ = nil; + return NO; + } + + // now, bzip all files into one + NSTask *tarTask = [[NSTask alloc] init]; + + [tarTask setCurrentDirectoryPath:[NSString stringWithUTF8String:tmpDir]]; + [tarTask setLaunchPath:@"/usr/bin/tar"]; + + NSMutableArray *bzipArgs = [NSMutableArray arrayWithObjects:@"-cjvf", + @"log.tar.bz2",nil]; + [bzipArgs addObjectsFromArray:logFilenames]; + + [logFilenames release]; + + [tarTask setArguments:bzipArgs]; + [tarTask launch]; + [tarTask waitUntilExit]; + [tarTask release]; + + NSString *logTarFile = [NSString stringWithFormat:@"%s/log.tar.bz2",tmpDir]; + logFileData_ = [[NSData alloc] initWithContentsOfFile:logTarFile]; + if (logFileData_ == nil) { + GTMLoggerDebug(@"Cannot find temp tar log file: %@", logTarFile); + return NO; + } + return YES; +#endif // TARGET_OS_IPHONE +} + +//============================================================================= +- (BOOL)readMinidumpData { + NSString *minidumpDir = + [parameters_ objectForKey:@kReporterMinidumpDirectoryKey]; + NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey]; + + if (![minidumpID length]) + return NO; + + NSString *path = [minidumpDir stringByAppendingPathComponent:minidumpID]; + path = [path stringByAppendingPathExtension:@"dmp"]; + + // check the size of the minidump and limit it to a reasonable size + // before attempting to load into memory and upload + const char *fileName = [path fileSystemRepresentation]; + struct stat fileStatus; + + BOOL success = YES; + + if (!stat(fileName, &fileStatus)) { + if (fileStatus.st_size > kMinidumpFileLengthLimit) { + fprintf(stderr, "Breakpad Uploader: minidump file too large " \ + "to upload : %d\n", (int)fileStatus.st_size); + success = NO; + } + } else { + fprintf(stderr, "Breakpad Uploader: unable to determine minidump " \ + "file length\n"); + success = NO; + } + + if (success) { + minidumpContents_ = [[NSData alloc] initWithContentsOfFile:path]; + success = ([minidumpContents_ length] ? YES : NO); + } + + if (!success) { + // something wrong with the minidump file -- delete it + unlink(fileName); + } + + return success; +} + +#pragma mark - +//============================================================================= + +- (void)createServerParameterDictionaries { + serverDictionary_ = [[NSMutableDictionary alloc] init]; + socorroDictionary_ = [[NSMutableDictionary alloc] init]; + googleDictionary_ = [[NSMutableDictionary alloc] init]; + extraServerVars_ = [[NSMutableDictionary alloc] init]; + + [serverDictionary_ setObject:socorroDictionary_ forKey:kSocorroServerType]; + [serverDictionary_ setObject:googleDictionary_ forKey:kGoogleServerType]; + + [googleDictionary_ setObject:@"ptime" forKey:@BREAKPAD_PROCESS_UP_TIME]; + [googleDictionary_ setObject:@"email" forKey:@BREAKPAD_EMAIL]; + [googleDictionary_ setObject:@"comments" forKey:@BREAKPAD_COMMENTS]; + [googleDictionary_ setObject:@"prod" forKey:@BREAKPAD_PRODUCT]; + [googleDictionary_ setObject:@"ver" forKey:@BREAKPAD_VERSION]; + [googleDictionary_ setObject:@"guid" forKey:@"guid"]; + + [socorroDictionary_ setObject:@"Comments" forKey:@BREAKPAD_COMMENTS]; + [socorroDictionary_ setObject:@"CrashTime" + forKey:@BREAKPAD_PROCESS_CRASH_TIME]; + [socorroDictionary_ setObject:@"StartupTime" + forKey:@BREAKPAD_PROCESS_START_TIME]; + [socorroDictionary_ setObject:@"Version" + forKey:@BREAKPAD_VERSION]; + [socorroDictionary_ setObject:@"ProductName" + forKey:@BREAKPAD_PRODUCT]; + [socorroDictionary_ setObject:@"Email" + forKey:@BREAKPAD_EMAIL]; +} + +- (NSMutableDictionary *)dictionaryForServerType:(NSString *)serverType { + if (serverType == nil || [serverType length] == 0) { + return [serverDictionary_ objectForKey:kDefaultServerType]; + } + return [serverDictionary_ objectForKey:serverType]; +} + +- (NSMutableDictionary *)urlParameterDictionary { + NSString *serverType = [parameters_ objectForKey:@BREAKPAD_SERVER_TYPE]; + return [self dictionaryForServerType:serverType]; + +} + +- (BOOL)populateServerDictionary:(NSMutableDictionary *)crashParameters { + NSDictionary *urlParameterNames = [self urlParameterDictionary]; + + id key; + NSEnumerator *enumerator = [parameters_ keyEnumerator]; + + while ((key = [enumerator nextObject])) { + // The key from parameters_ corresponds to a key in + // urlParameterNames. The value in parameters_ gets stored in + // crashParameters with a key that is the value in + // urlParameterNames. + + // For instance, if parameters_ has [PRODUCT_NAME => "FOOBAR"] and + // urlParameterNames has [PRODUCT_NAME => "pname"] the final HTTP + // URL parameter becomes [pname => "FOOBAR"]. + NSString *breakpadParameterName = (NSString *)key; + NSString *urlParameter = [urlParameterNames + objectForKey:breakpadParameterName]; + if (urlParameter) { + [crashParameters setObject:[parameters_ objectForKey:key] + forKey:urlParameter]; + } + } + + // Now, add the parameters that were added by the application. + enumerator = [extraServerVars_ keyEnumerator]; + + while ((key = [enumerator nextObject])) { + NSString *urlParameterName = (NSString *)key; + NSString *urlParameterValue = + [extraServerVars_ objectForKey:urlParameterName]; + [crashParameters setObject:urlParameterValue + forKey:urlParameterName]; + } + return YES; +} + +- (void)addServerParameter:(id)value forKey:(NSString *)key { + [extraServerVars_ setObject:value forKey:key]; +} + +//============================================================================= +- (void)handleNetworkResponse:(NSData *)data withError:(NSError *)error { + NSString *result = [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding]; + const char *reportID = "ERR"; + if (error) { + fprintf(stderr, "Breakpad Uploader: Send Error: %s\n", + [[error description] UTF8String]); + } else { + NSCharacterSet *trimSet = + [NSCharacterSet whitespaceAndNewlineCharacterSet]; + reportID = [[result stringByTrimmingCharactersInSet:trimSet] UTF8String]; + [self logUploadWithID:reportID]; + } + + // rename the minidump file according to the id returned from the server + NSString *minidumpDir = + [parameters_ objectForKey:@kReporterMinidumpDirectoryKey]; + NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey]; + + NSString *srcString = [NSString stringWithFormat:@"%@/%@.dmp", + minidumpDir, minidumpID]; + NSString *destString = [NSString stringWithFormat:@"%@/%s.dmp", + minidumpDir, reportID]; + + const char *src = [srcString fileSystemRepresentation]; + const char *dest = [destString fileSystemRepresentation]; + + if (rename(src, dest) == 0) { + GTMLoggerInfo(@"Breakpad Uploader: Renamed %s to %s after successful " \ + "upload",src, dest); + } + else { + // can't rename - don't worry - it's not important for users + GTMLoggerDebug(@"Breakpad Uploader: successful upload report ID = %s\n", + reportID ); + } + [result release]; +} + +//============================================================================= +- (void)report { + NSURL *url = [NSURL URLWithString:[parameters_ objectForKey:@BREAKPAD_URL]]; + HTTPMultipartUpload *upload = [[HTTPMultipartUpload alloc] initWithURL:url]; + NSMutableDictionary *uploadParameters = [NSMutableDictionary dictionary]; + + if (![self populateServerDictionary:uploadParameters]) { + [upload release]; + return; + } + + [upload setParameters:uploadParameters]; + + // Add minidump file + if (minidumpContents_) { + [upload addFileContents:minidumpContents_ name:@"upload_file_minidump"]; + + // If there is a log file, upload it together with the minidump. + if (logFileData_) { + [upload addFileContents:logFileData_ name:@"log"]; + } + + // Send it + NSError *error = nil; + NSData *data = [upload send:&error]; + + if (![url isFileURL]) { + [self handleNetworkResponse:data withError:error]; + } else { + if (error) { + fprintf(stderr, "Breakpad Uploader: Error writing request file: %s\n", + [[error description] UTF8String]); + } + } + + } else { + // Minidump is missing -- upload just the log file. + if (logFileData_) { + [self uploadData:logFileData_ name:@"log"]; + } + } + [upload release]; +} + +- (void)uploadData:(NSData *)data name:(NSString *)name { + NSURL *url = [NSURL URLWithString:[parameters_ objectForKey:@BREAKPAD_URL]]; + NSMutableDictionary *uploadParameters = [NSMutableDictionary dictionary]; + + if (![self populateServerDictionary:uploadParameters]) + return; + + HTTPMultipartUpload *upload = + [[HTTPMultipartUpload alloc] initWithURL:url]; + + [uploadParameters setObject:name forKey:@"type"]; + [upload setParameters:uploadParameters]; + [upload addFileContents:data name:name]; + + [upload send:nil]; + [upload release]; +} + +- (void)logUploadWithID:(const char *)uploadID { + NSString *minidumpDir = + [parameters_ objectForKey:@kReporterMinidumpDirectoryKey]; + NSString *logFilePath = [NSString stringWithFormat:@"%@/%s", + minidumpDir, kReporterLogFilename]; + NSString *logLine = [NSString stringWithFormat:@"%0.f,%s\n", + [[NSDate date] timeIntervalSince1970], uploadID]; + NSData *logData = [logLine dataUsingEncoding:NSUTF8StringEncoding]; + + NSFileManager *fileManager = [NSFileManager defaultManager]; + if ([fileManager fileExistsAtPath:logFilePath]) { + NSFileHandle *logFileHandle = + [NSFileHandle fileHandleForWritingAtPath:logFilePath]; + [logFileHandle seekToEndOfFile]; + [logFileHandle writeData:logData]; + [logFileHandle closeFile]; + } else { + [fileManager createFileAtPath:logFilePath + contents:logData + attributes:nil]; + } +} + +//============================================================================= +- (NSMutableDictionary *)parameters { + return parameters_; +} + +//============================================================================= +- (void)dealloc { + [parameters_ release]; + [minidumpContents_ release]; + [logFileData_ release]; + [googleDictionary_ release]; + [socorroDictionary_ release]; + [serverDictionary_ release]; + [extraServerVars_ release]; + [super dealloc]; +} + +@end diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/Controller.h b/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/Controller.h new file mode 100644 index 000000000..7b3be2d69 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/Controller.h @@ -0,0 +1,65 @@ +// Copyright (c) 2006, 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 <Cocoa/Cocoa.h> + +#import <Breakpad/Breakpad.h> + +enum BreakpadForkBehavior { + DONOTHING = 0, + UNINSTALL, + RESETEXCEPTIONPORT +}; + +enum BreakpadForkTestCrashPoint { + DURINGLAUNCH = 5, + AFTERLAUNCH = 6, + BETWEENFORKEXEC = 7 +}; + +@interface Controller : NSObject { + IBOutlet NSWindow *window_; + IBOutlet NSWindow *forkTestOptions_; + + BreakpadRef breakpad_; + + enum BreakpadForkBehavior bpForkOption; + + BOOL useVFork; + enum BreakpadForkTestCrashPoint progCrashPoint; +} + +- (IBAction)crash:(id)sender; +- (IBAction)forkTestOptions:(id)sender; +- (IBAction)forkTestGo:(id)sender; +- (IBAction)showForkTestWindow:(id) sender; +- (void)generateReportWithoutCrash:(id)sender; +- (void)awakeFromNib; + +@end diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/Controller.m b/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/Controller.m new file mode 100644 index 000000000..87c43024b --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/Controller.m @@ -0,0 +1,261 @@ +// Copyright (c) 2006, 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 <Breakpad/Breakpad.h> + +#import "Controller.h" +#import "TestClass.h" +#import "GTMDefines.h" +#include <unistd.h> +#include <mach/mach.h> + +@implementation Controller + +- (void)causeCrash { + float *aPtr = nil; + NSLog(@"Crash!"); + NSLog(@"Bad programmer: %f", *aPtr); +} + +- (void)generateReportWithoutCrash:(id)sender { + BreakpadGenerateAndSendReport(breakpad_); +} + +- (IBAction)showForkTestWindow:(id) sender { + [forkTestOptions_ setIsVisible:YES]; +} + +- (IBAction)forkTestOptions:(id)sender { + NSInteger tag = [[sender selectedCell] tag]; + NSLog(@"sender tag: %d", tag); + if (tag <= 2) { + bpForkOption = tag; + } + + if (tag == 3) { + useVFork = NO; + } + + if (tag == 4) { + useVFork = YES; + } + + if (tag >= 5 && tag <= 7) { + progCrashPoint = tag; + } + +} + +- (IBAction)forkTestGo:(id)sender { + + NSString *resourcePath = [[NSBundle bundleForClass: + [self class]] resourcePath]; + NSString *execProgname = nil; + if (progCrashPoint == DURINGLAUNCH) { + execProgname = [resourcePath stringByAppendingString:@"/crashduringload"]; + } else if (progCrashPoint == AFTERLAUNCH) { + execProgname = [resourcePath stringByAppendingString:@"/crashInMain"]; + } + + const char *progName = NULL; + if (progCrashPoint != BETWEENFORKEXEC) { + progName = [execProgname UTF8String]; + } + + int pid; + + if (bpForkOption == UNINSTALL) { + BreakpadRelease(breakpad_); + } + + if (useVFork) { + pid = vfork(); + } else { + pid = fork(); + } + + if (pid == 0) { + sleep(3); + NSLog(@"Child continuing"); + FILE *fd = fopen("/tmp/childlog.txt","wt"); + kern_return_t kr; + if (bpForkOption == RESETEXCEPTIONPORT) { + kr = task_set_exception_ports(mach_task_self(), + EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION | + EXC_MASK_ARITHMETIC | EXC_MASK_BREAKPOINT, + MACH_PORT_NULL, + EXCEPTION_DEFAULT, + THREAD_STATE_NONE); + fprintf(fd,"task_set_exception_ports returned %d\n", kr); + } + + if (progCrashPoint == BETWEENFORKEXEC) { + fprintf(fd,"crashing post-fork\n"); + int *a = NULL; + printf("%d\n",*a++); + } + + fprintf(fd,"about to call exec with %s\n", progName); + fclose(fd); + int i = execl(progName, progName, NULL); + fprintf(fd, "exec returned! %d\n", i); + fclose(fd); + } +} + +- (IBAction)crash:(id)sender { + NSInteger tag = [sender tag]; + + if (tag == 1) { + [NSObject cancelPreviousPerformRequestsWithTarget:self]; + [self performSelector:@selector(causeCrash) withObject:nil afterDelay:10.0]; + [sender setState:NSOnState]; + return; + } + + if (tag == 2 && breakpad_) { + BreakpadRelease(breakpad_); + breakpad_ = NULL; + return; + } + + [self causeCrash]; +} + +- (void)anotherThread { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + TestClass *tc = [[TestClass alloc] init]; + + [tc wait]; + + [pool release]; +} + +- (void)awakeFromNib { + NSBundle *bundle = [NSBundle mainBundle]; + NSDictionary *info = [bundle infoDictionary]; + + + breakpad_ = BreakpadCreate(info); + + // Do some unit tests with keys + // first a series of bogus values + BreakpadSetKeyValue(breakpad_, nil, @"bad2"); + BreakpadSetKeyValue(nil, @"bad3", @"bad3"); + + // Now some good ones + BreakpadSetKeyValue(breakpad_,@"key1", @"value1"); + BreakpadSetKeyValue(breakpad_,@"key2", @"value2"); + BreakpadSetKeyValue(breakpad_,@"key3", @"value3"); + + // Look for a bogus one that we didn't try to set + NSString *test = BreakpadKeyValue(breakpad_, @"bad4"); + if (test) { + NSLog(@"Bad BreakpadKeyValue (bad4)"); + } + + // Look for a bogus one we did try to set + test = BreakpadKeyValue(breakpad_, @"bad1"); + if (test) { + NSLog(@"Bad BreakpadKeyValue (bad1)"); + } + + // Test some bad args for BreakpadKeyValue + test = BreakpadKeyValue(nil, @"bad5"); + if (test) { + NSLog(@"Bad BreakpadKeyValue (bad5)"); + } + + test = BreakpadKeyValue(breakpad_, nil); + if (test) { + NSLog(@"Bad BreakpadKeyValue (nil)"); + } + + // Find some we did set + test = BreakpadKeyValue(breakpad_, @"key1"); + if (![test isEqualToString:@"value1"]) { + NSLog(@"Can't find BreakpadKeyValue (key1)"); + } + test = BreakpadKeyValue(breakpad_, @"key2"); + if (![test isEqualToString:@"value2"]) { + NSLog(@"Can't find BreakpadKeyValue (key2)"); + } + test = BreakpadKeyValue(breakpad_, @"key3"); + if (![test isEqualToString:@"value3"]) { + NSLog(@"Can't find BreakpadKeyValue (key3)"); + } + + // Bad args for BreakpadRemoveKeyValue + BreakpadRemoveKeyValue(nil, @"bad6"); + BreakpadRemoveKeyValue(breakpad_, nil); + + // Remove one that is valid + BreakpadRemoveKeyValue(breakpad_, @"key3"); + + // Try and find it + test = BreakpadKeyValue(breakpad_, @"key3"); + if (test) { + NSLog(@"Shouldn't find BreakpadKeyValue (key3)"); + } + + // Try and remove it again + BreakpadRemoveKeyValue(breakpad_, @"key3"); + + // Try removal by setting to nil + BreakpadSetKeyValue(breakpad_,@"key2", nil); + // Try and find it + test = BreakpadKeyValue(breakpad_, @"key2"); + if (test) { + NSLog(@"Shouldn't find BreakpadKeyValue (key2)"); + } + + BreakpadAddUploadParameter(breakpad_, + @"MeaningOfLife", + @"42"); + [NSThread detachNewThreadSelector:@selector(anotherThread) + toTarget:self withObject:nil]; + + NSUserDefaults *args = [NSUserDefaults standardUserDefaults]; + + // If the user specified autocrash on the command line, toggle + // Breakpad to not confirm and crash immediately. This is for + // automated testing. + if ([args boolForKey:@"autocrash"]) { + BreakpadSetKeyValue(breakpad_, + @BREAKPAD_SKIP_CONFIRM, + @"YES"); + [self causeCrash]; + } + + progCrashPoint = DURINGLAUNCH; + [window_ center]; + [window_ makeKeyAndOrderFront:self]; +} + +@end diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/English.lproj/InfoPlist.strings b/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/English.lproj/InfoPlist.strings Binary files differnew file mode 100644 index 000000000..b8c6c6bf0 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/English.lproj/InfoPlist.strings diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/English.lproj/MainMenu.xib b/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/English.lproj/MainMenu.xib new file mode 100644 index 000000000..840c0db33 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/English.lproj/MainMenu.xib @@ -0,0 +1,3748 @@ +<?xml version="1.0" encoding="UTF-8"?> +<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.10"> + <data> + <int key="IBDocument.SystemTarget">1050</int> + <string key="IBDocument.SystemVersion">10F569</string> + <string key="IBDocument.InterfaceBuilderVersion">788</string> + <string key="IBDocument.AppKitVersion">1038.29</string> + <string key="IBDocument.HIToolboxVersion">461.00</string> + <object class="NSMutableDictionary" key="IBDocument.PluginVersions"> + <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="NS.object.0">788</string> + </object> + <object class="NSMutableArray" key="IBDocument.EditedObjectIDs"> + <bool key="EncodedWithXMLCoder">YES</bool> + <integer value="220"/> + </object> + <object class="NSArray" key="IBDocument.PluginDependencies"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + </object> + <object class="NSMutableDictionary" key="IBDocument.Metadata"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys" id="0"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + </object> + <object class="NSMutableArray" key="IBDocument.RootObjects" id="925601844"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSCustomObject" id="870565383"> + <object class="NSMutableString" key="NSClassName"> + <characters key="NS.bytes">NSApplication</characters> + </object> + </object> + <object class="NSCustomObject" id="442653439"> + <string key="NSClassName">FirstResponder</string> + </object> + <object class="NSCustomObject" id="751079937"> + <string key="NSClassName">NSApplication</string> + </object> + <object class="NSWindowTemplate" id="341270541"> + <int key="NSWindowStyleMask">15</int> + <int key="NSWindowBacking">2</int> + <string key="NSWindowRect">{{945, 874}, {320, 188}}</string> + <int key="NSWTFlags">1886912512</int> + <string key="NSWindowTitle">Window</string> + <string key="NSWindowClass">NSWindow</string> + <object class="NSMutableString" key="NSViewClass"> + <characters key="NS.bytes">View</characters> + </object> + <string key="NSWindowContentMaxSize">{1.79769e+308, 1.79769e+308}</string> + <string key="NSWindowContentMinSize">{213, 107}</string> + <object class="NSView" key="NSWindowView" id="814272478"> + <reference key="NSNextResponder"/> + <int key="NSvFlags">256</int> + <object class="NSMutableArray" key="NSSubviews"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSButton" id="726278107"> + <reference key="NSNextResponder" ref="814272478"/> + <int key="NSvFlags">301</int> + <string key="NSFrame">{{14, 140}, {292, 32}}</string> + <reference key="NSSuperview" ref="814272478"/> + <reference key="NSWindow"/> + <bool key="NSEnabled">YES</bool> + <object class="NSButtonCell" key="NSCell" id="539552922"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">134217728</int> + <string key="NSContents">Crash! (Airbag Installed)</string> + <object class="NSFont" key="NSSupport" id="933596199"> + <string key="NSName">LucidaGrande</string> + <double key="NSSize">13</double> + <int key="NSfFlags">1044</int> + </object> + <reference key="NSControlView" ref="726278107"/> + <int key="NSButtonFlags">-2038284033</int> + <int key="NSButtonFlags2">1</int> + <reference key="NSAlternateImage" ref="933596199"/> + <string key="NSAlternateContents"/> + <object class="NSMutableString" key="NSKeyEquivalent"> + <characters key="NS.bytes"/> + </object> + <int key="NSPeriodicDelay">200</int> + <int key="NSPeriodicInterval">25</int> + </object> + </object> + <object class="NSButton" id="799567279"> + <reference key="NSNextResponder" ref="814272478"/> + <int key="NSvFlags">301</int> + <string key="NSFrame">{{14, 76}, {292, 32}}</string> + <reference key="NSSuperview" ref="814272478"/> + <reference key="NSWindow"/> + <int key="NSTag">2</int> + <bool key="NSEnabled">YES</bool> + <object class="NSButtonCell" key="NSCell" id="1010617379"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">134217728</int> + <string key="NSContents">Crash! (Airbag NOT Installed)</string> + <reference key="NSSupport" ref="933596199"/> + <reference key="NSControlView" ref="799567279"/> + <int key="NSTag">2</int> + <int key="NSButtonFlags">-2038284033</int> + <int key="NSButtonFlags2">1</int> + <reference key="NSAlternateImage" ref="933596199"/> + <string key="NSAlternateContents"/> + <object class="NSMutableString" key="NSKeyEquivalent"> + <characters key="NS.bytes"/> + </object> + <int key="NSPeriodicDelay">200</int> + <int key="NSPeriodicInterval">25</int> + </object> + </object> + <object class="NSButton" id="27781390"> + <reference key="NSNextResponder" ref="814272478"/> + <int key="NSvFlags">301</int> + <string key="NSFrame">{{14, 108}, {292, 32}}</string> + <reference key="NSSuperview" ref="814272478"/> + <reference key="NSWindow"/> + <int key="NSTag">1</int> + <bool key="NSEnabled">YES</bool> + <object class="NSButtonCell" key="NSCell" id="547901497"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">134217728</int> + <string key="NSContents">Crash! (Airbag Installed w/10sec delay)</string> + <reference key="NSSupport" ref="933596199"/> + <reference key="NSControlView" ref="27781390"/> + <int key="NSTag">1</int> + <int key="NSButtonFlags">-2038284033</int> + <int key="NSButtonFlags2">1</int> + <reference key="NSAlternateImage" ref="933596199"/> + <string key="NSAlternateContents"/> + <object class="NSMutableString" key="NSKeyEquivalent"> + <characters key="NS.bytes"/> + </object> + <int key="NSPeriodicDelay">200</int> + <int key="NSPeriodicInterval">25</int> + </object> + </object> + <object class="NSButton" id="856256540"> + <reference key="NSNextResponder" ref="814272478"/> + <int key="NSvFlags">268</int> + <string key="NSFrame">{{14, 44}, {292, 32}}</string> + <reference key="NSSuperview" ref="814272478"/> + <reference key="NSWindow"/> + <bool key="NSEnabled">YES</bool> + <object class="NSButtonCell" key="NSCell" id="353736234"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">134217728</int> + <string key="NSContents">Fork Test</string> + <reference key="NSSupport" ref="933596199"/> + <reference key="NSControlView" ref="856256540"/> + <int key="NSButtonFlags">-2038284033</int> + <int key="NSButtonFlags2">129</int> + <string key="NSAlternateContents"/> + <string key="NSKeyEquivalent"/> + <int key="NSPeriodicDelay">200</int> + <int key="NSPeriodicInterval">25</int> + </object> + </object> + <object class="NSButton" id="460755987"> + <reference key="NSNextResponder" ref="814272478"/> + <int key="NSvFlags">268</int> + <string key="NSFrame">{{14, 12}, {292, 32}}</string> + <reference key="NSSuperview" ref="814272478"/> + <reference key="NSWindow"/> + <bool key="NSEnabled">YES</bool> + <object class="NSButtonCell" key="NSCell" id="775425649"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">134217728</int> + <string key="NSContents">Generate report without crash</string> + <reference key="NSSupport" ref="933596199"/> + <reference key="NSControlView" ref="460755987"/> + <int key="NSButtonFlags">-2038284033</int> + <int key="NSButtonFlags2">129</int> + <string key="NSAlternateContents"/> + <string key="NSKeyEquivalent"/> + <int key="NSPeriodicDelay">200</int> + <int key="NSPeriodicInterval">25</int> + </object> + </object> + </object> + <string key="NSFrameSize">{320, 188}</string> + <reference key="NSSuperview"/> + <reference key="NSWindow"/> + </object> + <string key="NSScreenRect">{{0, 0}, {1440, 878}}</string> + <string key="NSMinSize">{213, 129}</string> + <string key="NSMaxSize">{1.79769e+308, 1.79769e+308}</string> + </object> + <object class="NSMenu" id="695387251"> + <string key="NSTitle">MainMenu</string> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="458207250"> + <reference key="NSMenu" ref="695387251"/> + <string key="NSTitle">NewApplication</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <object class="NSCustomResource" key="NSOnImage" id="419346806"> + <string key="NSClassName">NSImage</string> + <string key="NSResourceName">NSMenuCheckmark</string> + </object> + <object class="NSCustomResource" key="NSMixedImage" id="290286705"> + <string key="NSClassName">NSImage</string> + <string key="NSResourceName">NSMenuMixedState</string> + </object> + <string key="NSAction">submenuAction:</string> + <object class="NSMenu" key="NSSubmenu" id="753534561"> + <string key="NSTitle">NewApplication</string> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="838552093"> + <reference key="NSMenu" ref="753534561"/> + <string key="NSTitle">About NewApplication</string> + <string key="NSKeyEquiv"/> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="758254482"> + <reference key="NSMenu" ref="753534561"/> + <bool key="NSIsDisabled">YES</bool> + <bool key="NSIsSeparator">YES</bool> + <string key="NSTitle"/> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="443649494"> + <reference key="NSMenu" ref="753534561"/> + <string key="NSTitle">Preferences…</string> + <string key="NSKeyEquiv">,</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="217746140"> + <reference key="NSMenu" ref="753534561"/> + <bool key="NSIsDisabled">YES</bool> + <bool key="NSIsSeparator">YES</bool> + <string key="NSTitle"/> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="826764396"> + <reference key="NSMenu" ref="753534561"/> + <string key="NSTitle">Services</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + <string key="NSAction">submenuAction:</string> + <object class="NSMenu" key="NSSubmenu" id="276709607"> + <object class="NSMutableString" key="NSTitle"> + <characters key="NS.bytes">Services</characters> + </object> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + <string key="NSName">_NSServicesMenu</string> + </object> + </object> + <object class="NSMenuItem" id="881859155"> + <reference key="NSMenu" ref="753534561"/> + <bool key="NSIsDisabled">YES</bool> + <bool key="NSIsSeparator">YES</bool> + <string key="NSTitle"/> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="104472016"> + <reference key="NSMenu" ref="753534561"/> + <string key="NSTitle">Hide NewApplication</string> + <string key="NSKeyEquiv">h</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="216168366"> + <reference key="NSMenu" ref="753534561"/> + <string key="NSTitle">Hide Others</string> + <string key="NSKeyEquiv">h</string> + <int key="NSKeyEquivModMask">1572864</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="667790509"> + <reference key="NSMenu" ref="753534561"/> + <string key="NSTitle">Show All</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="928933982"> + <reference key="NSMenu" ref="753534561"/> + <bool key="NSIsDisabled">YES</bool> + <bool key="NSIsSeparator">YES</bool> + <string key="NSTitle"/> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="887927135"> + <reference key="NSMenu" ref="753534561"/> + <string key="NSTitle">Quit NewApplication</string> + <string key="NSKeyEquiv">q</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + </object> + <string key="NSName">_NSAppleMenu</string> + </object> + </object> + <object class="NSMenuItem" id="369472335"> + <reference key="NSMenu" ref="695387251"/> + <string key="NSTitle">File</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + <string key="NSAction">submenuAction:</string> + <object class="NSMenu" key="NSSubmenu" id="902982238"> + <object class="NSMutableString" key="NSTitle"> + <characters key="NS.bytes">File</characters> + </object> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="660391032"> + <reference key="NSMenu" ref="902982238"/> + <string key="NSTitle">New</string> + <string key="NSKeyEquiv">n</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="367379562"> + <reference key="NSMenu" ref="902982238"/> + <string key="NSTitle">Open...</string> + <string key="NSKeyEquiv">o</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="84883275"> + <reference key="NSMenu" ref="902982238"/> + <string key="NSTitle">Open Recent</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + <string key="NSAction">submenuAction:</string> + <object class="NSMenu" key="NSSubmenu" id="693280130"> + <object class="NSMutableString" key="NSTitle"> + <characters key="NS.bytes">Open Recent</characters> + </object> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="85018532"> + <reference key="NSMenu" ref="693280130"/> + <string key="NSTitle">Clear Menu</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + </object> + <string key="NSName">_NSRecentDocumentsMenu</string> + </object> + </object> + <object class="NSMenuItem" id="154948703"> + <reference key="NSMenu" ref="902982238"/> + <bool key="NSIsDisabled">YES</bool> + <bool key="NSIsSeparator">YES</bool> + <string key="NSTitle"/> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="468594275"> + <reference key="NSMenu" ref="902982238"/> + <string key="NSTitle">Close</string> + <string key="NSKeyEquiv">w</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="479945444"> + <reference key="NSMenu" ref="902982238"/> + <string key="NSTitle">Save</string> + <string key="NSKeyEquiv">s</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="976375553"> + <reference key="NSMenu" ref="902982238"/> + <string key="NSTitle">Save As…</string> + <string key="NSKeyEquiv">S</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="885975128"> + <reference key="NSMenu" ref="902982238"/> + <string key="NSTitle">Revert</string> + <string key="NSKeyEquiv"/> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="232609393"> + <reference key="NSMenu" ref="902982238"/> + <bool key="NSIsDisabled">YES</bool> + <bool key="NSIsSeparator">YES</bool> + <string key="NSTitle"/> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="409810395"> + <reference key="NSMenu" ref="902982238"/> + <string key="NSTitle">Page Setup…</string> + <string key="NSKeyEquiv">P</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="302505815"> + <reference key="NSMenu" ref="902982238"/> + <string key="NSTitle">Print…</string> + <string key="NSKeyEquiv">p</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + </object> + </object> + </object> + <object class="NSMenuItem" id="542216986"> + <reference key="NSMenu" ref="695387251"/> + <string key="NSTitle">Edit</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + <string key="NSAction">submenuAction:</string> + <object class="NSMenu" key="NSSubmenu" id="1053284541"> + <object class="NSMutableString" key="NSTitle"> + <characters key="NS.bytes">Edit</characters> + </object> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="284548410"> + <reference key="NSMenu" ref="1053284541"/> + <string key="NSTitle">Undo</string> + <string key="NSKeyEquiv">z</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="1001272176"> + <reference key="NSMenu" ref="1053284541"/> + <string key="NSTitle">Redo</string> + <string key="NSKeyEquiv">Z</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="209744238"> + <reference key="NSMenu" ref="1053284541"/> + <bool key="NSIsDisabled">YES</bool> + <bool key="NSIsSeparator">YES</bool> + <string key="NSTitle"/> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="909447496"> + <reference key="NSMenu" ref="1053284541"/> + <string key="NSTitle">Cut</string> + <string key="NSKeyEquiv">x</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="994487277"> + <reference key="NSMenu" ref="1053284541"/> + <string key="NSTitle">Copy</string> + <string key="NSKeyEquiv">c</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="84012734"> + <reference key="NSMenu" ref="1053284541"/> + <string key="NSTitle">Paste</string> + <string key="NSKeyEquiv">v</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="182251545"> + <reference key="NSMenu" ref="1053284541"/> + <string key="NSTitle">Paste and Match Style</string> + <string key="NSKeyEquiv">V</string> + <int key="NSKeyEquivModMask">1572864</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="512189403"> + <reference key="NSMenu" ref="1053284541"/> + <string key="NSTitle">Delete</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="917620781"> + <reference key="NSMenu" ref="1053284541"/> + <string key="NSTitle">Select All</string> + <string key="NSKeyEquiv">a</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="431895313"> + <reference key="NSMenu" ref="1053284541"/> + <bool key="NSIsDisabled">YES</bool> + <bool key="NSIsSeparator">YES</bool> + <string key="NSTitle"/> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="153501847"> + <reference key="NSMenu" ref="1053284541"/> + <string key="NSTitle">Find</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + <string key="NSAction">submenuAction:</string> + <object class="NSMenu" key="NSSubmenu" id="333484665"> + <object class="NSMutableString" key="NSTitle"> + <characters key="NS.bytes">Find</characters> + </object> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="203238834"> + <reference key="NSMenu" ref="333484665"/> + <string key="NSTitle">Find…</string> + <string key="NSKeyEquiv">f</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + <int key="NSTag">1</int> + </object> + <object class="NSMenuItem" id="861312964"> + <reference key="NSMenu" ref="333484665"/> + <string key="NSTitle">Find Next</string> + <string key="NSKeyEquiv">g</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + <int key="NSTag">2</int> + </object> + <object class="NSMenuItem" id="743767160"> + <reference key="NSMenu" ref="333484665"/> + <string key="NSTitle">Find Previous</string> + <string key="NSKeyEquiv">G</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + <int key="NSTag">3</int> + </object> + <object class="NSMenuItem" id="180446588"> + <reference key="NSMenu" ref="333484665"/> + <string key="NSTitle">Use Selection for Find</string> + <string key="NSKeyEquiv">e</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + <int key="NSTag">7</int> + </object> + <object class="NSMenuItem" id="731027425"> + <reference key="NSMenu" ref="333484665"/> + <string key="NSTitle">Jump to Selection</string> + <string key="NSKeyEquiv">j</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + </object> + </object> + </object> + <object class="NSMenuItem" id="61602259"> + <reference key="NSMenu" ref="1053284541"/> + <string key="NSTitle">Spelling</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + <string key="NSAction">submenuAction:</string> + <object class="NSMenu" key="NSSubmenu" id="8174285"> + <string key="NSTitle">Spelling</string> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="438210660"> + <reference key="NSMenu" ref="8174285"/> + <string key="NSTitle">Spelling…</string> + <string key="NSKeyEquiv">:</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="102172584"> + <reference key="NSMenu" ref="8174285"/> + <string key="NSTitle">Check Spelling</string> + <string key="NSKeyEquiv">;</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="540509341"> + <reference key="NSMenu" ref="8174285"/> + <string key="NSTitle">Check Spelling as You Type</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + </object> + </object> + </object> + </object> + </object> + </object> + <object class="NSMenuItem" id="764068863"> + <reference key="NSMenu" ref="695387251"/> + <string key="NSTitle">Window</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + <string key="NSAction">submenuAction:</string> + <object class="NSMenu" key="NSSubmenu" id="200536676"> + <object class="NSMutableString" key="NSTitle"> + <characters key="NS.bytes">Window</characters> + </object> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="683986939"> + <reference key="NSMenu" ref="200536676"/> + <string key="NSTitle">Minimize</string> + <string key="NSKeyEquiv">m</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="548098734"> + <reference key="NSMenu" ref="200536676"/> + <string key="NSTitle">Zoom</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="297002686"> + <reference key="NSMenu" ref="200536676"/> + <bool key="NSIsDisabled">YES</bool> + <bool key="NSIsSeparator">YES</bool> + <string key="NSTitle"/> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + <object class="NSMenuItem" id="164762492"> + <reference key="NSMenu" ref="200536676"/> + <string key="NSTitle">Bring All to Front</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + </object> + <string key="NSName">_NSWindowsMenu</string> + </object> + </object> + <object class="NSMenuItem" id="599772536"> + <reference key="NSMenu" ref="695387251"/> + <string key="NSTitle">Help</string> + <string key="NSKeyEquiv"/> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + <string key="NSAction">submenuAction:</string> + <object class="NSMenu" key="NSSubmenu" id="1066958924"> + <string key="NSTitle">Help</string> + <object class="NSMutableArray" key="NSMenuItems"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMenuItem" id="192540884"> + <reference key="NSMenu" ref="1066958924"/> + <string key="NSTitle">NewApplication Help</string> + <string key="NSKeyEquiv">?</string> + <int key="NSKeyEquivModMask">1048576</int> + <int key="NSMnemonicLoc">2147483647</int> + <reference key="NSOnImage" ref="419346806"/> + <reference key="NSMixedImage" ref="290286705"/> + </object> + </object> + </object> + </object> + </object> + <string key="NSName">_NSMainMenu</string> + </object> + <object class="NSCustomObject" id="623097029"> + <string key="NSClassName">Controller</string> + </object> + <object class="NSWindowTemplate" id="347013037"> + <int key="NSWindowStyleMask">15</int> + <int key="NSWindowBacking">2</int> + <string key="NSWindowRect">{{858, 755}, {787, 260}}</string> + <int key="NSWTFlags">603979776</int> + <string key="NSWindowTitle">Window</string> + <string key="NSWindowClass">NSWindow</string> + <nil key="NSViewClass"/> + <string key="NSWindowContentMaxSize">{1.79769e+308, 1.79769e+308}</string> + <object class="NSView" key="NSWindowView" id="594333702"> + <reference key="NSNextResponder"/> + <int key="NSvFlags">256</int> + <object class="NSMutableArray" key="NSSubviews"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSMatrix" id="891367997"> + <reference key="NSNextResponder" ref="594333702"/> + <int key="NSvFlags">268</int> + <string key="NSFrame">{{20, 7}, {645, 79}}</string> + <reference key="NSSuperview" ref="594333702"/> + <bool key="NSEnabled">YES</bool> + <int key="NSNumRows">3</int> + <int key="NSNumCols">1</int> + <object class="NSMutableArray" key="NSCells"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSButtonCell" id="410017819"> + <int key="NSCellFlags">-2080244224</int> + <int key="NSCellFlags2">0</int> + <string key="NSContents">program crashes during launch because of missing dylib</string> + <reference key="NSSupport" ref="933596199"/> + <reference key="NSControlView" ref="891367997"/> + <int key="NSTag">5</int> + <int key="NSButtonFlags">1211912703</int> + <int key="NSButtonFlags2">0</int> + <object class="NSButtonImageSource" key="NSAlternateImage" id="619763889"> + <string key="NSImageName">NSRadioButton</string> + </object> + <string key="NSAlternateContents"/> + <string key="NSKeyEquivalent"/> + <int key="NSPeriodicDelay">200</int> + <int key="NSPeriodicInterval">25</int> + </object> + <object class="NSButtonCell" id="904578786"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">0</int> + <string key="NSContents">program crashes after launch</string> + <reference key="NSSupport" ref="933596199"/> + <reference key="NSControlView" ref="891367997"/> + <int key="NSTag">6</int> + <int key="NSButtonFlags">1211912703</int> + <int key="NSButtonFlags2">0</int> + <object class="NSImage" key="NSNormalImage"> + <int key="NSImageFlags">549453824</int> + <string key="NSSize">{18, 18}</string> + <object class="NSMutableArray" key="NSReps"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray"> + <bool key="EncodedWithXMLCoder">YES</bool> + <integer value="0"/> + <object class="NSBitmapImageRep"> + <object class="NSData" key="NSTIFFRepresentation"> + <bytes key="NS.bytes">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 +AAAAAF1KAAA0kQAACCVYWVogAAAAAAAAdCAAALRgAAAjPVhZWiAAAAAAAAAlbAAAFyoAAKfDWFlaIAAA +AAAAAPNSAAEAAAABFs9zZjMyAAAAAAABDEIAAAXe///zJgAAB5IAAP2R///7ov///aMAAAPcAADAbGN1 +cnYAAAAAAAAAAQHNAABjdXJ2AAAAAAAAAAEBzQAAY3VydgAAAAAAAAABAc0AAHZjZ3QAAAAAAAAAAAAD +AQAAAQACBAUGBwkKCw0ODxASExQWFxgaGxweHyAiIyQmJygpKywtLzAxMjM1Njc4OTs8PT5AQUJDREZH +SElKS0xOT1BRUlNUVVZXWFlaW1xdXl9hYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ent8fX5/gIGCg4SF +hoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnZ6foKGio6SlpqanqKmqq6ytra6vsLGysrO0tba3uLi5uru8 +vL2+v8DBwcLDxMXGxsfIycrKy8zNzs7P0NHS0tPU1dbW19jZ2drb3Nzd3t/g4eLi4+Tl5ufo6enq6+zt +7u/w8fHy8/T19vf4+fr7/P3+/v8AAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR8gISIjJCUnKCkq +Ky0uLzAxMzQ1Njc4OTo7PD0/QEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaWltcXV5fYGFiY2RlZmdo +aWprbG1ub3BxcnN0dXZ3d3h5ent8fH1+f4CBgoKDhIWGh4iIiYqLjI2Oj5CRkpOUlJWWl5iZmpucnZ2e +n6ChoqOkpaamp6ipqqusra6vsLCxsrO0tba3uLm5uru8vb6/wMHCw8TFx8jJysvMzc7P0NDR0tPU1dbX +2Nna29ze3+Dh4uPk5ebn6err7O3u7/Hy8/T19vf5+vv8/f7/AAIDAwQFBgcICQoKCwwNDg8QERITFBUW +FxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODg5Ojs8PT4+P0BBQkNDREVGR0hJSUpLTE1O +Tk9QUVJSU1RVVVZXWFhZWltbXF1eXl9gYWFiY2RkZWZnZ2hpaWprbGxtbm5vcHFxcnNzdHV1dnd4eHl6 +ent8fH1+fn+AgYGCg4SEhYaHiImJiouMjY6Oj5CRkpOTlJWWl5iZmZqbnJ2en6ChoqOkpaanqKmqq6yt +rq+xsrO0tba3uLq7vL2+wMHDxMbHycrMzs/R0tTW19nb3d7g4uTm6Ors7vDy9Pb4+vz+/wAAbmRpbgAA +AAAAAAY2AACXGgAAVjoAAFPKAACJ3gAAJ8IAABaoAABQDQAAVDkAAiuFAAIZmQABeFEAAwEAAAIAAAAA +AAEABgANABcAIwAxAEAAUgBlAHsAkwCrAMUA4gD/AR8BPwFhAYUBqgHQAfgCIAJLAncCpQLSAwIDMwNl +A5gDzgQFBD0EdQSvBOsFKQVnBacF6AYqBm4GtQb8B0UHkgfkCDkIkAjnCT4JmAn0ClAKrQsLC2sLygwq +DIwM8Q1XDcAOKA6SDv4PbA/bEE0QxBE7EbQSMRKwEzITuRREFNAVYBXxFocXHhfAGGIZBBmsGlQa+RuU +HC4czh1yHhQeux9jIA0gvCFoIhkizyOJJEEk+SW6JnknOygFKMspkypiKzIsASzXLawuhy9gMD4xGzH8 +MtszvzSgNYY2cjdcOEw5OTorOxs8CD0EPfU+6z/nQOFB2ELUQ9VE00XcRttH5EjxSgBLCUwdTTFOUE9v +UI9Rt1LdVAVVNlZsV6VY4FohW21ct135X09goGH0Y0tkqGYFZ19oxGova5ptCG54b/BxbnLsdG119Xd/ +eQh6knwqfcV/W4D4gpSEO4Xih4CJKorYjIqOOY/jkZuTWJUOlsyYiZpSnB6d4Z+soX+jWqUvpxOo+6rj +rMuuwLC4sra0rra0uL+60LzfvwDBHcLdxLXGhchYyi7MCs3lz7rRmtOA1WPXR9kq2xPc/97s4M/iveSn +5o3obupT7ELuLPAM8fLz0PW396H5f/tZ/T3//wAAAAEAAwALABYAJQA3AE0AZQCBAJ8AwQDlAQsBNQFh +AZABwQH1AisCZAKfAtwDHANfA6MD6gQ0BH8EzQT1BR0FcAXEBhsGdAbPBy0HXAeMB+4IUgi4CSAJVAmK +CfYKZArVC0cLgQu8DDIMqw0mDaIOIQ6hDyQPqRAvELgQ/RFDEc8SXRLuE4AUFRSrFUMV3RZ5FxcXthhY +GPwZoRpIGvEbnBxJHPgdqB5bHw8fxSB9ITch8iKwJDAk8yW3Jn4nRigQKNwpqSp5K0osHCzxLccuoC95 +MFUxMzISMvMz1TS5NaA2hzdxOFw5STo4Oyg8Gj4DPvs/9EDuQepD6ETpRexG8Uf3SP9LFEwhTTBOQE9S +UGZSklOrVMVV4Vb/WB5ZP1phW4Vcq13SXvthUmJ/Y69k4GYSZ0dofGm0au1tZG6ib+FxInJlc6l073Y2 +d396FXtjfLJ+A39VgKmB/4NWhK+GCYjCiiGLgYzjjkePrJESknuT5Ja8mCuZm5sMnH+d9J9qoOGiWqPV +pVGmz6eOqE6pzqtRrNSuWq/gsWmy8rR+tgu5Kbq6vE294b93wQ7Cp8RBxd3He8kZyrrLisxbzf/Po9FK +0vHUm9ZF1/HZn9tO3Cbc/96x4GTiGePQ5YjnQegf6Pzquex27jbv9/G583z0X/VC9wj40Pqa/GX+Mf// +AAAAAQADAAsAJQA3AE0AZQCBAJ8AwQELATUBYQGQAcEB9QIrAmQCnwLcAxwDXwOjA+oENAR/BM0FHQVw +BcQGGwZ0Bs8HLQeMB+4IUgi4CSAJign2CmQK1QtHC7wMMgyrDSYNog4hDqEPJA+pEC8QuBFDEl0S7hOA +FBUUqxVDFnkXFxe2GFgY/BpIGvEbnBxJHPgdqB8PH8UgfSE3IfIjbyQwJPMltydGKBAo3Cp5K0osHC3H +LqAveTEzMhIy8zS5NaA2hzhcOUk6ODwaPQ4+Az/0QO5C6EPoROlG8Uf3SglLFEwhTkBPUlF7UpJUxVXh +Vv9ZP1phXKtd0mAlYVJjr2TgZhJofGm0au1tZG6ib+FxInJldO92Nnd/eMl6FXyyfgN/VYCpgf+Er4YJ +h2WIwoohi4GOR4+skRKSe5PklVCWvJgrmZubDJx/nfSfaqDholqj1aVRps+oTqnOq1Gs1K2Xrlqv4LFp +svK0frYLt5m5Kbnxurq8Tb3hv3fBDsHawqfEQcUPxd3He8hKyRnKusuKzFvN/87Rz6PQdtFK0vHTxtSb +1kXXG9fx2MjZn9tO3Cbc/93Y3rHfiuBk4hni9ePQ5KzliOZk50HoH+j86drqueuX7HbtVu427xbv9/DX +8bnymvN89F/1QvYl9wj37PjQ+bX6mvt//GX9S/4x//8AAGRlc2MAAAAAAAAACkNvbG9yIExDRAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAABIAAAAcAEMAbwBsAG8AcgAgAEwAQwBE +AABtbW9kAAAAAAAABhAAAJxOAAAAAL5zkQAAAAAAAAAAAAAAAAAAAAAAdGV4dAAAAABDb3B5cmlnaHQg +QXBwbGUgQ29tcHV0ZXIsIEluYy4sIDIwMDUAAAAAA</bytes> + </object> + </object> + </object> + </object> + <object class="NSColor" key="NSColor" id="30384615"> + <int key="NSColorSpace">3</int> + <bytes key="NSWhite">MCAwAA</bytes> + </object> + </object> + <reference key="NSAlternateImage" ref="619763889"/> + <int key="NSPeriodicDelay">400</int> + <int key="NSPeriodicInterval">75</int> + </object> + <object class="NSButtonCell" id="971445237"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">0</int> + <string key="NSContents">program crashes in between fork() and exec() (3rd option in first group will happen before crash)</string> + <reference key="NSSupport" ref="933596199"/> + <reference key="NSControlView" ref="891367997"/> + <int key="NSTag">7</int> + <int key="NSButtonFlags">1211912703</int> + <int key="NSButtonFlags2">0</int> + <object class="NSImage" key="NSNormalImage"> + <int key="NSImageFlags">549453824</int> + <string key="NSSize">{18, 18}</string> + <object class="NSMutableArray" key="NSReps"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray"> + <bool key="EncodedWithXMLCoder">YES</bool> + <integer value="0"/> + <object class="NSBitmapImageRep"> + <object class="NSData" key="NSTIFFRepresentation"> + <bytes key="NS.bytes">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 +ArBYWVogAAAAAAAANWMAAJ/rAAAZsVhZWiAAAAAAAAAlzQAAE9UAALbFWFlaIAAAAAAAAPPYAAEAAAAB +FghzZjMyAAAAAAABC7cAAAWW///zVwAABykAAP3X///7t////aYAAAPaAADA9mN1cnYAAAAAAAAAAQHN +AABjdXJ2AAAAAAAAAAEBzQAAY3VydgAAAAAAAAABAc0AAHZjZ3QAAAAAAAAAAAADAQAAAgAAAioENAYA +B9AJlAtRDQwOshBOEekTgxUVFqMYMRm9GzwcvR4+H7chLiKnJBoliCb3KGIpyCswLJMt9C9WMLIyDjNn +NL02FTdoOLs6ETtdPKw9+D9DQJBB1kMeRGZFq0bySDRJeEq6S/tNPk59T75Q+lI2U25Uo1XZVwlYOlln +WpZbwFzsXhdfQWBrYZRiv2PoZRNmPWdqaJhpyGr5bC1tZW6hb+BxLXJ+c9d1OHafeA55gnr5fHR98H9t +gOuCY4PYhUqGsIgSiWyKuIwBjTqObY+VkLORyJLUk9eU05XFlrWXlZh2mUuaG5rsm6ucbJ0qndqei588 +n9+ghKEqoceiYqL/o5qkLqTDpVml66Z7pwunnKgpqLWpQqnQqlqq5Ktvq/qsg60MrZauIa6qrzOvvbBH +sNGxXLHnsnKy/7OMtBm0p7U3tci2Wbbrt4C4FrituUa54rqAux27wbxlvQq9rr5Tvvi/ncBAwOXBisIu +wtPDeMQdxMLFaMYPxrXHXMgEyKzJVcn/yqnLVcwBzK7NXc4Mzr3PcNAk0NnRkdJK0wTTw9SC1UTWCtbR +153Ybdk+2hfa8dvM3KfdhN5g3zzgGuD34dbiteOV5HblWeY85yHoCOjw6drqxuu27Kbtm+6R74zwivGM +8pPzn/Sw9cj25/gP+T76e/u//Rr+hP//AAABpANzBRoGsggnCZsLFQx+Dd4PRRCiEf8TYxS0FgoXXRiu +GgQbTRyZHekfMCB8IcIjCSRSJZUm3SgdKWAqpCvjLSYuZC+lMOIyIDNgNJs12TcTOFA5izrEO/49NT5w +P6dA30IWQ01Eg0W4Ru9IIElVSoZLt0zmThVPRFBwUZ5SylP5VSRWUVd+WKtZ2lsIXDhdaV6bX89hBWI8 +Y3dktGX0ZzhohGnVayxsiW3sb1Vww3I0c6p1I3aeeBl5k3sMfIR99X9kgM6CLYOJhNyGJodriKGJ1Ir5 +jBuNL45Aj0WQSJE8kjKTGJP+lNyVspaKl1OYHZjlmaSaZJshm9ecj51EnfSepJ9Vn/+gqqFWofyio6NL +o/CklKU4pdymfacfp8KoYqkDqaWqRarlq4asJ6zHrWiuCq6rr02v77CSsTax27KAsyezzrR3tSG1zLZ5 +tye32LiKuT659rqwu2q8J7zkvaK+YL8ev93AnMFcwhzC3MOdxF7FIMXixqXHaMgryPDJtcp6y0DMB8zO +zZbOX88oz/PQvtGJ0lbTI9Px1MDVkNZi1zTYB9jb2bDah9te3DjdEt3t3sjfpOB/4VviN+MT4/DkzeWq +5ojnZuhG6SXqBuro68rsre2S7nfvXvBH8TDyHPMI8/j06fXc9tL3yvjF+cL6w/vG/ND92v7s//8AAAMJ +BboIZwrCDSsPghG8E/IWHxg5GkgcVB5VIEQiMyQTJeknuimHK00tCy7AMHEyHDO/NV829ziKOhs7pj0s +PrBALEGmQx9EkkYCR3JI3EpCS6pND05vT89RLVKKU+dVP1aYV+9ZRVqdW/NdSV6hX+thM2JzY61k42YS +Z0FoZ2mOaq5rz2zsbglvI3A9cVRybHOEdJx1tHbOd+d5A3ofez98Yn2Lfrl/8IEqgmyDsoT8hkuHnYjw +ikSLmYzsjj+PjJDWkh2TW5SXlceW85gWmTOaSJtVnFqdWp5Pn0SgKaEQoeuiwqOYpGClKaXtpqmnZqgf +qNKph6o5quWrk6xArOatja41rtevebAcsLyxWbH3spWzL7PJtGO0/LWTtiq2wrdWt+q4f7kUuaW6OLrL +u1277Lx9vQ69nr4tvry/TL/bwGjA98GGwhPCocMvw77ESsTYxWXF9MZ/xwzHmcgkyKXJJ8mpyizKpMsc +y5XMDsyFzPjNa83fzlPOxc81z6fQGNCK0PvRbNHe0lDSw9M206vUINSV1QvVhtYA1nzW+Nd61//YhNkK +2ZnaL9rG213cCty63WveI97d35fgUuEO4crih+NE5ALkw+WD5kXnCufP6JbpYOor6vrry+yd7XbuUe8w +8BXw+/Ht8uDz4PTl9fj3E/hE+X363vxa/gH//wAAbmRpbgAAAAAAAAMGAACogAAAUwAAADRAAACqQAAA +JpcAABLbAABQQAAAVEAAAj99AAI1egACxUsAAwB4AAIAAAADAAsAGQAsAEUAYwCHALEA4QEWAVEBkgHZ +AiYCeQLSAzEDlwQDBHYE7wVvBfUGgwcXB7IIUwj8CawKYgsgC+QMrw2BDloPORAfEQ0SBRMGFBEVJBZA +F2MYjhm/GvYcMh1xHrMf9SE1ImwjnSTJJfAnFig7KWMqjivBLP4uSC+jMRMymzRBNgo3+joWPGY+8EG8 +RNhIQEvvT95UCFhkXOxhlWZYaylv/XTKeYN+GoKOhxGLqJBOlP6Ztp5voyan1Kx0sQG1c7nGvfHB9cX7 +ygbOFNIi1izaMN4p4hTl7emv7Vbw3vRC93z6iP1g//8AAAAEAA8AIgA9AF8AiQC7APQBNAF8AcwCIgKB +AuYDUwPHBEIExAVOBd4GdgcUB7oIZgkaCdQKlQteDC0NAw3gDsQPrxCiEZwSnxOpFLsV0xbyGBcZQRpw +G6Mc2R4RH0oggyG3IuckEyU8JmQniyiyKd0rDCxBLYAuyTAhMYkzBjSbNkw4HToSPDA+fED8Q7FGmUmx +TPdQaFQAV7tbll+KY5RnrmvTb/p0H3g6fEOAMoQXiASL+I/yk/KX+JwBoA2kHKgrrDuwS7RYuGK8aMBo +xGvId8yI0J/UuNjS3OvhAOUQ6RftE/EC9OH4rfxi//8AAAABAAYADQAXACUANQBIAF8AeQCWALcA3AEE +ATABYQGVAc4CDAJOApUC4QMyA4gD5QRGBK4FHAWPBgkGigcQB54IMQjMCW0KFArDC3cMMgzzDbsOiA9a +EDIRFhIIEwgUFRUvFlYXhxjDGgkbVhyqHgMfYCC8IhIjYSSrJfMnOSiBKc0rHyx7LeQvXTDrMpE0VTY7 +OEg6gjzuP5NCckWCSMJMMk/QU5xXk1u1X/9kcGkGbb5ylneLfJqByoeDjcyUf5t4oo2plLBetru8eMFr +xirK7c+v1GnZFd2r4iXme+ql7pryUvXD+OT7qv4M//8AAGRlc2MAAAAAAAAAFUhQIExQMzA2NSBDYWxp +YnJhdGVkAAAAAAAAAAAVAEgAUAAgAEwAUAAzADAANgA1ACAAQwBhAGwAaQBiAHIAYQB0AGUAZAAAAAAV +SFAgTFAzMDY1IENhbGlicmF0ZWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAHRleHQAAAAAQ29weXJpZ2h0IEFwcGxlIEluYy4sIDIwMDgAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAABtbW9kAAAAAAAAIvAAACaQAAAAAMJtVwAAAAAAAAAAAAAAAAAAAAAAA</bytes> + </object> + </object> + </object> + </object> + <reference key="NSColor" ref="30384615"/> + </object> + <reference key="NSAlternateImage" ref="619763889"/> + <string key="NSAlternateContents"/> + <int key="NSPeriodicDelay">200</int> + <int key="NSPeriodicInterval">25</int> + </object> + </object> + <string key="NSCellSize">{645, 25}</string> + <string key="NSIntercellSpacing">{4, 2}</string> + <int key="NSMatrixFlags">1151868928</int> + <string key="NSCellClass">NSActionCell</string> + <object class="NSButtonCell" key="NSProtoCell" id="1072218638"> + <int key="NSCellFlags">-2080244224</int> + <int key="NSCellFlags2">0</int> + <string key="NSContents">program crashes after launch</string> + <reference key="NSSupport" ref="933596199"/> + <int key="NSTag">5</int> + <int key="NSButtonFlags">1211912703</int> + <int key="NSButtonFlags2">0</int> + <reference key="NSAlternateImage" ref="619763889"/> + <string key="NSAlternateContents"/> + <string key="NSKeyEquivalent"/> + <int key="NSPeriodicDelay">200</int> + <int key="NSPeriodicInterval">25</int> + </object> + <reference key="NSSelectedCell" ref="410017819"/> + <object class="NSColor" key="NSBackgroundColor" id="349124561"> + <int key="NSColorSpace">6</int> + <string key="NSCatalogName">System</string> + <string key="NSColorName">controlColor</string> + <object class="NSColor" key="NSColor"> + <int key="NSColorSpace">3</int> + <bytes key="NSWhite">MC42NjY2NjY2NjY3AA</bytes> + </object> + </object> + <object class="NSColor" key="NSCellBackgroundColor" id="195671423"> + <int key="NSColorSpace">3</int> + <bytes key="NSWhite">MQA</bytes> + </object> + <reference key="NSFont" ref="933596199"/> + </object> + <object class="NSMatrix" id="7590393"> + <reference key="NSNextResponder" ref="594333702"/> + <int key="NSvFlags">268</int> + <string key="NSFrame">{{20, 170}, {565, 70}}</string> + <reference key="NSSuperview" ref="594333702"/> + <bool key="NSEnabled">YES</bool> + <int key="NSNumRows">3</int> + <int key="NSNumCols">1</int> + <object class="NSMutableArray" key="NSCells"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSButtonCell" id="808388382"> + <int key="NSCellFlags">-2080244224</int> + <int key="NSCellFlags2">0</int> + <string key="NSContents">Leave breakpad alone before fork</string> + <reference key="NSSupport" ref="933596199"/> + <reference key="NSControlView" ref="7590393"/> + <int key="NSButtonFlags">1211912703</int> + <int key="NSButtonFlags2">0</int> + <reference key="NSAlternateImage" ref="619763889"/> + <string key="NSAlternateContents"/> + <string key="NSKeyEquivalent"/> + <int key="NSPeriodicDelay">200</int> + <int key="NSPeriodicInterval">25</int> + </object> + <object class="NSButtonCell" id="378736460"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">0</int> + <string key="NSContents">Uninitialize Breakpad before fork</string> + <reference key="NSSupport" ref="933596199"/> + <reference key="NSControlView" ref="7590393"/> + <int key="NSTag">1</int> + <int key="NSButtonFlags">1211912703</int> + <int key="NSButtonFlags2">0</int> + <object class="NSImage" key="NSNormalImage"> + <int key="NSImageFlags">549453824</int> + <string key="NSSize">{18, 18}</string> + <object class="NSMutableArray" key="NSReps"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray"> + <bool key="EncodedWithXMLCoder">YES</bool> + <integer value="0"/> + <object class="NSBitmapImageRep"> + <object class="NSData" key="NSTIFFRepresentation"> + <bytes key="NS.bytes">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 +AAAAAF1KAAA0kQAACCVYWVogAAAAAAAAdCAAALRgAAAjPVhZWiAAAAAAAAAlbAAAFyoAAKfDWFlaIAAA +AAAAAPNSAAEAAAABFs9zZjMyAAAAAAABDEIAAAXe///zJgAAB5IAAP2R///7ov///aMAAAPcAADAbGN1 +cnYAAAAAAAAAAQHNAABjdXJ2AAAAAAAAAAEBzQAAY3VydgAAAAAAAAABAc0AAHZjZ3QAAAAAAAAAAAAD +AQAAAQACBAUGBwkKCw0ODxASExQWFxgaGxweHyAiIyQmJygpKywtLzAxMjM1Njc4OTs8PT5AQUJDREZH +SElKS0xOT1BRUlNUVVZXWFlaW1xdXl9hYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ent8fX5/gIGCg4SF +hoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnZ6foKGio6SlpqanqKmqq6ytra6vsLGysrO0tba3uLi5uru8 +vL2+v8DBwcLDxMXGxsfIycrKy8zNzs7P0NHS0tPU1dbW19jZ2drb3Nzd3t/g4eLi4+Tl5ufo6enq6+zt +7u/w8fHy8/T19vf4+fr7/P3+/v8AAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR8gISIjJCUnKCkq +Ky0uLzAxMzQ1Njc4OTo7PD0/QEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaWltcXV5fYGFiY2RlZmdo +aWprbG1ub3BxcnN0dXZ3d3h5ent8fH1+f4CBgoKDhIWGh4iIiYqLjI2Oj5CRkpOUlJWWl5iZmpucnZ2e +n6ChoqOkpaamp6ipqqusra6vsLCxsrO0tba3uLm5uru8vb6/wMHCw8TFx8jJysvMzc7P0NDR0tPU1dbX +2Nna29ze3+Dh4uPk5ebn6err7O3u7/Hy8/T19vf5+vv8/f7/AAIDAwQFBgcICQoKCwwNDg8QERITFBUW +FxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODg5Ojs8PT4+P0BBQkNDREVGR0hJSUpLTE1O +Tk9QUVJSU1RVVVZXWFhZWltbXF1eXl9gYWFiY2RkZWZnZ2hpaWprbGxtbm5vcHFxcnNzdHV1dnd4eHl6 +ent8fH1+fn+AgYGCg4SEhYaHiImJiouMjY6Oj5CRkpOTlJWWl5iZmZqbnJ2en6ChoqOkpaanqKmqq6yt +rq+xsrO0tba3uLq7vL2+wMHDxMbHycrMzs/R0tTW19nb3d7g4uTm6Ors7vDy9Pb4+vz+/wAAbmRpbgAA +AAAAAAY2AACXGgAAVjoAAFPKAACJ3gAAJ8IAABaoAABQDQAAVDkAAiuFAAIZmQABeFEAAwEAAAIAAAAA +AAEABgANABcAIwAxAEAAUgBlAHsAkwCrAMUA4gD/AR8BPwFhAYUBqgHQAfgCIAJLAncCpQLSAwIDMwNl +A5gDzgQFBD0EdQSvBOsFKQVnBacF6AYqBm4GtQb8B0UHkgfkCDkIkAjnCT4JmAn0ClAKrQsLC2sLygwq +DIwM8Q1XDcAOKA6SDv4PbA/bEE0QxBE7EbQSMRKwEzITuRREFNAVYBXxFocXHhfAGGIZBBmsGlQa+RuU +HC4czh1yHhQeux9jIA0gvCFoIhkizyOJJEEk+SW6JnknOygFKMspkypiKzIsASzXLawuhy9gMD4xGzH8 +MtszvzSgNYY2cjdcOEw5OTorOxs8CD0EPfU+6z/nQOFB2ELUQ9VE00XcRttH5EjxSgBLCUwdTTFOUE9v +UI9Rt1LdVAVVNlZsV6VY4FohW21ct135X09goGH0Y0tkqGYFZ19oxGova5ptCG54b/BxbnLsdG119Xd/ +eQh6knwqfcV/W4D4gpSEO4Xih4CJKorYjIqOOY/jkZuTWJUOlsyYiZpSnB6d4Z+soX+jWqUvpxOo+6rj +rMuuwLC4sra0rra0uL+60LzfvwDBHcLdxLXGhchYyi7MCs3lz7rRmtOA1WPXR9kq2xPc/97s4M/iveSn +5o3obupT7ELuLPAM8fLz0PW396H5f/tZ/T3//wAAAAEAAwALABYAJQA3AE0AZQCBAJ8AwQDlAQsBNQFh +AZABwQH1AisCZAKfAtwDHANfA6MD6gQ0BH8EzQT1BR0FcAXEBhsGdAbPBy0HXAeMB+4IUgi4CSAJVAmK +CfYKZArVC0cLgQu8DDIMqw0mDaIOIQ6hDyQPqRAvELgQ/RFDEc8SXRLuE4AUFRSrFUMV3RZ5FxcXthhY +GPwZoRpIGvEbnBxJHPgdqB5bHw8fxSB9ITch8iKwJDAk8yW3Jn4nRigQKNwpqSp5K0osHCzxLccuoC95 +MFUxMzISMvMz1TS5NaA2hzdxOFw5STo4Oyg8Gj4DPvs/9EDuQepD6ETpRexG8Uf3SP9LFEwhTTBOQE9S +UGZSklOrVMVV4Vb/WB5ZP1phW4Vcq13SXvthUmJ/Y69k4GYSZ0dofGm0au1tZG6ib+FxInJlc6l073Y2 +d396FXtjfLJ+A39VgKmB/4NWhK+GCYjCiiGLgYzjjkePrJESknuT5Ja8mCuZm5sMnH+d9J9qoOGiWqPV +pVGmz6eOqE6pzqtRrNSuWq/gsWmy8rR+tgu5Kbq6vE294b93wQ7Cp8RBxd3He8kZyrrLisxbzf/Po9FK +0vHUm9ZF1/HZn9tO3Cbc/96x4GTiGePQ5YjnQegf6Pzquex27jbv9/G583z0X/VC9wj40Pqa/GX+Mf// +AAAAAQADAAsAJQA3AE0AZQCBAJ8AwQELATUBYQGQAcEB9QIrAmQCnwLcAxwDXwOjA+oENAR/BM0FHQVw +BcQGGwZ0Bs8HLQeMB+4IUgi4CSAJign2CmQK1QtHC7wMMgyrDSYNog4hDqEPJA+pEC8QuBFDEl0S7hOA +FBUUqxVDFnkXFxe2GFgY/BpIGvEbnBxJHPgdqB8PH8UgfSE3IfIjbyQwJPMltydGKBAo3Cp5K0osHC3H +LqAveTEzMhIy8zS5NaA2hzhcOUk6ODwaPQ4+Az/0QO5C6EPoROlG8Uf3SglLFEwhTkBPUlF7UpJUxVXh +Vv9ZP1phXKtd0mAlYVJjr2TgZhJofGm0au1tZG6ib+FxInJldO92Nnd/eMl6FXyyfgN/VYCpgf+Er4YJ +h2WIwoohi4GOR4+skRKSe5PklVCWvJgrmZubDJx/nfSfaqDholqj1aVRps+oTqnOq1Gs1K2Xrlqv4LFp +svK0frYLt5m5Kbnxurq8Tb3hv3fBDsHawqfEQcUPxd3He8hKyRnKusuKzFvN/87Rz6PQdtFK0vHTxtSb +1kXXG9fx2MjZn9tO3Cbc/93Y3rHfiuBk4hni9ePQ5KzliOZk50HoH+j86drqueuX7HbtVu427xbv9/DX +8bnymvN89F/1QvYl9wj37PjQ+bX6mvt//GX9S/4x//8AAGRlc2MAAAAAAAAACkNvbG9yIExDRAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAABIAAAAcAEMAbwBsAG8AcgAgAEwAQwBE +AABtbW9kAAAAAAAABhAAAJxOAAAAAL5zkQAAAAAAAAAAAAAAAAAAAAAAdGV4dAAAAABDb3B5cmlnaHQg +QXBwbGUgQ29tcHV0ZXIsIEluYy4sIDIwMDUAAAAAA</bytes> + </object> + </object> + </object> + </object> + <reference key="NSColor" ref="30384615"/> + </object> + <reference key="NSAlternateImage" ref="619763889"/> + <int key="NSPeriodicDelay">400</int> + <int key="NSPeriodicInterval">75</int> + </object> + <object class="NSButtonCell" id="251439646"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">0</int> + <string key="NSContents">Call task_set_exception_port with null exception port in child process before exec</string> + <reference key="NSSupport" ref="933596199"/> + <reference key="NSControlView" ref="7590393"/> + <int key="NSTag">2</int> + <int key="NSButtonFlags">1211912703</int> + <int key="NSButtonFlags2">0</int> + <object class="NSImage" key="NSNormalImage"> + <int key="NSImageFlags">549453824</int> + <string key="NSSize">{18, 18}</string> + <object class="NSMutableArray" key="NSReps"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray"> + <bool key="EncodedWithXMLCoder">YES</bool> + <integer value="0"/> + <object class="NSBitmapImageRep"> + <object class="NSData" key="NSTIFFRepresentation"> + <bytes key="NS.bytes">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 +ArBYWVogAAAAAAAANWMAAJ/rAAAZsVhZWiAAAAAAAAAlzQAAE9UAALbFWFlaIAAAAAAAAPPYAAEAAAAB +FghzZjMyAAAAAAABC7cAAAWW///zVwAABykAAP3X///7t////aYAAAPaAADA9mN1cnYAAAAAAAAAAQHN +AABjdXJ2AAAAAAAAAAEBzQAAY3VydgAAAAAAAAABAc0AAHZjZ3QAAAAAAAAAAAADAQAAAgAAAioENAYA +B9AJlAtRDQwOshBOEekTgxUVFqMYMRm9GzwcvR4+H7chLiKnJBoliCb3KGIpyCswLJMt9C9WMLIyDjNn +NL02FTdoOLs6ETtdPKw9+D9DQJBB1kMeRGZFq0bySDRJeEq6S/tNPk59T75Q+lI2U25Uo1XZVwlYOlln +WpZbwFzsXhdfQWBrYZRiv2PoZRNmPWdqaJhpyGr5bC1tZW6hb+BxLXJ+c9d1OHafeA55gnr5fHR98H9t +gOuCY4PYhUqGsIgSiWyKuIwBjTqObY+VkLORyJLUk9eU05XFlrWXlZh2mUuaG5rsm6ucbJ0qndqei588 +n9+ghKEqoceiYqL/o5qkLqTDpVml66Z7pwunnKgpqLWpQqnQqlqq5Ktvq/qsg60MrZauIa6qrzOvvbBH +sNGxXLHnsnKy/7OMtBm0p7U3tci2Wbbrt4C4FrituUa54rqAux27wbxlvQq9rr5Tvvi/ncBAwOXBisIu +wtPDeMQdxMLFaMYPxrXHXMgEyKzJVcn/yqnLVcwBzK7NXc4Mzr3PcNAk0NnRkdJK0wTTw9SC1UTWCtbR +153Ybdk+2hfa8dvM3KfdhN5g3zzgGuD34dbiteOV5HblWeY85yHoCOjw6drqxuu27Kbtm+6R74zwivGM +8pPzn/Sw9cj25/gP+T76e/u//Rr+hP//AAABpANzBRoGsggnCZsLFQx+Dd4PRRCiEf8TYxS0FgoXXRiu +GgQbTRyZHekfMCB8IcIjCSRSJZUm3SgdKWAqpCvjLSYuZC+lMOIyIDNgNJs12TcTOFA5izrEO/49NT5w +P6dA30IWQ01Eg0W4Ru9IIElVSoZLt0zmThVPRFBwUZ5SylP5VSRWUVd+WKtZ2lsIXDhdaV6bX89hBWI8 +Y3dktGX0ZzhohGnVayxsiW3sb1Vww3I0c6p1I3aeeBl5k3sMfIR99X9kgM6CLYOJhNyGJodriKGJ1Ir5 +jBuNL45Aj0WQSJE8kjKTGJP+lNyVspaKl1OYHZjlmaSaZJshm9ecj51EnfSepJ9Vn/+gqqFWofyio6NL +o/CklKU4pdymfacfp8KoYqkDqaWqRarlq4asJ6zHrWiuCq6rr02v77CSsTax27KAsyezzrR3tSG1zLZ5 +tye32LiKuT659rqwu2q8J7zkvaK+YL8ev93AnMFcwhzC3MOdxF7FIMXixqXHaMgryPDJtcp6y0DMB8zO +zZbOX88oz/PQvtGJ0lbTI9Px1MDVkNZi1zTYB9jb2bDah9te3DjdEt3t3sjfpOB/4VviN+MT4/DkzeWq +5ojnZuhG6SXqBuro68rsre2S7nfvXvBH8TDyHPMI8/j06fXc9tL3yvjF+cL6w/vG/ND92v7s//8AAAMJ +BboIZwrCDSsPghG8E/IWHxg5GkgcVB5VIEQiMyQTJeknuimHK00tCy7AMHEyHDO/NV829ziKOhs7pj0s +PrBALEGmQx9EkkYCR3JI3EpCS6pND05vT89RLVKKU+dVP1aYV+9ZRVqdW/NdSV6hX+thM2JzY61k42YS +Z0FoZ2mOaq5rz2zsbglvI3A9cVRybHOEdJx1tHbOd+d5A3ofez98Yn2Lfrl/8IEqgmyDsoT8hkuHnYjw +ikSLmYzsjj+PjJDWkh2TW5SXlceW85gWmTOaSJtVnFqdWp5Pn0SgKaEQoeuiwqOYpGClKaXtpqmnZqgf +qNKph6o5quWrk6xArOatja41rtevebAcsLyxWbH3spWzL7PJtGO0/LWTtiq2wrdWt+q4f7kUuaW6OLrL +u1277Lx9vQ69nr4tvry/TL/bwGjA98GGwhPCocMvw77ESsTYxWXF9MZ/xwzHmcgkyKXJJ8mpyizKpMsc +y5XMDsyFzPjNa83fzlPOxc81z6fQGNCK0PvRbNHe0lDSw9M206vUINSV1QvVhtYA1nzW+Nd61//YhNkK +2ZnaL9rG213cCty63WveI97d35fgUuEO4crih+NE5ALkw+WD5kXnCufP6JbpYOor6vrry+yd7XbuUe8w +8BXw+/Ht8uDz4PTl9fj3E/hE+X363vxa/gH//wAAbmRpbgAAAAAAAAMGAACogAAAUwAAADRAAACqQAAA +JpcAABLbAABQQAAAVEAAAj99AAI1egACxUsAAwB4AAIAAAADAAsAGQAsAEUAYwCHALEA4QEWAVEBkgHZ +AiYCeQLSAzEDlwQDBHYE7wVvBfUGgwcXB7IIUwj8CawKYgsgC+QMrw2BDloPORAfEQ0SBRMGFBEVJBZA +F2MYjhm/GvYcMh1xHrMf9SE1ImwjnSTJJfAnFig7KWMqjivBLP4uSC+jMRMymzRBNgo3+joWPGY+8EG8 +RNhIQEvvT95UCFhkXOxhlWZYaylv/XTKeYN+GoKOhxGLqJBOlP6Ztp5voyan1Kx0sQG1c7nGvfHB9cX7 +ygbOFNIi1izaMN4p4hTl7emv7Vbw3vRC93z6iP1g//8AAAAEAA8AIgA9AF8AiQC7APQBNAF8AcwCIgKB +AuYDUwPHBEIExAVOBd4GdgcUB7oIZgkaCdQKlQteDC0NAw3gDsQPrxCiEZwSnxOpFLsV0xbyGBcZQRpw +G6Mc2R4RH0oggyG3IuckEyU8JmQniyiyKd0rDCxBLYAuyTAhMYkzBjSbNkw4HToSPDA+fED8Q7FGmUmx +TPdQaFQAV7tbll+KY5RnrmvTb/p0H3g6fEOAMoQXiASL+I/yk/KX+JwBoA2kHKgrrDuwS7RYuGK8aMBo +xGvId8yI0J/UuNjS3OvhAOUQ6RftE/EC9OH4rfxi//8AAAABAAYADQAXACUANQBIAF8AeQCWALcA3AEE +ATABYQGVAc4CDAJOApUC4QMyA4gD5QRGBK4FHAWPBgkGigcQB54IMQjMCW0KFArDC3cMMgzzDbsOiA9a +EDIRFhIIEwgUFRUvFlYXhxjDGgkbVhyqHgMfYCC8IhIjYSSrJfMnOSiBKc0rHyx7LeQvXTDrMpE0VTY7 +OEg6gjzuP5NCckWCSMJMMk/QU5xXk1u1X/9kcGkGbb5ylneLfJqByoeDjcyUf5t4oo2plLBetru8eMFr +xirK7c+v1GnZFd2r4iXme+ql7pryUvXD+OT7qv4M//8AAGRlc2MAAAAAAAAAFUhQIExQMzA2NSBDYWxp +YnJhdGVkAAAAAAAAAAAVAEgAUAAgAEwAUAAzADAANgA1ACAAQwBhAGwAaQBiAHIAYQB0AGUAZAAAAAAV +SFAgTFAzMDY1IENhbGlicmF0ZWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAHRleHQAAAAAQ29weXJpZ2h0IEFwcGxlIEluYy4sIDIwMDgAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAABtbW9kAAAAAAAAIvAAACaQAAAAAMJtVwAAAAAAAAAAAAAAAAAAAAAAA</bytes> + </object> + </object> + </object> + </object> + <reference key="NSColor" ref="30384615"/> + </object> + <reference key="NSAlternateImage" ref="619763889"/> + <int key="NSPeriodicDelay">400</int> + <int key="NSPeriodicInterval">75</int> + </object> + </object> + <string key="NSCellSize">{565, 22}</string> + <string key="NSIntercellSpacing">{4, 2}</string> + <int key="NSMatrixFlags">1151868928</int> + <string key="NSCellClass">NSActionCell</string> + <object class="NSButtonCell" key="NSProtoCell" id="773902463"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">0</int> + <string key="NSContents">Radio</string> + <reference key="NSSupport" ref="933596199"/> + <int key="NSButtonFlags">1211912703</int> + <int key="NSButtonFlags2">0</int> + <object class="NSImage" key="NSNormalImage"> + <int key="NSImageFlags">549453824</int> + <string key="NSSize">{18, 18}</string> + <object class="NSMutableArray" key="NSReps"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray"> + <bool key="EncodedWithXMLCoder">YES</bool> + <integer value="0"/> + <object class="NSBitmapImageRep"> + <object class="NSData" key="NSTIFFRepresentation"> + <bytes key="NS.bytes">TU0AKgAABRgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAMAAAADAAAAAwAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAADwRERGLJycnySsrK/A1NTXw +IyMjyRwcHIsJCQk8AAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFFRUVdVBQUOCoqKj/ +29vb//n5+f/6+vr/2tra/6qqqv9UVFTgHx8fdQAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUZGRl5 +dXV198PDw//8/Pz////////////////////////////U1NT/fHx89yUlJXkAAAAFAAAAAAAAAAAAAAAA +AAAAAxEREUZqamrmtbW1/+3t7f/+/v7//v7+//7+/v/9/f3//f39//39/f/39/f/xMTE/3d3d+YZGRlG +AAAAAwAAAAAAAAAAAAAACkJCQqGtra3/xsbG/+vr6//y8vL/9fX1//X19f/z8/P/9fX1//Ly8v/u7u7/ +0tLS/6+vr/9KSkqhAAAACgAAAAAAAAAAAAAAF3h4eN2/v7//z8/P/93d3f/q6ur/7+/v/+/v7//w8PD/ +7e3t/+3t7f/i4uL/zs7O/8XFxf98fHzdAAAAFwAAAAAAAAADAAAAJKSkpPjOzs7/2dnZ/+Dg4P/i4uL/ +5eXl/+bm5v/n5+f/5eXl/+Li4v/e3t7/2tra/9DQ0P+srKz4AAAAJAAAAAMAAAADAAAALrCwsPrW1tb/ +3t7e/+Tk5P/p6en/6+vr/+zs7P/p6en/6+vr/+fn5//k5OT/4ODg/9nZ2f+zs7P6AAAALgAAAAMAAAAD +AAAALp2dnezg4OD/5eXl/+rq6v/u7u7/8PDw//Dw8P/x8fH/8PDw/+7u7v/q6ur/5ubm/+Hh4f+ZmZns +AAAALgAAAAMAAAADAAAAJG5ubs/l5eX/6enp/+/v7//y8vL/9vb2//r6+v/5+fn/9/f3//b29v/x8fH/ +6+vr/+Tk5P9ra2vPAAAAJAAAAAMAAAAAAAAAFy4uLpPCwsL67Ozs//Pz8//5+fn//v7+//7+/v/+/v7/ +/v7+//v7+//19fX/8PDw/8LCwvosLCyTAAAAFwAAAAAAAAAAAAAACgAAAENfX1/S5OTk/vn5+f/+/v7/ +///////////////////////////8/Pz/5ubm/l9fX9IAAABDAAAACgAAAAAAAAAAAAAAAwAAABcAAABl +YmJi3NLS0v3////////////////////////////////V1dX9ZGRk3AAAAGUAAAAXAAAAAwAAAAAAAAAA +AAAAAAAAAAUAAAAfAAAAZTMzM8KAgIDwv7+//O3t7f/t7e3/v7+//ICAgPAzMzPCAAAAZQAAAB8AAAAF +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAFwAAAEMAAAB3AAAAnwAAALMAAACzAAAAnwAAAHcAAABD +AAAAFwAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAoAAAAXAAAAJAAAAC4AAAAu +AAAAJAAAABcAAAAKAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAwAAAAMAAAADAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQEAAAMAAAABABIAAAEB +AAMAAAABABIAAAECAAMAAAAEAAAFugEDAAMAAAABAAEAAAEGAAMAAAABAAIAAAERAAQAAAABAAAACAES +AAMAAAABAAEAAAEVAAMAAAABAAQAAAEWAAMAAAABABIAAAEXAAQAAAABAAAFEAEcAAMAAAABAAEAAAFS +AAMAAAABAAEAAAFTAAMAAAAEAAAFwgAAAAAACAAIAAgACAABAAEAAQABA</bytes> + </object> + </object> + </object> + </object> + <reference key="NSColor" ref="30384615"/> + </object> + <reference key="NSAlternateImage" ref="619763889"/> + <int key="NSPeriodicDelay">400</int> + <int key="NSPeriodicInterval">75</int> + </object> + <reference key="NSSelectedCell" ref="808388382"/> + <reference key="NSBackgroundColor" ref="349124561"/> + <reference key="NSCellBackgroundColor" ref="195671423"/> + <reference key="NSFont" ref="933596199"/> + </object> + <object class="NSMatrix" id="1050951576"> + <reference key="NSNextResponder" ref="594333702"/> + <int key="NSvFlags">268</int> + <string key="NSFrame">{{20, 104}, {565, 38}}</string> + <reference key="NSSuperview" ref="594333702"/> + <bool key="NSEnabled">YES</bool> + <int key="NSNumRows">2</int> + <int key="NSNumCols">1</int> + <object class="NSMutableArray" key="NSCells"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSButtonCell" id="943458284"> + <int key="NSCellFlags">-2080244224</int> + <int key="NSCellFlags2">0</int> + <string key="NSContents">fork()</string> + <reference key="NSSupport" ref="933596199"/> + <reference key="NSControlView" ref="1050951576"/> + <int key="NSTag">3</int> + <int key="NSButtonFlags">1211912703</int> + <int key="NSButtonFlags2">0</int> + <reference key="NSAlternateImage" ref="619763889"/> + <string key="NSAlternateContents"/> + <string key="NSKeyEquivalent"/> + <int key="NSPeriodicDelay">200</int> + <int key="NSPeriodicInterval">25</int> + </object> + <object class="NSButtonCell" id="69061500"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">0</int> + <string key="NSContents">vfork()</string> + <reference key="NSSupport" ref="933596199"/> + <reference key="NSControlView" ref="1050951576"/> + <int key="NSTag">4</int> + <int key="NSButtonFlags">1211912703</int> + <int key="NSButtonFlags2">0</int> + <object class="NSImage" key="NSNormalImage"> + <int key="NSImageFlags">549453824</int> + <string key="NSSize">{18, 18}</string> + <object class="NSMutableArray" key="NSReps"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray"> + <bool key="EncodedWithXMLCoder">YES</bool> + <integer value="0"/> + <object class="NSBitmapImageRep"> + <object class="NSData" key="NSTIFFRepresentation"> + <bytes key="NS.bytes">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 +AAAAAF1KAAA0kQAACCVYWVogAAAAAAAAdCAAALRgAAAjPVhZWiAAAAAAAAAlbAAAFyoAAKfDWFlaIAAA +AAAAAPNSAAEAAAABFs9zZjMyAAAAAAABDEIAAAXe///zJgAAB5IAAP2R///7ov///aMAAAPcAADAbGN1 +cnYAAAAAAAAAAQHNAABjdXJ2AAAAAAAAAAEBzQAAY3VydgAAAAAAAAABAc0AAHZjZ3QAAAAAAAAAAAAD +AQAAAQACBAUGBwkKCw0ODxASExQWFxgaGxweHyAiIyQmJygpKywtLzAxMjM1Njc4OTs8PT5AQUJDREZH +SElKS0xOT1BRUlNUVVZXWFlaW1xdXl9hYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ent8fX5/gIGCg4SF +hoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnZ6foKGio6SlpqanqKmqq6ytra6vsLGysrO0tba3uLi5uru8 +vL2+v8DBwcLDxMXGxsfIycrKy8zNzs7P0NHS0tPU1dbW19jZ2drb3Nzd3t/g4eLi4+Tl5ufo6enq6+zt +7u/w8fHy8/T19vf4+fr7/P3+/v8AAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR8gISIjJCUnKCkq +Ky0uLzAxMzQ1Njc4OTo7PD0/QEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaWltcXV5fYGFiY2RlZmdo +aWprbG1ub3BxcnN0dXZ3d3h5ent8fH1+f4CBgoKDhIWGh4iIiYqLjI2Oj5CRkpOUlJWWl5iZmpucnZ2e +n6ChoqOkpaamp6ipqqusra6vsLCxsrO0tba3uLm5uru8vb6/wMHCw8TFx8jJysvMzc7P0NDR0tPU1dbX +2Nna29ze3+Dh4uPk5ebn6err7O3u7/Hy8/T19vf5+vv8/f7/AAIDAwQFBgcICQoKCwwNDg8QERITFBUW +FxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODg5Ojs8PT4+P0BBQkNDREVGR0hJSUpLTE1O +Tk9QUVJSU1RVVVZXWFhZWltbXF1eXl9gYWFiY2RkZWZnZ2hpaWprbGxtbm5vcHFxcnNzdHV1dnd4eHl6 +ent8fH1+fn+AgYGCg4SEhYaHiImJiouMjY6Oj5CRkpOTlJWWl5iZmZqbnJ2en6ChoqOkpaanqKmqq6yt +rq+xsrO0tba3uLq7vL2+wMHDxMbHycrMzs/R0tTW19nb3d7g4uTm6Ors7vDy9Pb4+vz+/wAAbmRpbgAA +AAAAAAY2AACXGgAAVjoAAFPKAACJ3gAAJ8IAABaoAABQDQAAVDkAAiuFAAIZmQABeFEAAwEAAAIAAAAA +AAEABgANABcAIwAxAEAAUgBlAHsAkwCrAMUA4gD/AR8BPwFhAYUBqgHQAfgCIAJLAncCpQLSAwIDMwNl +A5gDzgQFBD0EdQSvBOsFKQVnBacF6AYqBm4GtQb8B0UHkgfkCDkIkAjnCT4JmAn0ClAKrQsLC2sLygwq +DIwM8Q1XDcAOKA6SDv4PbA/bEE0QxBE7EbQSMRKwEzITuRREFNAVYBXxFocXHhfAGGIZBBmsGlQa+RuU +HC4czh1yHhQeux9jIA0gvCFoIhkizyOJJEEk+SW6JnknOygFKMspkypiKzIsASzXLawuhy9gMD4xGzH8 +MtszvzSgNYY2cjdcOEw5OTorOxs8CD0EPfU+6z/nQOFB2ELUQ9VE00XcRttH5EjxSgBLCUwdTTFOUE9v +UI9Rt1LdVAVVNlZsV6VY4FohW21ct135X09goGH0Y0tkqGYFZ19oxGova5ptCG54b/BxbnLsdG119Xd/ +eQh6knwqfcV/W4D4gpSEO4Xih4CJKorYjIqOOY/jkZuTWJUOlsyYiZpSnB6d4Z+soX+jWqUvpxOo+6rj +rMuuwLC4sra0rra0uL+60LzfvwDBHcLdxLXGhchYyi7MCs3lz7rRmtOA1WPXR9kq2xPc/97s4M/iveSn +5o3obupT7ELuLPAM8fLz0PW396H5f/tZ/T3//wAAAAEAAwALABYAJQA3AE0AZQCBAJ8AwQDlAQsBNQFh +AZABwQH1AisCZAKfAtwDHANfA6MD6gQ0BH8EzQT1BR0FcAXEBhsGdAbPBy0HXAeMB+4IUgi4CSAJVAmK +CfYKZArVC0cLgQu8DDIMqw0mDaIOIQ6hDyQPqRAvELgQ/RFDEc8SXRLuE4AUFRSrFUMV3RZ5FxcXthhY +GPwZoRpIGvEbnBxJHPgdqB5bHw8fxSB9ITch8iKwJDAk8yW3Jn4nRigQKNwpqSp5K0osHCzxLccuoC95 +MFUxMzISMvMz1TS5NaA2hzdxOFw5STo4Oyg8Gj4DPvs/9EDuQepD6ETpRexG8Uf3SP9LFEwhTTBOQE9S +UGZSklOrVMVV4Vb/WB5ZP1phW4Vcq13SXvthUmJ/Y69k4GYSZ0dofGm0au1tZG6ib+FxInJlc6l073Y2 +d396FXtjfLJ+A39VgKmB/4NWhK+GCYjCiiGLgYzjjkePrJESknuT5Ja8mCuZm5sMnH+d9J9qoOGiWqPV +pVGmz6eOqE6pzqtRrNSuWq/gsWmy8rR+tgu5Kbq6vE294b93wQ7Cp8RBxd3He8kZyrrLisxbzf/Po9FK +0vHUm9ZF1/HZn9tO3Cbc/96x4GTiGePQ5YjnQegf6Pzquex27jbv9/G583z0X/VC9wj40Pqa/GX+Mf// +AAAAAQADAAsAJQA3AE0AZQCBAJ8AwQELATUBYQGQAcEB9QIrAmQCnwLcAxwDXwOjA+oENAR/BM0FHQVw +BcQGGwZ0Bs8HLQeMB+4IUgi4CSAJign2CmQK1QtHC7wMMgyrDSYNog4hDqEPJA+pEC8QuBFDEl0S7hOA +FBUUqxVDFnkXFxe2GFgY/BpIGvEbnBxJHPgdqB8PH8UgfSE3IfIjbyQwJPMltydGKBAo3Cp5K0osHC3H +LqAveTEzMhIy8zS5NaA2hzhcOUk6ODwaPQ4+Az/0QO5C6EPoROlG8Uf3SglLFEwhTkBPUlF7UpJUxVXh +Vv9ZP1phXKtd0mAlYVJjr2TgZhJofGm0au1tZG6ib+FxInJldO92Nnd/eMl6FXyyfgN/VYCpgf+Er4YJ +h2WIwoohi4GOR4+skRKSe5PklVCWvJgrmZubDJx/nfSfaqDholqj1aVRps+oTqnOq1Gs1K2Xrlqv4LFp +svK0frYLt5m5Kbnxurq8Tb3hv3fBDsHawqfEQcUPxd3He8hKyRnKusuKzFvN/87Rz6PQdtFK0vHTxtSb +1kXXG9fx2MjZn9tO3Cbc/93Y3rHfiuBk4hni9ePQ5KzliOZk50HoH+j86drqueuX7HbtVu427xbv9/DX +8bnymvN89F/1QvYl9wj37PjQ+bX6mvt//GX9S/4x//8AAGRlc2MAAAAAAAAACkNvbG9yIExDRAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAABIAAAAcAEMAbwBsAG8AcgAgAEwAQwBE +AABtbW9kAAAAAAAABhAAAJxOAAAAAL5zkQAAAAAAAAAAAAAAAAAAAAAAdGV4dAAAAABDb3B5cmlnaHQg +QXBwbGUgQ29tcHV0ZXIsIEluYy4sIDIwMDUAAAAAA</bytes> + </object> + </object> + </object> + </object> + <reference key="NSColor" ref="30384615"/> + </object> + <reference key="NSAlternateImage" ref="619763889"/> + <int key="NSPeriodicDelay">400</int> + <int key="NSPeriodicInterval">75</int> + </object> + </object> + <string key="NSCellSize">{565, 18}</string> + <string key="NSIntercellSpacing">{4, 2}</string> + <int key="NSMatrixFlags">1151868928</int> + <string key="NSCellClass">NSActionCell</string> + <object class="NSButtonCell" key="NSProtoCell" id="709643899"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">0</int> + <string key="NSContents">Radio</string> + <reference key="NSSupport" ref="933596199"/> + <int key="NSButtonFlags">1211912703</int> + <int key="NSButtonFlags2">0</int> + <object class="NSImage" key="NSNormalImage"> + <int key="NSImageFlags">549453824</int> + <string key="NSSize">{18, 18}</string> + <object class="NSMutableArray" key="NSReps"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray"> + <bool key="EncodedWithXMLCoder">YES</bool> + <integer value="0"/> + <object class="NSBitmapImageRep"> + <object class="NSData" key="NSTIFFRepresentation"> + <bytes key="NS.bytes">TU0AKgAABRgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAMAAAADAAAAAwAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAADwRERGLJycnySsrK/A1NTXw +IyMjyRwcHIsJCQk8AAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFFRUVdVBQUOCoqKj/ +29vb//n5+f/6+vr/2tra/6qqqv9UVFTgHx8fdQAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUZGRl5 +dXV198PDw//8/Pz////////////////////////////U1NT/fHx89yUlJXkAAAAFAAAAAAAAAAAAAAAA +AAAAAxEREUZqamrmtbW1/+3t7f/+/v7//v7+//7+/v/9/f3//f39//39/f/39/f/xMTE/3d3d+YZGRlG +AAAAAwAAAAAAAAAAAAAACkJCQqGtra3/xsbG/+vr6//y8vL/9fX1//X19f/z8/P/9fX1//Ly8v/u7u7/ +0tLS/6+vr/9KSkqhAAAACgAAAAAAAAAAAAAAF3h4eN2/v7//z8/P/93d3f/q6ur/7+/v/+/v7//w8PD/ +7e3t/+3t7f/i4uL/zs7O/8XFxf98fHzdAAAAFwAAAAAAAAADAAAAJKSkpPjOzs7/2dnZ/+Dg4P/i4uL/ +5eXl/+bm5v/n5+f/5eXl/+Li4v/e3t7/2tra/9DQ0P+srKz4AAAAJAAAAAMAAAADAAAALrCwsPrW1tb/ +3t7e/+Tk5P/p6en/6+vr/+zs7P/p6en/6+vr/+fn5//k5OT/4ODg/9nZ2f+zs7P6AAAALgAAAAMAAAAD +AAAALp2dnezg4OD/5eXl/+rq6v/u7u7/8PDw//Dw8P/x8fH/8PDw/+7u7v/q6ur/5ubm/+Hh4f+ZmZns +AAAALgAAAAMAAAADAAAAJG5ubs/l5eX/6enp/+/v7//y8vL/9vb2//r6+v/5+fn/9/f3//b29v/x8fH/ +6+vr/+Tk5P9ra2vPAAAAJAAAAAMAAAAAAAAAFy4uLpPCwsL67Ozs//Pz8//5+fn//v7+//7+/v/+/v7/ +/v7+//v7+//19fX/8PDw/8LCwvosLCyTAAAAFwAAAAAAAAAAAAAACgAAAENfX1/S5OTk/vn5+f/+/v7/ +///////////////////////////8/Pz/5ubm/l9fX9IAAABDAAAACgAAAAAAAAAAAAAAAwAAABcAAABl +YmJi3NLS0v3////////////////////////////////V1dX9ZGRk3AAAAGUAAAAXAAAAAwAAAAAAAAAA +AAAAAAAAAAUAAAAfAAAAZTMzM8KAgIDwv7+//O3t7f/t7e3/v7+//ICAgPAzMzPCAAAAZQAAAB8AAAAF +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAFwAAAEMAAAB3AAAAnwAAALMAAACzAAAAnwAAAHcAAABD +AAAAFwAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAoAAAAXAAAAJAAAAC4AAAAu +AAAAJAAAABcAAAAKAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAwAAAAMAAAADAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQEAAAMAAAABABIAAAEB +AAMAAAABABIAAAECAAMAAAAEAAAFugEDAAMAAAABAAEAAAEGAAMAAAABAAIAAAERAAQAAAABAAAACAES +AAMAAAABAAEAAAEVAAMAAAABAAQAAAEWAAMAAAABABIAAAEXAAQAAAABAAAFEAEcAAMAAAABAAEAAAFS +AAMAAAABAAEAAAFTAAMAAAAEAAAFwgAAAAAACAAIAAgACAABAAEAAQABA</bytes> + </object> + </object> + </object> + </object> + <reference key="NSColor" ref="30384615"/> + </object> + <reference key="NSAlternateImage" ref="619763889"/> + <int key="NSPeriodicDelay">400</int> + <int key="NSPeriodicInterval">75</int> + </object> + <reference key="NSSelectedCell" ref="943458284"/> + <reference key="NSBackgroundColor" ref="349124561"/> + <reference key="NSCellBackgroundColor" ref="195671423"/> + <reference key="NSFont" ref="933596199"/> + </object> + <object class="NSButton" id="512228208"> + <reference key="NSNextResponder" ref="594333702"/> + <int key="NSvFlags">268</int> + <string key="NSFrame">{{591, 59}, {178, 161}}</string> + <reference key="NSSuperview" ref="594333702"/> + <bool key="NSEnabled">YES</bool> + <object class="NSButtonCell" key="NSCell" id="69630975"> + <int key="NSCellFlags">67239424</int> + <int key="NSCellFlags2">134217728</int> + <string key="NSContents">Go!</string> + <object class="NSFont" key="NSSupport"> + <string key="NSName">LucidaGrande</string> + <double key="NSSize">10</double> + <int key="NSfFlags">16</int> + </object> + <reference key="NSControlView" ref="512228208"/> + <int key="NSButtonFlags">-2033434369</int> + <int key="NSButtonFlags2">130</int> + <string key="NSAlternateContents"/> + <string key="NSKeyEquivalent"/> + <int key="NSPeriodicDelay">400</int> + <int key="NSPeriodicInterval">75</int> + </object> + </object> + </object> + <string key="NSFrameSize">{787, 260}</string> + <reference key="NSSuperview"/> + </object> + <string key="NSScreenRect">{{0, 0}, {1440, 878}}</string> + <string key="NSMaxSize">{1.79769e+308, 1.79769e+308}</string> + </object> + </object> + <object class="IBObjectContainer" key="IBDocument.Objects"> + <object class="NSMutableArray" key="connectionRecords"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">performMiniaturize:</string> + <reference key="source" ref="442653439"/> + <reference key="destination" ref="683986939"/> + </object> + <int key="connectionID">37</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">arrangeInFront:</string> + <reference key="source" ref="442653439"/> + <reference key="destination" ref="164762492"/> + </object> + <int key="connectionID">39</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">print:</string> + <reference key="source" ref="442653439"/> + <reference key="destination" ref="302505815"/> + </object> + <int key="connectionID">86</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">runPageLayout:</string> + <reference key="source" ref="442653439"/> + <reference key="destination" ref="409810395"/> + </object> + <int key="connectionID">87</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">showHelp:</string> + <reference key="source" ref="442653439"/> + <reference key="destination" ref="192540884"/> + </object> + <int key="connectionID">122</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">clearRecentDocuments:</string> + <reference key="source" ref="442653439"/> + <reference key="destination" ref="85018532"/> + </object> + <int key="connectionID">127</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">terminate:</string> + <reference key="source" ref="870565383"/> + <reference key="destination" ref="887927135"/> + </object> + <int key="connectionID">139</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">orderFrontStandardAboutPanel:</string> + <reference key="source" ref="870565383"/> + <reference key="destination" ref="838552093"/> + </object> + <int key="connectionID">142</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">hideOtherApplications:</string> + <reference key="source" ref="870565383"/> + <reference key="destination" ref="216168366"/> + </object> + <int key="connectionID">146</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">hide:</string> + <reference key="source" ref="870565383"/> + <reference key="destination" ref="104472016"/> + </object> + <int key="connectionID">152</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">unhideAllApplications:</string> + <reference key="source" ref="870565383"/> + <reference key="destination" ref="667790509"/> + </object> + <int key="connectionID">153</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">cut:</string> + <reference key="source" ref="442653439"/> + <reference key="destination" ref="909447496"/> + </object> + <int key="connectionID">175</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">paste:</string> + <reference key="source" ref="442653439"/> + <reference key="destination" ref="84012734"/> + </object> + <int key="connectionID">176</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">redo:</string> + <reference key="source" ref="442653439"/> + <reference key="destination" ref="1001272176"/> + </object> + <int key="connectionID">178</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">selectAll:</string> + <reference key="source" ref="442653439"/> + <reference key="destination" ref="917620781"/> + </object> + <int key="connectionID">179</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">undo:</string> + <reference key="source" ref="442653439"/> + <reference key="destination" ref="284548410"/> + </object> + <int key="connectionID">180</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">copy:</string> + <reference key="source" ref="442653439"/> + <reference key="destination" ref="994487277"/> + </object> + <int key="connectionID">181</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">showGuessPanel:</string> + <reference key="source" ref="442653439"/> + <reference key="destination" ref="438210660"/> + </object> + <int key="connectionID">188</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">checkSpelling:</string> + <reference key="source" ref="442653439"/> + <reference key="destination" ref="102172584"/> + </object> + <int key="connectionID">190</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">toggleContinuousSpellChecking:</string> + <reference key="source" ref="442653439"/> + <reference key="destination" ref="540509341"/> + </object> + <int key="connectionID">192</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">performClose:</string> + <reference key="source" ref="442653439"/> + <reference key="destination" ref="468594275"/> + </object> + <int key="connectionID">193</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">delete:</string> + <reference key="source" ref="442653439"/> + <reference key="destination" ref="512189403"/> + </object> + <int key="connectionID">195</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">performZoom:</string> + <reference key="source" ref="442653439"/> + <reference key="destination" ref="548098734"/> + </object> + <int key="connectionID">198</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">performFindPanelAction:</string> + <reference key="source" ref="442653439"/> + <reference key="destination" ref="203238834"/> + </object> + <int key="connectionID">199</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">performFindPanelAction:</string> + <reference key="source" ref="442653439"/> + <reference key="destination" ref="861312964"/> + </object> + <int key="connectionID">200</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">performFindPanelAction:</string> + <reference key="source" ref="442653439"/> + <reference key="destination" ref="743767160"/> + </object> + <int key="connectionID">201</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">performFindPanelAction:</string> + <reference key="source" ref="442653439"/> + <reference key="destination" ref="180446588"/> + </object> + <int key="connectionID">202</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">centerSelectionInVisibleArea:</string> + <reference key="source" ref="442653439"/> + <reference key="destination" ref="731027425"/> + </object> + <int key="connectionID">203</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">pasteAsPlainText:</string> + <reference key="source" ref="442653439"/> + <reference key="destination" ref="182251545"/> + </object> + <int key="connectionID">205</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">crash:</string> + <reference key="source" ref="623097029"/> + <reference key="destination" ref="726278107"/> + </object> + <int key="connectionID">208</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">window_</string> + <reference key="source" ref="623097029"/> + <reference key="destination" ref="341270541"/> + </object> + <int key="connectionID">209</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">crash:</string> + <reference key="source" ref="623097029"/> + <reference key="destination" ref="799567279"/> + </object> + <int key="connectionID">211</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">crash:</string> + <reference key="source" ref="623097029"/> + <reference key="destination" ref="27781390"/> + </object> + <int key="connectionID">213</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">forkTestOptions_</string> + <reference key="source" ref="623097029"/> + <reference key="destination" ref="347013037"/> + </object> + <int key="connectionID">241</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">forkTestOptions:</string> + <reference key="source" ref="623097029"/> + <reference key="destination" ref="943458284"/> + </object> + <int key="connectionID">242</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">forkTestOptions:</string> + <reference key="source" ref="623097029"/> + <reference key="destination" ref="7590393"/> + </object> + <int key="connectionID">243</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">forkTestOptions:</string> + <reference key="source" ref="623097029"/> + <reference key="destination" ref="410017819"/> + </object> + <int key="connectionID">244</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">forkTestGo:</string> + <reference key="source" ref="623097029"/> + <reference key="destination" ref="512228208"/> + </object> + <int key="connectionID">250</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">forkTestOptions:</string> + <reference key="source" ref="623097029"/> + <reference key="destination" ref="1050951576"/> + </object> + <int key="connectionID">261</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">forkTestOptions:</string> + <reference key="source" ref="623097029"/> + <reference key="destination" ref="891367997"/> + </object> + <int key="connectionID">262</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">showForkTestWindow:</string> + <reference key="source" ref="623097029"/> + <reference key="destination" ref="856256540"/> + </object> + <int key="connectionID">283</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">generateReportWithoutCrash:</string> + <reference key="source" ref="623097029"/> + <reference key="destination" ref="460755987"/> + </object> + <int key="connectionID">327</int> + </object> + </object> + <object class="IBMutableOrderedSet" key="objectRecords"> + <object class="NSArray" key="orderedObjects"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBObjectRecord"> + <int key="objectID">0</int> + <reference key="object" ref="0"/> + <reference key="children" ref="925601844"/> + <nil key="parent"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-2</int> + <reference key="object" ref="870565383"/> + <reference key="parent" ref="0"/> + <string key="objectName">File's Owner</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-1</int> + <reference key="object" ref="442653439"/> + <reference key="parent" ref="0"/> + <string key="objectName">First Responder</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-3</int> + <reference key="object" ref="751079937"/> + <reference key="parent" ref="0"/> + <string key="objectName">Application</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">21</int> + <reference key="object" ref="341270541"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="814272478"/> + </object> + <reference key="parent" ref="0"/> + <string key="objectName">Window</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">2</int> + <reference key="object" ref="814272478"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="726278107"/> + <reference ref="799567279"/> + <reference ref="27781390"/> + <reference ref="856256540"/> + <reference ref="460755987"/> + </object> + <reference key="parent" ref="341270541"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">206</int> + <reference key="object" ref="726278107"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="539552922"/> + </object> + <reference key="parent" ref="814272478"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">210</int> + <reference key="object" ref="799567279"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="1010617379"/> + </object> + <reference key="parent" ref="814272478"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">212</int> + <reference key="object" ref="27781390"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="547901497"/> + </object> + <reference key="parent" ref="814272478"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">218</int> + <reference key="object" ref="856256540"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="353736234"/> + </object> + <reference key="parent" ref="814272478"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">325</int> + <reference key="object" ref="460755987"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="775425649"/> + </object> + <reference key="parent" ref="814272478"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">29</int> + <reference key="object" ref="695387251"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="764068863"/> + <reference ref="458207250"/> + <reference ref="369472335"/> + <reference ref="599772536"/> + <reference ref="542216986"/> + </object> + <reference key="parent" ref="0"/> + <string key="objectName">MainMenu</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">19</int> + <reference key="object" ref="764068863"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="200536676"/> + </object> + <reference key="parent" ref="695387251"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">24</int> + <reference key="object" ref="200536676"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="164762492"/> + <reference ref="683986939"/> + <reference ref="297002686"/> + <reference ref="548098734"/> + </object> + <reference key="parent" ref="764068863"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">5</int> + <reference key="object" ref="164762492"/> + <reference key="parent" ref="200536676"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">23</int> + <reference key="object" ref="683986939"/> + <reference key="parent" ref="200536676"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">92</int> + <reference key="object" ref="297002686"/> + <reference key="parent" ref="200536676"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">197</int> + <reference key="object" ref="548098734"/> + <reference key="parent" ref="200536676"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">56</int> + <reference key="object" ref="458207250"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="753534561"/> + </object> + <reference key="parent" ref="695387251"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">57</int> + <reference key="object" ref="753534561"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="838552093"/> + <reference ref="443649494"/> + <reference ref="826764396"/> + <reference ref="104472016"/> + <reference ref="887927135"/> + <reference ref="217746140"/> + <reference ref="881859155"/> + <reference ref="216168366"/> + <reference ref="928933982"/> + <reference ref="667790509"/> + <reference ref="758254482"/> + </object> + <reference key="parent" ref="458207250"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">58</int> + <reference key="object" ref="838552093"/> + <reference key="parent" ref="753534561"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">129</int> + <reference key="object" ref="443649494"/> + <reference key="parent" ref="753534561"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">131</int> + <reference key="object" ref="826764396"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="276709607"/> + </object> + <reference key="parent" ref="753534561"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">130</int> + <reference key="object" ref="276709607"/> + <reference key="parent" ref="826764396"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">134</int> + <reference key="object" ref="104472016"/> + <reference key="parent" ref="753534561"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">136</int> + <reference key="object" ref="887927135"/> + <reference key="parent" ref="753534561"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">143</int> + <reference key="object" ref="217746140"/> + <reference key="parent" ref="753534561"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">144</int> + <reference key="object" ref="881859155"/> + <reference key="parent" ref="753534561"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">145</int> + <reference key="object" ref="216168366"/> + <reference key="parent" ref="753534561"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">149</int> + <reference key="object" ref="928933982"/> + <reference key="parent" ref="753534561"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">150</int> + <reference key="object" ref="667790509"/> + <reference key="parent" ref="753534561"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">196</int> + <reference key="object" ref="758254482"/> + <reference key="parent" ref="753534561"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">83</int> + <reference key="object" ref="369472335"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="902982238"/> + </object> + <reference key="parent" ref="695387251"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">81</int> + <reference key="object" ref="902982238"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="367379562"/> + <reference ref="468594275"/> + <reference ref="232609393"/> + <reference ref="479945444"/> + <reference ref="409810395"/> + <reference ref="302505815"/> + <reference ref="154948703"/> + <reference ref="976375553"/> + <reference ref="660391032"/> + <reference ref="885975128"/> + <reference ref="84883275"/> + </object> + <reference key="parent" ref="369472335"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">72</int> + <reference key="object" ref="367379562"/> + <reference key="parent" ref="902982238"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">73</int> + <reference key="object" ref="468594275"/> + <reference key="parent" ref="902982238"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">74</int> + <reference key="object" ref="232609393"/> + <reference key="parent" ref="902982238"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">75</int> + <reference key="object" ref="479945444"/> + <reference key="parent" ref="902982238"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">77</int> + <reference key="object" ref="409810395"/> + <reference key="parent" ref="902982238"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">78</int> + <reference key="object" ref="302505815"/> + <reference key="parent" ref="902982238"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">79</int> + <reference key="object" ref="154948703"/> + <reference key="parent" ref="902982238"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">80</int> + <reference key="object" ref="976375553"/> + <reference key="parent" ref="902982238"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">82</int> + <reference key="object" ref="660391032"/> + <reference key="parent" ref="902982238"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">112</int> + <reference key="object" ref="885975128"/> + <reference key="parent" ref="902982238"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">124</int> + <reference key="object" ref="84883275"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="693280130"/> + </object> + <reference key="parent" ref="902982238"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">125</int> + <reference key="object" ref="693280130"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="85018532"/> + </object> + <reference key="parent" ref="84883275"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">126</int> + <reference key="object" ref="85018532"/> + <reference key="parent" ref="693280130"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">103</int> + <reference key="object" ref="599772536"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="1066958924"/> + </object> + <reference key="parent" ref="695387251"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">106</int> + <reference key="object" ref="1066958924"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="192540884"/> + </object> + <reference key="parent" ref="599772536"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">111</int> + <reference key="object" ref="192540884"/> + <reference key="parent" ref="1066958924"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">163</int> + <reference key="object" ref="542216986"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="1053284541"/> + </object> + <reference key="parent" ref="695387251"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">169</int> + <reference key="object" ref="1053284541"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="209744238"/> + <reference ref="994487277"/> + <reference ref="284548410"/> + <reference ref="909447496"/> + <reference ref="512189403"/> + <reference ref="153501847"/> + <reference ref="84012734"/> + <reference ref="917620781"/> + <reference ref="1001272176"/> + <reference ref="431895313"/> + <reference ref="61602259"/> + <reference ref="182251545"/> + </object> + <reference key="parent" ref="542216986"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">156</int> + <reference key="object" ref="209744238"/> + <reference key="parent" ref="1053284541"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">157</int> + <reference key="object" ref="994487277"/> + <reference key="parent" ref="1053284541"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">158</int> + <reference key="object" ref="284548410"/> + <reference key="parent" ref="1053284541"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">160</int> + <reference key="object" ref="909447496"/> + <reference key="parent" ref="1053284541"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">164</int> + <reference key="object" ref="512189403"/> + <reference key="parent" ref="1053284541"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">168</int> + <reference key="object" ref="153501847"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="333484665"/> + </object> + <reference key="parent" ref="1053284541"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">159</int> + <reference key="object" ref="333484665"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="203238834"/> + <reference ref="731027425"/> + <reference ref="180446588"/> + <reference ref="743767160"/> + <reference ref="861312964"/> + </object> + <reference key="parent" ref="153501847"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">154</int> + <reference key="object" ref="203238834"/> + <reference key="parent" ref="333484665"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">155</int> + <reference key="object" ref="731027425"/> + <reference key="parent" ref="333484665"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">161</int> + <reference key="object" ref="180446588"/> + <reference key="parent" ref="333484665"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">162</int> + <reference key="object" ref="743767160"/> + <reference key="parent" ref="333484665"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">167</int> + <reference key="object" ref="861312964"/> + <reference key="parent" ref="333484665"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">171</int> + <reference key="object" ref="84012734"/> + <reference key="parent" ref="1053284541"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">172</int> + <reference key="object" ref="917620781"/> + <reference key="parent" ref="1053284541"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">173</int> + <reference key="object" ref="1001272176"/> + <reference key="parent" ref="1053284541"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">174</int> + <reference key="object" ref="431895313"/> + <reference key="parent" ref="1053284541"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">184</int> + <reference key="object" ref="61602259"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="8174285"/> + </object> + <reference key="parent" ref="1053284541"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">185</int> + <reference key="object" ref="8174285"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="438210660"/> + <reference ref="102172584"/> + <reference ref="540509341"/> + </object> + <reference key="parent" ref="61602259"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">187</int> + <reference key="object" ref="438210660"/> + <reference key="parent" ref="8174285"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">189</int> + <reference key="object" ref="102172584"/> + <reference key="parent" ref="8174285"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">191</int> + <reference key="object" ref="540509341"/> + <reference key="parent" ref="8174285"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">204</int> + <reference key="object" ref="182251545"/> + <reference key="parent" ref="1053284541"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">207</int> + <reference key="object" ref="623097029"/> + <reference key="parent" ref="0"/> + <string key="objectName">Controller</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">220</int> + <reference key="object" ref="347013037"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="594333702"/> + </object> + <reference key="parent" ref="0"/> + <string key="objectName">Window (Window)</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">221</int> + <reference key="object" ref="594333702"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="891367997"/> + <reference ref="7590393"/> + <reference ref="1050951576"/> + <reference ref="512228208"/> + </object> + <reference key="parent" ref="347013037"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">226</int> + <reference key="object" ref="891367997"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="410017819"/> + <reference ref="904578786"/> + <reference ref="971445237"/> + <reference ref="1072218638"/> + </object> + <reference key="parent" ref="594333702"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">227</int> + <reference key="object" ref="410017819"/> + <reference key="parent" ref="891367997"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">228</int> + <reference key="object" ref="904578786"/> + <reference key="parent" ref="891367997"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">272</int> + <reference key="object" ref="971445237"/> + <reference key="parent" ref="891367997"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">232</int> + <reference key="object" ref="7590393"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="808388382"/> + <reference ref="378736460"/> + <reference ref="251439646"/> + <reference ref="773902463"/> + </object> + <reference key="parent" ref="594333702"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">233</int> + <reference key="object" ref="808388382"/> + <reference key="parent" ref="7590393"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">234</int> + <reference key="object" ref="378736460"/> + <reference key="parent" ref="7590393"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">236</int> + <reference key="object" ref="251439646"/> + <reference key="parent" ref="7590393"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">237</int> + <reference key="object" ref="1050951576"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="943458284"/> + <reference ref="69061500"/> + <reference ref="709643899"/> + </object> + <reference key="parent" ref="594333702"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">238</int> + <reference key="object" ref="943458284"/> + <reference key="parent" ref="1050951576"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">239</int> + <reference key="object" ref="69061500"/> + <reference key="parent" ref="1050951576"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">248</int> + <reference key="object" ref="512228208"/> + <object class="NSMutableArray" key="children"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference ref="69630975"/> + </object> + <reference key="parent" ref="594333702"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">329</int> + <reference key="object" ref="539552922"/> + <reference key="parent" ref="726278107"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">330</int> + <reference key="object" ref="1010617379"/> + <reference key="parent" ref="799567279"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">331</int> + <reference key="object" ref="547901497"/> + <reference key="parent" ref="27781390"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">332</int> + <reference key="object" ref="353736234"/> + <reference key="parent" ref="856256540"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">333</int> + <reference key="object" ref="775425649"/> + <reference key="parent" ref="460755987"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">334</int> + <reference key="object" ref="69630975"/> + <reference key="parent" ref="512228208"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">335</int> + <reference key="object" ref="1072218638"/> + <reference key="parent" ref="891367997"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">336</int> + <reference key="object" ref="773902463"/> + <reference key="parent" ref="7590393"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">337</int> + <reference key="object" ref="709643899"/> + <reference key="parent" ref="1050951576"/> + </object> + </object> + </object> + <object class="NSMutableDictionary" key="flattenedProperties"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>-3.IBPluginDependency</string> + <string>-3.ImportedFromIB2</string> + <string>103.IBPluginDependency</string> + <string>103.ImportedFromIB2</string> + <string>106.IBPluginDependency</string> + <string>106.ImportedFromIB2</string> + <string>111.IBPluginDependency</string> + <string>111.ImportedFromIB2</string> + <string>112.IBPluginDependency</string> + <string>112.ImportedFromIB2</string> + <string>124.IBPluginDependency</string> + <string>124.ImportedFromIB2</string> + <string>125.IBPluginDependency</string> + <string>125.ImportedFromIB2</string> + <string>126.IBPluginDependency</string> + <string>126.ImportedFromIB2</string> + <string>129.IBPluginDependency</string> + <string>129.ImportedFromIB2</string> + <string>130.IBPluginDependency</string> + <string>130.ImportedFromIB2</string> + <string>131.IBPluginDependency</string> + <string>131.ImportedFromIB2</string> + <string>134.IBPluginDependency</string> + <string>134.ImportedFromIB2</string> + <string>136.IBPluginDependency</string> + <string>136.ImportedFromIB2</string> + <string>143.IBPluginDependency</string> + <string>143.ImportedFromIB2</string> + <string>144.IBPluginDependency</string> + <string>144.ImportedFromIB2</string> + <string>145.IBPluginDependency</string> + <string>145.ImportedFromIB2</string> + <string>149.IBPluginDependency</string> + <string>149.ImportedFromIB2</string> + <string>150.IBPluginDependency</string> + <string>150.ImportedFromIB2</string> + <string>154.IBPluginDependency</string> + <string>154.ImportedFromIB2</string> + <string>155.IBPluginDependency</string> + <string>155.ImportedFromIB2</string> + <string>156.IBPluginDependency</string> + <string>156.ImportedFromIB2</string> + <string>157.IBPluginDependency</string> + <string>157.ImportedFromIB2</string> + <string>158.IBPluginDependency</string> + <string>158.ImportedFromIB2</string> + <string>159.IBPluginDependency</string> + <string>159.ImportedFromIB2</string> + <string>160.IBPluginDependency</string> + <string>160.ImportedFromIB2</string> + <string>161.IBPluginDependency</string> + <string>161.ImportedFromIB2</string> + <string>162.IBPluginDependency</string> + <string>162.ImportedFromIB2</string> + <string>163.IBPluginDependency</string> + <string>163.ImportedFromIB2</string> + <string>164.IBPluginDependency</string> + <string>164.ImportedFromIB2</string> + <string>167.IBPluginDependency</string> + <string>167.ImportedFromIB2</string> + <string>168.IBPluginDependency</string> + <string>168.ImportedFromIB2</string> + <string>169.IBPluginDependency</string> + <string>169.ImportedFromIB2</string> + <string>171.IBPluginDependency</string> + <string>171.ImportedFromIB2</string> + <string>172.IBPluginDependency</string> + <string>172.ImportedFromIB2</string> + <string>173.IBPluginDependency</string> + <string>173.ImportedFromIB2</string> + <string>174.IBPluginDependency</string> + <string>174.ImportedFromIB2</string> + <string>184.IBPluginDependency</string> + <string>184.ImportedFromIB2</string> + <string>185.IBPluginDependency</string> + <string>185.ImportedFromIB2</string> + <string>187.IBPluginDependency</string> + <string>187.ImportedFromIB2</string> + <string>189.IBPluginDependency</string> + <string>189.ImportedFromIB2</string> + <string>19.IBPluginDependency</string> + <string>19.ImportedFromIB2</string> + <string>191.IBPluginDependency</string> + <string>191.ImportedFromIB2</string> + <string>196.IBPluginDependency</string> + <string>196.ImportedFromIB2</string> + <string>197.IBPluginDependency</string> + <string>197.ImportedFromIB2</string> + <string>2.IBPluginDependency</string> + <string>2.ImportedFromIB2</string> + <string>204.IBPluginDependency</string> + <string>204.ImportedFromIB2</string> + <string>206.IBPluginDependency</string> + <string>206.ImportedFromIB2</string> + <string>207.ImportedFromIB2</string> + <string>21.IBEditorWindowLastContentRect</string> + <string>21.IBPluginDependency</string> + <string>21.IBWindowTemplateEditedContentRect</string> + <string>21.ImportedFromIB2</string> + <string>21.windowTemplate.hasMinSize</string> + <string>21.windowTemplate.minSize</string> + <string>210.IBPluginDependency</string> + <string>210.ImportedFromIB2</string> + <string>212.IBPluginDependency</string> + <string>212.ImportedFromIB2</string> + <string>218.IBPluginDependency</string> + <string>218.ImportedFromIB2</string> + <string>220.IBEditorWindowLastContentRect</string> + <string>220.IBPluginDependency</string> + <string>220.IBWindowTemplateEditedContentRect</string> + <string>220.ImportedFromIB2</string> + <string>221.IBPluginDependency</string> + <string>221.ImportedFromIB2</string> + <string>226.IBPluginDependency</string> + <string>226.ImportedFromIB2</string> + <string>227.IBPluginDependency</string> + <string>227.ImportedFromIB2</string> + <string>228.IBPluginDependency</string> + <string>228.ImportedFromIB2</string> + <string>23.IBPluginDependency</string> + <string>23.ImportedFromIB2</string> + <string>232.IBPluginDependency</string> + <string>232.ImportedFromIB2</string> + <string>233.IBPluginDependency</string> + <string>233.ImportedFromIB2</string> + <string>234.IBPluginDependency</string> + <string>234.ImportedFromIB2</string> + <string>236.IBPluginDependency</string> + <string>236.ImportedFromIB2</string> + <string>237.IBPluginDependency</string> + <string>237.ImportedFromIB2</string> + <string>238.IBPluginDependency</string> + <string>238.ImportedFromIB2</string> + <string>239.IBPluginDependency</string> + <string>239.ImportedFromIB2</string> + <string>24.IBPluginDependency</string> + <string>24.ImportedFromIB2</string> + <string>248.IBPluginDependency</string> + <string>248.ImportedFromIB2</string> + <string>272.IBPluginDependency</string> + <string>272.ImportedFromIB2</string> + <string>29.IBEditorWindowLastContentRect</string> + <string>29.IBPluginDependency</string> + <string>29.ImportedFromIB2</string> + <string>325.IBPluginDependency</string> + <string>325.ImportedFromIB2</string> + <string>329.IBPluginDependency</string> + <string>330.IBPluginDependency</string> + <string>331.IBPluginDependency</string> + <string>332.IBPluginDependency</string> + <string>333.IBPluginDependency</string> + <string>334.IBPluginDependency</string> + <string>335.IBPluginDependency</string> + <string>336.IBPluginDependency</string> + <string>337.IBPluginDependency</string> + <string>5.IBPluginDependency</string> + <string>5.ImportedFromIB2</string> + <string>56.IBPluginDependency</string> + <string>56.ImportedFromIB2</string> + <string>57.IBPluginDependency</string> + <string>57.ImportedFromIB2</string> + <string>58.IBPluginDependency</string> + <string>58.ImportedFromIB2</string> + <string>72.IBPluginDependency</string> + <string>72.ImportedFromIB2</string> + <string>73.IBPluginDependency</string> + <string>73.ImportedFromIB2</string> + <string>74.IBPluginDependency</string> + <string>74.ImportedFromIB2</string> + <string>75.IBPluginDependency</string> + <string>75.ImportedFromIB2</string> + <string>77.IBPluginDependency</string> + <string>77.ImportedFromIB2</string> + <string>78.IBPluginDependency</string> + <string>78.ImportedFromIB2</string> + <string>79.IBPluginDependency</string> + <string>79.ImportedFromIB2</string> + <string>80.IBPluginDependency</string> + <string>80.ImportedFromIB2</string> + <string>81.IBPluginDependency</string> + <string>81.ImportedFromIB2</string> + <string>82.IBPluginDependency</string> + <string>82.ImportedFromIB2</string> + <string>83.IBPluginDependency</string> + <string>83.ImportedFromIB2</string> + <string>92.IBPluginDependency</string> + <string>92.ImportedFromIB2</string> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <boolean value="YES"/> + <string>{{510, 1250}, {320, 188}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>{{510, 1250}, {320, 188}}</string> + <boolean value="YES"/> + <boolean value="YES"/> + <string>{213, 107}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>{{-55, 1287}, {787, 260}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>{{-55, 1287}, {787, 260}}</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>{{0, 1114}, {362, 20}}</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="YES"/> + </object> + </object> + <object class="NSMutableDictionary" key="unlocalizedProperties"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference key="dict.sortedKeys" ref="0"/> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + </object> + <nil key="activeLocalization"/> + <object class="NSMutableDictionary" key="localizations"> + <bool key="EncodedWithXMLCoder">YES</bool> + <reference key="dict.sortedKeys" ref="0"/> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + </object> + </object> + <nil key="sourceID"/> + <int key="maxID">337</int> + </object> + <object class="IBClassDescriber" key="IBDocument.Classes"> + <object class="NSMutableArray" key="referencedPartialClassDescriptions"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBPartialClassDescription"> + <string key="className">Controller</string> + <string key="superclassName">NSObject</string> + <object class="NSMutableDictionary" key="actions"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>crash:</string> + <string>forkTestGo:</string> + <string>forkTestOptions:</string> + <string>generateReportWithoutCrash:</string> + <string>showForkTestWindow:</string> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>id</string> + <string>id</string> + <string>id</string> + <string>id</string> + <string>id</string> + </object> + </object> + <object class="NSMutableDictionary" key="actionInfosByName"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>crash:</string> + <string>forkTestGo:</string> + <string>forkTestOptions:</string> + <string>generateReportWithoutCrash:</string> + <string>showForkTestWindow:</string> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBActionInfo"> + <string key="name">crash:</string> + <string key="candidateClassName">id</string> + </object> + <object class="IBActionInfo"> + <string key="name">forkTestGo:</string> + <string key="candidateClassName">id</string> + </object> + <object class="IBActionInfo"> + <string key="name">forkTestOptions:</string> + <string key="candidateClassName">id</string> + </object> + <object class="IBActionInfo"> + <string key="name">generateReportWithoutCrash:</string> + <string key="candidateClassName">id</string> + </object> + <object class="IBActionInfo"> + <string key="name">showForkTestWindow:</string> + <string key="candidateClassName">id</string> + </object> + </object> + </object> + <object class="NSMutableDictionary" key="outlets"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>forkTestOptions_</string> + <string>window_</string> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>NSWindow</string> + <string>NSWindow</string> + </object> + </object> + <object class="NSMutableDictionary" key="toOneOutletInfosByName"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>forkTestOptions_</string> + <string>window_</string> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBToOneOutletInfo"> + <string key="name">forkTestOptions_</string> + <string key="candidateClassName">NSWindow</string> + </object> + <object class="IBToOneOutletInfo"> + <string key="name">window_</string> + <string key="candidateClassName">NSWindow</string> + </object> + </object> + </object> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBProjectSource</string> + <string key="minorKey">testapp/Controller.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">Controller</string> + <string key="superclassName">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBUserSource</string> + <string key="minorKey"/> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">FirstResponder</string> + <string key="superclassName">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBUserSource</string> + <string key="minorKey"/> + </object> + </object> + </object> + <object class="NSMutableArray" key="referencedPartialClassDescriptionsV3.2+"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBPartialClassDescription"> + <string key="className">NSActionCell</string> + <string key="superclassName">NSCell</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSActionCell.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSApplication</string> + <string key="superclassName">NSResponder</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier" id="785325875"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSApplication.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSApplication</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier" id="806686590"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSApplicationScripting.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSApplication</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier" id="301712406"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSColorPanel.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSApplication</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSHelpManager.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSApplication</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSPageLayout.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSApplication</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSUserInterfaceItemSearching.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSBrowser</string> + <string key="superclassName">NSControl</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSBrowser.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSButton</string> + <string key="superclassName">NSControl</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSButton.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSButtonCell</string> + <string key="superclassName">NSActionCell</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSButtonCell.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSCell</string> + <string key="superclassName">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSCell.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSControl</string> + <string key="superclassName">NSView</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier" id="787388657"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSControl.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSDocument</string> + <string key="superclassName">NSObject</string> + <object class="NSMutableDictionary" key="actions"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>printDocument:</string> + <string>revertDocumentToSaved:</string> + <string>runPageLayout:</string> + <string>saveDocument:</string> + <string>saveDocumentAs:</string> + <string>saveDocumentTo:</string> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>id</string> + <string>id</string> + <string>id</string> + <string>id</string> + <string>id</string> + <string>id</string> + </object> + </object> + <object class="NSMutableDictionary" key="actionInfosByName"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>printDocument:</string> + <string>revertDocumentToSaved:</string> + <string>runPageLayout:</string> + <string>saveDocument:</string> + <string>saveDocumentAs:</string> + <string>saveDocumentTo:</string> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBActionInfo"> + <string key="name">printDocument:</string> + <string key="candidateClassName">id</string> + </object> + <object class="IBActionInfo"> + <string key="name">revertDocumentToSaved:</string> + <string key="candidateClassName">id</string> + </object> + <object class="IBActionInfo"> + <string key="name">runPageLayout:</string> + <string key="candidateClassName">id</string> + </object> + <object class="IBActionInfo"> + <string key="name">saveDocument:</string> + <string key="candidateClassName">id</string> + </object> + <object class="IBActionInfo"> + <string key="name">saveDocumentAs:</string> + <string key="candidateClassName">id</string> + </object> + <object class="IBActionInfo"> + <string key="name">saveDocumentTo:</string> + <string key="candidateClassName">id</string> + </object> + </object> + </object> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSDocument.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSDocument</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSDocumentScripting.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSDocumentController</string> + <string key="superclassName">NSObject</string> + <object class="NSMutableDictionary" key="actions"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>clearRecentDocuments:</string> + <string>newDocument:</string> + <string>openDocument:</string> + <string>saveAllDocuments:</string> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>id</string> + <string>id</string> + <string>id</string> + <string>id</string> + </object> + </object> + <object class="NSMutableDictionary" key="actionInfosByName"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>clearRecentDocuments:</string> + <string>newDocument:</string> + <string>openDocument:</string> + <string>saveAllDocuments:</string> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="IBActionInfo"> + <string key="name">clearRecentDocuments:</string> + <string key="candidateClassName">id</string> + </object> + <object class="IBActionInfo"> + <string key="name">newDocument:</string> + <string key="candidateClassName">id</string> + </object> + <object class="IBActionInfo"> + <string key="name">openDocument:</string> + <string key="candidateClassName">id</string> + </object> + <object class="IBActionInfo"> + <string key="name">saveAllDocuments:</string> + <string key="candidateClassName">id</string> + </object> + </object> + </object> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSDocumentController.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSFormatter</string> + <string key="superclassName">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">Foundation.framework/Headers/NSFormatter.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSMatrix</string> + <string key="superclassName">NSControl</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSMatrix.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSMenu</string> + <string key="superclassName">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier" id="136824428"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSMenu.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSMenuItem</string> + <string key="superclassName">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier" id="171959132"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSMenuItem.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSMovieView</string> + <string key="superclassName">NSView</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSMovieView.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSAccessibility.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <reference key="sourceIdentifier" ref="785325875"/> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <reference key="sourceIdentifier" ref="806686590"/> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <reference key="sourceIdentifier" ref="301712406"/> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <reference key="sourceIdentifier" ref="787388657"/> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSDictionaryController.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSDragging.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSFontManager.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSFontPanel.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSKeyValueBinding.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <reference key="sourceIdentifier" ref="136824428"/> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSNibLoading.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSOutlineView.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSPasteboard.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSSavePanel.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier" id="521965700"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSTableView.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSToolbarItem.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier" id="104369095"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSView.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">Foundation.framework/Headers/NSArchiver.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">Foundation.framework/Headers/NSClassDescription.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">Foundation.framework/Headers/NSError.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">Foundation.framework/Headers/NSFileManager.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">Foundation.framework/Headers/NSKeyValueCoding.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">Foundation.framework/Headers/NSKeyValueObserving.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">Foundation.framework/Headers/NSKeyedArchiver.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">Foundation.framework/Headers/NSObject.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">Foundation.framework/Headers/NSObjectScripting.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">Foundation.framework/Headers/NSPortCoder.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">Foundation.framework/Headers/NSRunLoop.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">Foundation.framework/Headers/NSScriptClassDescription.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">Foundation.framework/Headers/NSScriptKeyValueCoding.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">Foundation.framework/Headers/NSScriptObjectSpecifiers.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">Foundation.framework/Headers/NSScriptWhoseTests.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">Foundation.framework/Headers/NSThread.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">Foundation.framework/Headers/NSURL.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">Foundation.framework/Headers/NSURLConnection.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">Foundation.framework/Headers/NSURLDownload.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSResponder</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSInterfaceStyle.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSResponder</string> + <string key="superclassName">NSObject</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSResponder.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSTableView</string> + <string key="superclassName">NSControl</string> + <reference key="sourceIdentifier" ref="521965700"/> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSText</string> + <string key="superclassName">NSView</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSText.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSView</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSClipView.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSView</string> + <reference key="sourceIdentifier" ref="171959132"/> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSView</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSRulerView.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSView</string> + <string key="superclassName">NSResponder</string> + <reference key="sourceIdentifier" ref="104369095"/> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSWindow</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSDrawer.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSWindow</string> + <string key="superclassName">NSResponder</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSWindow.h</string> + </object> + </object> + <object class="IBPartialClassDescription"> + <string key="className">NSWindow</string> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBFrameworkSource</string> + <string key="minorKey">AppKit.framework/Headers/NSWindowScripting.h</string> + </object> + </object> + </object> + </object> + <int key="IBDocument.localizationMode">0</int> + <string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string> + <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies"> + <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string> + <integer value="1050" key="NS.object.0"/> + </object> + <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults"> + <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string> + <integer value="1050" key="NS.object.0"/> + </object> + <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies"> + <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3</string> + <integer value="3000" key="NS.object.0"/> + </object> + <bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool> + <string key="IBDocument.LastKnownRelativeProjectPath">../../Breakpad.xcodeproj</string> + <int key="IBDocument.defaultPropertyAccessControl">3</int> + <object class="NSMutableDictionary" key="IBDocument.LastKnownImageSizes"> + <bool key="EncodedWithXMLCoder">YES</bool> + <object class="NSArray" key="dict.sortedKeys"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>NSMenuCheckmark</string> + <string>NSMenuMixedState</string> + </object> + <object class="NSMutableArray" key="dict.values"> + <bool key="EncodedWithXMLCoder">YES</bool> + <string>{9, 8}</string> + <string>{7, 2}</string> + </object> + </object> + </data> +</archive> diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/Info.plist b/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/Info.plist new file mode 100644 index 000000000..6094ec6ce --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/Info.plist @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleIconFile</key> + <string>bomb</string> + <key>CFBundleIdentifier</key> + <string>com.Google.BreakpadTest</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>${PRODUCT_NAME}</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1.0</string> + <key>NSMainNibFile</key> + <string>MainMenu</string> + <key>NSPrincipalClass</key> + <string>NSApplication</string> + <key>BreakpadProductDisplay</key> + <string>Breakpad Tester</string> + <key>BreakpadProduct</key> + <string>Breakpad_Tester</string> + <key>BreakpadVersion</key> + <string>1.2.3.4</string> + <key>BreakpadReportInterval</key> + <string>10</string> + <key>BreakpadSkipConfirm</key> + <string>NO</string> + <key>BreakpadSendAndExit</key> + <string>YES</string> + <key>BreakpadRequestEmail</key> + <string>YES</string> + <key>BreakpadRequestComments</key> + <string>YES</string> + <key>BreakpadVendor</key> + <string>Foo Bar Corp, Incorporated, LTD, LLC</string> + <key>BreakpadServerParameters</key> + <dict> + <key>Param1</key> + <string>Value1</string> + <key>Param2</key> + <string>Value2</string> + </dict> + <key>LSUIElement</key> + <string>1</string> +</dict> +</plist> diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/TestClass.h b/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/TestClass.h new file mode 100644 index 000000000..0a6d736d1 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/TestClass.h @@ -0,0 +1,37 @@ +// Copyright (c) 2006, 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 <Cocoa/Cocoa.h> + +@interface TestClass : NSObject { +} + +- (void)wait; + +@end diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/TestClass.mm b/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/TestClass.mm new file mode 100644 index 000000000..6e6a8833d --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/TestClass.mm @@ -0,0 +1,95 @@ +// Copyright (c) 2006, 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 <unistd.h> + +#import "TestClass.h" + +struct AStruct { + int x; + float y; + double z; +}; + +class InternalTestClass { + public: + InternalTestClass(int a) : a_(a) {} + ~InternalTestClass() {} + + void snooze(float a); + void snooze(int a); + int snooze(int a, float b); + + protected: + int a_; + AStruct s_; + + static void InternalFunction(AStruct &s); + static float kStaticFloatValue; +}; + +void InternalTestClass::snooze(float a) { + InternalFunction(s_); + sleep(a_ * a); +} + +void InternalTestClass::snooze(int a) { + InternalFunction(s_); + sleep(a_ * a); +} + +int InternalTestClass::snooze(int a, float b) { + InternalFunction(s_); + sleep(a_ * a * b); + + return 33; +} + +void InternalTestClass::InternalFunction(AStruct &s) { + s.x = InternalTestClass::kStaticFloatValue; +} + +float InternalTestClass::kStaticFloatValue = 42; + +static float PlainOldFunction() { + return 3.14145f; +} + +@implementation TestClass + +- (void)wait { + InternalTestClass t(10); + float z = PlainOldFunction(); + + while (1) { + t.snooze(z); + } +} + +@end diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/bomb.icns b/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/bomb.icns Binary files differnew file mode 100644 index 000000000..c360dbf61 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/bomb.icns diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/crashInMain b/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/crashInMain Binary files differnew file mode 100755 index 000000000..03bb31727 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/crashInMain diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/crashduringload b/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/crashduringload Binary files differnew file mode 100755 index 000000000..5ca9debb7 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/crashduringload diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/main.m b/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/main.m new file mode 100644 index 000000000..1ed19bf96 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/testapp/main.m @@ -0,0 +1,34 @@ +// Copyright (c) 2006, 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 <Cocoa/Cocoa.h> + +int main(int argc, char *argv[]) { + return NSApplicationMain(argc, (const char **) argv); +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/tests/BreakpadFramework_Test.mm b/toolkit/crashreporter/google-breakpad/src/client/mac/tests/BreakpadFramework_Test.mm new file mode 100644 index 000000000..2ea103c69 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/tests/BreakpadFramework_Test.mm @@ -0,0 +1,217 @@ +// Copyright (c) 2009, 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. +// +// BreakpadFramework_Test.mm +// Test case file for Breakpad.h/mm. +// + +#import "GTMSenTestCase.h" +#import "Breakpad.h" + +#include <mach/mach.h> + +@interface BreakpadFramework_Test : GTMTestCase { + @private + int last_exception_code_; + int last_exception_type_; + mach_port_t last_exception_thread_; + // We're not using Obj-C BOOL because we need to interop with + // Breakpad's callback. + bool shouldHandleException_; +} + +// This method is used by a callback used by test cases to determine +// whether to return true or false to Breakpad when handling an +// exception. +- (bool)shouldHandleException; +// This method returns a minimal dictionary that has what +// Breakpad needs to initialize. +- (NSMutableDictionary *)breakpadInitializationDictionary; +// This method is used by the exception handling callback +// to communicate to test cases the properites of the last +// exception. +- (void)setLastExceptionType:(int)type andCode:(int)code + andThread:(mach_port_t)thread; +@end + +// Callback for Breakpad exceptions +bool myBreakpadCallback(int exception_type, + int exception_code, + mach_port_t crashing_thread, + void *context); + +bool myBreakpadCallback(int exception_type, + int exception_code, + mach_port_t crashing_thread, + void *context) { + BreakpadFramework_Test *testCaseClass = + (BreakpadFramework_Test *)context; + [testCaseClass setLastExceptionType:exception_type + andCode:exception_code + andThread:crashing_thread]; + bool shouldHandleException = + [testCaseClass shouldHandleException]; + NSLog(@"Callback returning %d", shouldHandleException); + return shouldHandleException; +} +const int kNoLastExceptionCode = -1; +const int kNoLastExceptionType = -1; +const mach_port_t kNoLastExceptionThread = MACH_PORT_NULL; + +@implementation BreakpadFramework_Test +- (void) initializeExceptionStateVariables { + last_exception_code_ = kNoLastExceptionCode; + last_exception_type_ = kNoLastExceptionType; + last_exception_thread_ = kNoLastExceptionThread; +} + +- (NSMutableDictionary *)breakpadInitializationDictionary { + NSMutableDictionary *breakpadParams = + [NSMutableDictionary dictionaryWithCapacity:3]; + + [breakpadParams setObject:@"UnitTests" forKey:@BREAKPAD_PRODUCT]; + [breakpadParams setObject:@"1.0" forKey:@BREAKPAD_VERSION]; + [breakpadParams setObject:@"http://staging" forKey:@BREAKPAD_URL]; + return breakpadParams; +} + +- (bool)shouldHandleException { + return shouldHandleException_; +} + +- (void)setLastExceptionType:(int)type + andCode:(int)code + andThread:(mach_port_t)thread { + last_exception_type_ = type; + last_exception_code_ = code; + last_exception_thread_ = thread; +} + +// Test that the parameters mark required actually enable Breakpad to +// be initialized. +- (void)testBreakpadInstantiationWithRequiredParameters { + BreakpadRef b = BreakpadCreate([self breakpadInitializationDictionary]); + STAssertNotNULL(b, @"BreakpadCreate failed with required parameters"); + BreakpadRelease(b); +} + +// Test that Breakpad fails to initialize cleanly when required +// parameters are not present. +- (void)testBreakpadInstantiationWithoutRequiredParameters { + NSMutableDictionary *breakpadDictionary = + [self breakpadInitializationDictionary]; + + // Skip setting version, so that BreakpadCreate fails. + [breakpadDictionary removeObjectForKey:@BREAKPAD_VERSION]; + BreakpadRef b = BreakpadCreate(breakpadDictionary); + STAssertNULL(b, @"BreakpadCreate did not fail when missing a required" + " parameter!"); + + breakpadDictionary = [self breakpadInitializationDictionary]; + // Now test with no product + [breakpadDictionary removeObjectForKey:@BREAKPAD_PRODUCT]; + b = BreakpadCreate(breakpadDictionary); + STAssertNULL(b, @"BreakpadCreate did not fail when missing a required" + " parameter!"); + + breakpadDictionary = [self breakpadInitializationDictionary]; + // Now test with no URL + [breakpadDictionary removeObjectForKey:@BREAKPAD_URL]; + b = BreakpadCreate(breakpadDictionary); + STAssertNULL(b, @"BreakpadCreate did not fail when missing a required" + " parameter!"); + BreakpadRelease(b); +} + +// Test to ensure that when we call BreakpadAddUploadParameter, +// it's added to the dictionary correctly(this test depends on +// some internal details of Breakpad, namely, the special prefix +// that it uses to figure out which key/value pairs to upload). +- (void)testAddingBreakpadServerVariable { + NSMutableDictionary *breakpadDictionary = + [self breakpadInitializationDictionary]; + + BreakpadRef b = BreakpadCreate(breakpadDictionary); + STAssertNotNULL(b, @"BreakpadCreate failed with valid dictionary!"); + + BreakpadAddUploadParameter(b, + @"key", + @"value"); + + // Test that it did not add the key/value directly, e.g. without + // prepending the key with the prefix. + STAssertNil(BreakpadKeyValue(b, @"key"), + @"AddUploadParameter added key directly to dictionary" + " instead of prepending it!"); + + NSString *prependedKeyname = + [@BREAKPAD_SERVER_PARAMETER_PREFIX stringByAppendingString:@"key"]; + + STAssertEqualStrings(BreakpadKeyValue(b, prependedKeyname), + @"value", + @"Calling BreakpadAddUploadParameter did not prepend " + "key name"); + BreakpadRelease(b); +} + +// Test that when we do on-demand minidump generation, +// the exception code/type/thread are set properly. +- (void)testFilterCallbackReturnsFalse { + NSMutableDictionary *breakpadDictionary = + [self breakpadInitializationDictionary]; + + BreakpadRef b = BreakpadCreate(breakpadDictionary); + STAssertNotNULL(b, @"BreakpadCreate failed with valid dictionary!"); + BreakpadSetFilterCallback(b, &myBreakpadCallback, self); + + // This causes the callback to return false, meaning + // Breakpad won't take the exception + shouldHandleException_ = false; + + [self initializeExceptionStateVariables]; + STAssertEquals(last_exception_type_, kNoLastExceptionType, + @"Last exception type not initialized correctly."); + STAssertEquals(last_exception_code_, kNoLastExceptionCode, + @"Last exception code not initialized correctly."); + STAssertEquals(last_exception_thread_, kNoLastExceptionThread, + @"Last exception thread is not initialized correctly."); + + // Cause Breakpad's exception handler to be invoked. + BreakpadGenerateAndSendReport(b); + + STAssertEquals(last_exception_type_, 0, + @"Last exception type is not 0 for on demand"); + STAssertEquals(last_exception_code_, 0, + @"Last exception code is not 0 for on demand"); + STAssertEquals(last_exception_thread_, mach_thread_self(), + @"Last exception thread is not mach_thread_self() " + "for on demand"); +} + +@end diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/tests/crash_generation_server_test.cc b/toolkit/crashreporter/google-breakpad/src/client/mac/tests/crash_generation_server_test.cc new file mode 100644 index 000000000..0164f4a29 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/tests/crash_generation_server_test.cc @@ -0,0 +1,398 @@ +// Copyright (c) 2010, 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. +// +// crash_generation_server_test.cc +// Unit tests for CrashGenerationServer + +#include <dirent.h> +#include <glob.h> +#include <stdint.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <string> + +#include "breakpad_googletest_includes.h" +#include "client/mac/crash_generation/client_info.h" +#include "client/mac/crash_generation/crash_generation_client.h" +#include "client/mac/crash_generation/crash_generation_server.h" +#include "client/mac/handler/exception_handler.h" +#include "client/mac/tests/spawn_child_process.h" +#include "common/tests/auto_tempdir.h" +#include "google_breakpad/processor/minidump.h" + +namespace google_breakpad { +// This acts as the log sink for INFO logging from the processor +// logging code. The logging output confuses XCode and makes it think +// there are unit test failures. testlogging.h handles the overriding. +std::ostringstream info_log; +} + +namespace { +using std::string; +using google_breakpad::AutoTempDir; +using google_breakpad::ClientInfo; +using google_breakpad::CrashGenerationClient; +using google_breakpad::CrashGenerationServer; +using google_breakpad::ExceptionHandler; +using google_breakpad::Minidump; +using google_breakpad::MinidumpContext; +using google_breakpad::MinidumpException; +using google_breakpad::MinidumpModule; +using google_breakpad::MinidumpModuleList; +using google_breakpad::MinidumpSystemInfo; +using google_breakpad::MinidumpThread; +using google_breakpad::MinidumpThreadList; +using testing::Test; +using namespace google_breakpad_test; + +class CrashGenerationServerTest : public Test { +public: + // The port name to receive messages on + char mach_port_name[128]; + // Filename of the last dump that was generated + string last_dump_name; + // PID of the child process + pid_t child_pid; + // A temp dir + AutoTempDir temp_dir; + // Counter just to ensure that we don't hit the same port again + static int i; + bool filter_callback_called; + + void SetUp() { + sprintf(mach_port_name, + "com.google.breakpad.ServerTest.%d.%d", getpid(), + CrashGenerationServerTest::i++); + child_pid = (pid_t)-1; + filter_callback_called = false; + } +}; +int CrashGenerationServerTest::i = 0; + +// Test that starting and stopping a server works +TEST_F(CrashGenerationServerTest, testStartStopServer) { + CrashGenerationServer server(mach_port_name, + NULL, // filter callback + NULL, // filter context + NULL, // dump callback + NULL, // dump context + NULL, // exit callback + NULL, // exit context + false, // generate dumps + ""); // dump path + ASSERT_TRUE(server.Start()); + ASSERT_TRUE(server.Stop()); +} + +// Test that requesting a dump via CrashGenerationClient works +// Test without actually dumping +TEST_F(CrashGenerationServerTest, testRequestDumpNoDump) { + CrashGenerationServer server(mach_port_name, + NULL, // filter callback + NULL, // filter context + NULL, // dump callback + NULL, // dump context + NULL, // exit callback + NULL, // exit context + false, // don't generate dumps + temp_dir.path()); // dump path + ASSERT_TRUE(server.Start()); + + pid_t pid = fork(); + ASSERT_NE(-1, pid); + if (pid == 0) { + CrashGenerationClient client(mach_port_name); + bool result = client.RequestDump(); + exit(result ? 0 : 1); + } + + int ret; + ASSERT_EQ(pid, waitpid(pid, &ret, 0)); + EXPECT_TRUE(WIFEXITED(ret)); + EXPECT_EQ(0, WEXITSTATUS(ret)); + EXPECT_TRUE(server.Stop()); + // check that no minidump was written + string pattern = temp_dir.path() + "/*"; + glob_t dirContents; + ret = glob(pattern.c_str(), GLOB_NOSORT, NULL, &dirContents); + EXPECT_EQ(GLOB_NOMATCH, ret); + if (ret != GLOB_NOMATCH) + globfree(&dirContents); +} + +void dumpCallback(void *context, const ClientInfo &client_info, + const std::string &file_path) { + if (context) { + CrashGenerationServerTest* self = + reinterpret_cast<CrashGenerationServerTest*>(context); + if (!file_path.empty()) + self->last_dump_name = file_path; + self->child_pid = client_info.pid(); + } +} + +void *RequestDump(void *context) { + CrashGenerationClient client((const char*)context); + bool result = client.RequestDump(); + return (void*)(result ? 0 : 1); +} + +// Test that actually writing a minidump works +TEST_F(CrashGenerationServerTest, testRequestDump) { + CrashGenerationServer server(mach_port_name, + NULL, // filter callback + NULL, // filter context + dumpCallback, // dump callback + this, // dump context + NULL, // exit callback + NULL, // exit context + true, // generate dumps + temp_dir.path()); // dump path + ASSERT_TRUE(server.Start()); + + pid_t pid = fork(); + ASSERT_NE(-1, pid); + if (pid == 0) { + // Have to spawn off a separate thread to request the dump, + // because MinidumpGenerator assumes the handler thread is not + // the only thread + pthread_t thread; + if (pthread_create(&thread, NULL, RequestDump, (void*)mach_port_name) != 0) + exit(1); + void* result; + pthread_join(thread, &result); + exit(reinterpret_cast<intptr_t>(result)); + } + + int ret; + ASSERT_EQ(pid, waitpid(pid, &ret, 0)); + EXPECT_TRUE(WIFEXITED(ret)); + EXPECT_EQ(0, WEXITSTATUS(ret)); + EXPECT_TRUE(server.Stop()); + // check that minidump was written + ASSERT_FALSE(last_dump_name.empty()); + struct stat st; + EXPECT_EQ(0, stat(last_dump_name.c_str(), &st)); + EXPECT_LT(0, st.st_size); + // check client's PID + ASSERT_EQ(pid, child_pid); +} + +static void Crasher() { + int *a = (int*)0x42; + + fprintf(stdout, "Going to crash...\n"); + fprintf(stdout, "A = %d", *a); +} + +// Test that crashing a child process with an OOP ExceptionHandler installed +// results in a minidump being written by the CrashGenerationServer in +// the parent. +TEST_F(CrashGenerationServerTest, testChildProcessCrash) { + CrashGenerationServer server(mach_port_name, + NULL, // filter callback + NULL, // filter context + dumpCallback, // dump callback + this, // dump context + NULL, // exit callback + NULL, // exit context + true, // generate dumps + temp_dir.path()); // dump path + ASSERT_TRUE(server.Start()); + + pid_t pid = fork(); + ASSERT_NE(-1, pid); + if (pid == 0) { + // Instantiate an OOP exception handler. + ExceptionHandler eh("", NULL, NULL, NULL, true, mach_port_name); + Crasher(); + // not reached + exit(0); + } + + int ret; + ASSERT_EQ(pid, waitpid(pid, &ret, 0)); + EXPECT_FALSE(WIFEXITED(ret)); + EXPECT_TRUE(server.Stop()); + // check that minidump was written + ASSERT_FALSE(last_dump_name.empty()); + struct stat st; + EXPECT_EQ(0, stat(last_dump_name.c_str(), &st)); + EXPECT_LT(0, st.st_size); + + // Read the minidump, sanity check some data. + Minidump minidump(last_dump_name.c_str()); + ASSERT_TRUE(minidump.Read()); + + MinidumpSystemInfo* system_info = minidump.GetSystemInfo(); + ASSERT_TRUE(system_info); + const MDRawSystemInfo* raw_info = system_info->system_info(); + ASSERT_TRUE(raw_info); + EXPECT_EQ(kNativeArchitecture, raw_info->processor_architecture); + + MinidumpThreadList* thread_list = minidump.GetThreadList(); + ASSERT_TRUE(thread_list); + ASSERT_EQ((unsigned int)1, thread_list->thread_count()); + + MinidumpThread* main_thread = thread_list->GetThreadAtIndex(0); + ASSERT_TRUE(main_thread); + MinidumpContext* context = main_thread->GetContext(); + ASSERT_TRUE(context); + EXPECT_EQ(kNativeContext, context->GetContextCPU()); + + MinidumpModuleList* module_list = minidump.GetModuleList(); + ASSERT_TRUE(module_list); + const MinidumpModule* main_module = module_list->GetMainModule(); + ASSERT_TRUE(main_module); + EXPECT_EQ(GetExecutablePath(), main_module->code_file()); +} + +#if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6) && \ + (defined(__x86_64__) || defined(__i386__)) +// Test that crashing a child process of a different architecture +// produces a valid minidump. +TEST_F(CrashGenerationServerTest, testChildProcessCrashCrossArchitecture) { + CrashGenerationServer server(mach_port_name, + NULL, // filter callback + NULL, // filter context + dumpCallback, // dump callback + this, // dump context + NULL, // exit callback + NULL, // exit context + true, // generate dumps + temp_dir.path()); // dump path + ASSERT_TRUE(server.Start()); + + // Spawn a child process + string helper_path = GetHelperPath(); + const char* argv[] = { + helper_path.c_str(), + "crash", + mach_port_name, + NULL + }; + pid_t pid = spawn_child_process(argv); + ASSERT_NE(-1, pid); + + int ret; + ASSERT_EQ(pid, waitpid(pid, &ret, 0)); + EXPECT_FALSE(WIFEXITED(ret)); + EXPECT_TRUE(server.Stop()); + // check that minidump was written + ASSERT_FALSE(last_dump_name.empty()); + struct stat st; + EXPECT_EQ(0, stat(last_dump_name.c_str(), &st)); + EXPECT_LT(0, st.st_size); + +const MDCPUArchitecture kExpectedArchitecture = +#if defined(__x86_64__) + MD_CPU_ARCHITECTURE_X86 +#elif defined(__i386__) + MD_CPU_ARCHITECTURE_AMD64 +#endif + ; +const uint32_t kExpectedContext = +#if defined(__i386__) + MD_CONTEXT_AMD64 +#elif defined(__x86_64__) + MD_CONTEXT_X86 +#endif + ; + + // Read the minidump, sanity check some data. + Minidump minidump(last_dump_name.c_str()); + ASSERT_TRUE(minidump.Read()); + + MinidumpSystemInfo* system_info = minidump.GetSystemInfo(); + ASSERT_TRUE(system_info); + const MDRawSystemInfo* raw_info = system_info->system_info(); + ASSERT_TRUE(raw_info); + EXPECT_EQ(kExpectedArchitecture, raw_info->processor_architecture); + + MinidumpThreadList* thread_list = minidump.GetThreadList(); + ASSERT_TRUE(thread_list); + ASSERT_EQ((unsigned int)1, thread_list->thread_count()); + + MinidumpThread* main_thread = thread_list->GetThreadAtIndex(0); + ASSERT_TRUE(main_thread); + MinidumpContext* context = main_thread->GetContext(); + ASSERT_TRUE(context); + EXPECT_EQ(kExpectedContext, context->GetContextCPU()); + + MinidumpModuleList* module_list = minidump.GetModuleList(); + ASSERT_TRUE(module_list); + const MinidumpModule* main_module = module_list->GetMainModule(); + ASSERT_TRUE(main_module); + EXPECT_EQ(helper_path, main_module->code_file()); +} +#endif + +bool filter_callback(void* context) { + CrashGenerationServerTest* self = + reinterpret_cast<CrashGenerationServerTest*>(context); + self->filter_callback_called = true; + // veto dump generation + return false; +} + +// Test that a filter callback can veto minidump writing. +TEST_F(CrashGenerationServerTest, testFilter) { + CrashGenerationServer server(mach_port_name, + filter_callback, // filter callback + this, // filter context + dumpCallback, // dump callback + this, // dump context + NULL, // exit callback + NULL, // exit context + true, // generate dumps + temp_dir.path()); // dump path + ASSERT_TRUE(server.Start()); + + pid_t pid = fork(); + ASSERT_NE(-1, pid); + if (pid == 0) { + // Instantiate an OOP exception handler. + ExceptionHandler eh("", NULL, NULL, NULL, true, mach_port_name); + Crasher(); + // not reached + exit(0); + } + + int ret; + ASSERT_EQ(pid, waitpid(pid, &ret, 0)); + EXPECT_FALSE(WIFEXITED(ret)); + EXPECT_TRUE(server.Stop()); + + // check that no minidump was written + EXPECT_TRUE(last_dump_name.empty()); + EXPECT_TRUE(filter_callback_called); +} + +} // namespace diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/tests/exception_handler_test.cc b/toolkit/crashreporter/google-breakpad/src/client/mac/tests/exception_handler_test.cc new file mode 100644 index 000000000..a8cf6968c --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/tests/exception_handler_test.cc @@ -0,0 +1,713 @@ +// Copyright (c) 2010, 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. + +// exception_handler_test.cc: Unit tests for google_breakpad::ExceptionHandler + +#include <pthread.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "breakpad_googletest_includes.h" +#include "client/mac/handler/exception_handler.h" +#include "common/mac/MachIPC.h" +#include "common/tests/auto_tempdir.h" +#include "google_breakpad/processor/minidump.h" + +namespace google_breakpad { +// This acts as the log sink for INFO logging from the processor +// logging code. The logging output confuses XCode and makes it think +// there are unit test failures. testlogging.h handles the overriding. +std::ostringstream info_log; +} + +namespace { +using std::string; +using google_breakpad::AutoTempDir; +using google_breakpad::ExceptionHandler; +using google_breakpad::MachPortSender; +using google_breakpad::MachReceiveMessage; +using google_breakpad::MachSendMessage; +using google_breakpad::Minidump; +using google_breakpad::MinidumpContext; +using google_breakpad::MinidumpException; +using google_breakpad::MinidumpMemoryList; +using google_breakpad::MinidumpMemoryRegion; +using google_breakpad::ReceivePort; +using testing::Test; + +class ExceptionHandlerTest : public Test { + public: + void InProcessCrash(bool aborting); + AutoTempDir tempDir; + string lastDumpName; +}; + +static void Crasher() { + int *a = (int*)0x42; + + fprintf(stdout, "Going to crash...\n"); + fprintf(stdout, "A = %d", *a); +} + +static void AbortCrasher() { + fprintf(stdout, "Going to crash...\n"); + abort(); +} + +static void SoonToCrash(void(*crasher)()) { + crasher(); +} + +static bool MDCallback(const char *dump_dir, const char *file_name, + void *context, bool success) { + string path(dump_dir); + path.append("/"); + path.append(file_name); + path.append(".dmp"); + + int fd = *reinterpret_cast<int*>(context); + (void)write(fd, path.c_str(), path.length() + 1); + close(fd); + exit(0); + // not reached + return true; +} + +void ExceptionHandlerTest::InProcessCrash(bool aborting) { + // Give the child process a pipe to report back on. + int fds[2]; + ASSERT_EQ(0, pipe(fds)); + // Fork off a child process so it can crash. + pid_t pid = fork(); + if (pid == 0) { + // In the child process. + close(fds[0]); + ExceptionHandler eh(tempDir.path(), NULL, MDCallback, &fds[1], true, NULL); + // crash + SoonToCrash(aborting ? &AbortCrasher : &Crasher); + // not reached + exit(1); + } + // In the parent process. + ASSERT_NE(-1, pid); + // Wait for the background process to return the minidump file. + close(fds[1]); + char minidump_file[PATH_MAX]; + ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file)); + ASSERT_NE(0, nbytes); + + Minidump minidump(minidump_file); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + ASSERT_TRUE(exception); + + const MDRawExceptionStream* raw_exception = exception->exception(); + ASSERT_TRUE(raw_exception); + + if (aborting) { + EXPECT_EQ(MD_EXCEPTION_MAC_SOFTWARE, + raw_exception->exception_record.exception_code); + EXPECT_EQ(MD_EXCEPTION_CODE_MAC_ABORT, + raw_exception->exception_record.exception_flags); + } else { + EXPECT_EQ(MD_EXCEPTION_MAC_BAD_ACCESS, + raw_exception->exception_record.exception_code); +#if defined(__x86_64__) + EXPECT_EQ(MD_EXCEPTION_CODE_MAC_INVALID_ADDRESS, + raw_exception->exception_record.exception_flags); +#elif defined(__i386__) + EXPECT_EQ(MD_EXCEPTION_CODE_MAC_PROTECTION_FAILURE, + raw_exception->exception_record.exception_flags); +#endif + } + + const MinidumpContext* context = exception->GetContext(); + ASSERT_TRUE(context); + + uint64_t instruction_pointer; + ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer)); + + // Ideally would like to sanity check that abort() is on the stack + // but that's hard. + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(memory_list); + MinidumpMemoryRegion* region = + memory_list->GetMemoryRegionForAddress(instruction_pointer); + EXPECT_TRUE(region); + + // Child process should have exited with a zero status. + int ret; + ASSERT_EQ(pid, waitpid(pid, &ret, 0)); + EXPECT_NE(0, WIFEXITED(ret)); + EXPECT_EQ(0, WEXITSTATUS(ret)); +} + +TEST_F(ExceptionHandlerTest, InProcess) { + InProcessCrash(false); +} + +TEST_F(ExceptionHandlerTest, InProcessAbort) { + InProcessCrash(true); +} + +static bool DumpNameMDCallback(const char *dump_dir, const char *file_name, + void *context, bool success) { + ExceptionHandlerTest *self = reinterpret_cast<ExceptionHandlerTest*>(context); + if (dump_dir && file_name) { + self->lastDumpName = dump_dir; + self->lastDumpName += "/"; + self->lastDumpName += file_name; + self->lastDumpName += ".dmp"; + } + return true; +} + +TEST_F(ExceptionHandlerTest, WriteMinidump) { + ExceptionHandler eh(tempDir.path(), NULL, DumpNameMDCallback, this, true, + NULL); + ASSERT_TRUE(eh.WriteMinidump()); + + // Ensure that minidump file exists and is > 0 bytes. + ASSERT_FALSE(lastDumpName.empty()); + struct stat st; + ASSERT_EQ(0, stat(lastDumpName.c_str(), &st)); + ASSERT_LT(0, st.st_size); + + // The minidump should not contain an exception stream. + Minidump minidump(lastDumpName); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + EXPECT_FALSE(exception); +} + +TEST_F(ExceptionHandlerTest, WriteMinidumpWithException) { + ExceptionHandler eh(tempDir.path(), NULL, DumpNameMDCallback, this, true, + NULL); + ASSERT_TRUE(eh.WriteMinidump(true)); + + // Ensure that minidump file exists and is > 0 bytes. + ASSERT_FALSE(lastDumpName.empty()); + struct stat st; + ASSERT_EQ(0, stat(lastDumpName.c_str(), &st)); + ASSERT_LT(0, st.st_size); + + // The minidump should contain an exception stream. + Minidump minidump(lastDumpName); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + ASSERT_TRUE(exception); + const MDRawExceptionStream* raw_exception = exception->exception(); + ASSERT_TRUE(raw_exception); + + EXPECT_EQ(MD_EXCEPTION_MAC_BREAKPOINT, + raw_exception->exception_record.exception_code); +} + +TEST_F(ExceptionHandlerTest, DumpChildProcess) { + const int kTimeoutMs = 2000; + // Create a mach port to receive the child task on. + char machPortName[128]; + sprintf(machPortName, "ExceptionHandlerTest.%d", getpid()); + ReceivePort parent_recv_port(machPortName); + + // Give the child process a pipe to block on. + int fds[2]; + ASSERT_EQ(0, pipe(fds)); + + // Fork off a child process to dump. + pid_t pid = fork(); + if (pid == 0) { + // In the child process + close(fds[1]); + + // Send parent process the task and thread ports. + MachSendMessage child_message(0); + child_message.AddDescriptor(mach_task_self()); + child_message.AddDescriptor(mach_thread_self()); + + MachPortSender child_sender(machPortName); + if (child_sender.SendMessage(child_message, kTimeoutMs) != KERN_SUCCESS) + exit(1); + + // Wait for the parent process. + uint8_t data; + read(fds[0], &data, 1); + exit(0); + } + // In the parent process. + ASSERT_NE(-1, pid); + close(fds[0]); + + // Read the child's task and thread ports. + MachReceiveMessage child_message; + ASSERT_EQ(KERN_SUCCESS, + parent_recv_port.WaitForMessage(&child_message, kTimeoutMs)); + mach_port_t child_task = child_message.GetTranslatedPort(0); + mach_port_t child_thread = child_message.GetTranslatedPort(1); + ASSERT_NE((mach_port_t)MACH_PORT_NULL, child_task); + ASSERT_NE((mach_port_t)MACH_PORT_NULL, child_thread); + + // Write a minidump of the child process. + bool result = ExceptionHandler::WriteMinidumpForChild(child_task, + child_thread, + tempDir.path(), + DumpNameMDCallback, + this); + ASSERT_EQ(true, result); + + // Ensure that minidump file exists and is > 0 bytes. + ASSERT_FALSE(lastDumpName.empty()); + struct stat st; + ASSERT_EQ(0, stat(lastDumpName.c_str(), &st)); + ASSERT_LT(0, st.st_size); + + // Unblock child process + uint8_t data = 1; + (void)write(fds[1], &data, 1); + + // Child process should have exited with a zero status. + int ret; + ASSERT_EQ(pid, waitpid(pid, &ret, 0)); + EXPECT_NE(0, WIFEXITED(ret)); + EXPECT_EQ(0, WEXITSTATUS(ret)); +} + +// Test that memory around the instruction pointer is written +// to the dump as a MinidumpMemoryRegion. +TEST_F(ExceptionHandlerTest, InstructionPointerMemory) { + // Give the child process a pipe to report back on. + int fds[2]; + ASSERT_EQ(0, pipe(fds)); + + // These are defined here so the parent can use them to check the + // data from the minidump afterwards. + const uint32_t kMemorySize = 256; // bytes + const int kOffset = kMemorySize / 2; + // This crashes with SIGILL on x86/x86-64/arm. + const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff }; + + pid_t pid = fork(); + if (pid == 0) { + close(fds[0]); + ExceptionHandler eh(tempDir.path(), NULL, MDCallback, &fds[1], true, NULL); + // Get some executable memory. + char* memory = + reinterpret_cast<char*>(mmap(NULL, + kMemorySize, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANON, + -1, + 0)); + if (!memory) + exit(0); + + // Write some instructions that will crash. Put them in the middle + // of the block of memory, because the minidump should contain 128 + // bytes on either side of the instruction pointer. + memcpy(memory + kOffset, instructions, sizeof(instructions)); + + // Now execute the instructions, which should crash. + typedef void (*void_function)(void); + void_function memory_function = + reinterpret_cast<void_function>(memory + kOffset); + memory_function(); + // not reached + exit(1); + } + // In the parent process. + ASSERT_NE(-1, pid); + close(fds[1]); + + // Wait for the background process to return the minidump file. + close(fds[1]); + char minidump_file[PATH_MAX]; + ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file)); + ASSERT_NE(0, nbytes); + // Ensure that minidump file exists and is > 0 bytes. + struct stat st; + ASSERT_EQ(0, stat(minidump_file, &st)); + ASSERT_LT(0, st.st_size); + + // Child process should have exited with a zero status. + int ret; + ASSERT_EQ(pid, waitpid(pid, &ret, 0)); + EXPECT_NE(0, WIFEXITED(ret)); + EXPECT_EQ(0, WEXITSTATUS(ret)); + + // Read the minidump. Locate the exception record and the + // memory list, and then ensure that there is a memory region + // in the memory list that covers the instruction pointer from + // the exception record. + Minidump minidump(minidump_file); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(exception); + ASSERT_TRUE(memory_list); + ASSERT_NE((unsigned int)0, memory_list->region_count()); + + MinidumpContext* context = exception->GetContext(); + ASSERT_TRUE(context); + + uint64_t instruction_pointer; + ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer)); + + MinidumpMemoryRegion* region = + memory_list->GetMemoryRegionForAddress(instruction_pointer); + EXPECT_TRUE(region); + + EXPECT_EQ(kMemorySize, region->GetSize()); + const uint8_t* bytes = region->GetMemory(); + ASSERT_TRUE(bytes); + + uint8_t prefix_bytes[kOffset]; + uint8_t suffix_bytes[kMemorySize - kOffset - sizeof(instructions)]; + memset(prefix_bytes, 0, sizeof(prefix_bytes)); + memset(suffix_bytes, 0, sizeof(suffix_bytes)); + EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0); + EXPECT_TRUE(memcmp(bytes + kOffset, instructions, sizeof(instructions)) == 0); + EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(instructions), + suffix_bytes, sizeof(suffix_bytes)) == 0); +} + +// Test that the memory region around the instruction pointer is +// bounded correctly on the low end. +TEST_F(ExceptionHandlerTest, InstructionPointerMemoryMinBound) { + // Give the child process a pipe to report back on. + int fds[2]; + ASSERT_EQ(0, pipe(fds)); + + // These are defined here so the parent can use them to check the + // data from the minidump afterwards. + const uint32_t kMemorySize = 256; // bytes + const int kOffset = 0; + // This crashes with SIGILL on x86/x86-64/arm. + const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff }; + + pid_t pid = fork(); + if (pid == 0) { + close(fds[0]); + ExceptionHandler eh(tempDir.path(), NULL, MDCallback, &fds[1], true, NULL); + // Get some executable memory. + char* memory = + reinterpret_cast<char*>(mmap(NULL, + kMemorySize, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANON, + -1, + 0)); + if (!memory) + exit(0); + + // Write some instructions that will crash. Put them at the start + // of the block of memory, to ensure that the memory bounding + // works properly. + memcpy(memory + kOffset, instructions, sizeof(instructions)); + + // Now execute the instructions, which should crash. + typedef void (*void_function)(void); + void_function memory_function = + reinterpret_cast<void_function>(memory + kOffset); + memory_function(); + // not reached + exit(1); + } + // In the parent process. + ASSERT_NE(-1, pid); + close(fds[1]); + + // Wait for the background process to return the minidump file. + close(fds[1]); + char minidump_file[PATH_MAX]; + ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file)); + ASSERT_NE(0, nbytes); + // Ensure that minidump file exists and is > 0 bytes. + struct stat st; + ASSERT_EQ(0, stat(minidump_file, &st)); + ASSERT_LT(0, st.st_size); + + // Child process should have exited with a zero status. + int ret; + ASSERT_EQ(pid, waitpid(pid, &ret, 0)); + EXPECT_NE(0, WIFEXITED(ret)); + EXPECT_EQ(0, WEXITSTATUS(ret)); + + // Read the minidump. Locate the exception record and the + // memory list, and then ensure that there is a memory region + // in the memory list that covers the instruction pointer from + // the exception record. + Minidump minidump(minidump_file); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(exception); + ASSERT_TRUE(memory_list); + ASSERT_NE((unsigned int)0, memory_list->region_count()); + + MinidumpContext* context = exception->GetContext(); + ASSERT_TRUE(context); + + uint64_t instruction_pointer; + ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer)); + + MinidumpMemoryRegion* region = + memory_list->GetMemoryRegionForAddress(instruction_pointer); + EXPECT_TRUE(region); + + EXPECT_EQ(kMemorySize / 2, region->GetSize()); + const uint8_t* bytes = region->GetMemory(); + ASSERT_TRUE(bytes); + + uint8_t suffix_bytes[kMemorySize / 2 - sizeof(instructions)]; + memset(suffix_bytes, 0, sizeof(suffix_bytes)); + EXPECT_TRUE(memcmp(bytes + kOffset, instructions, sizeof(instructions)) == 0); + EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(instructions), + suffix_bytes, sizeof(suffix_bytes)) == 0); +} + +// Test that the memory region around the instruction pointer is +// bounded correctly on the high end. +TEST_F(ExceptionHandlerTest, InstructionPointerMemoryMaxBound) { + // Give the child process a pipe to report back on. + int fds[2]; + ASSERT_EQ(0, pipe(fds)); + + // These are defined here so the parent can use them to check the + // data from the minidump afterwards. + // Use 4k here because the OS will hand out a single page even + // if a smaller size is requested, and this test wants to + // test the upper bound of the memory range. + const uint32_t kMemorySize = 4096; // bytes + // This crashes with SIGILL on x86/x86-64/arm. + const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff }; + const int kOffset = kMemorySize - sizeof(instructions); + + pid_t pid = fork(); + if (pid == 0) { + close(fds[0]); + ExceptionHandler eh(tempDir.path(), NULL, MDCallback, &fds[1], true, NULL); + // Get some executable memory. + char* memory = + reinterpret_cast<char*>(mmap(NULL, + kMemorySize, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANON, + -1, + 0)); + if (!memory) + exit(0); + + // Write some instructions that will crash. Put them at the start + // of the block of memory, to ensure that the memory bounding + // works properly. + memcpy(memory + kOffset, instructions, sizeof(instructions)); + + // Now execute the instructions, which should crash. + typedef void (*void_function)(void); + void_function memory_function = + reinterpret_cast<void_function>(memory + kOffset); + memory_function(); + // not reached + exit(1); + } + // In the parent process. + ASSERT_NE(-1, pid); + close(fds[1]); + + // Wait for the background process to return the minidump file. + close(fds[1]); + char minidump_file[PATH_MAX]; + ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file)); + ASSERT_NE(0, nbytes); + // Ensure that minidump file exists and is > 0 bytes. + struct stat st; + ASSERT_EQ(0, stat(minidump_file, &st)); + ASSERT_LT(0, st.st_size); + + // Child process should have exited with a zero status. + int ret; + ASSERT_EQ(pid, waitpid(pid, &ret, 0)); + EXPECT_NE(0, WIFEXITED(ret)); + EXPECT_EQ(0, WEXITSTATUS(ret)); + + // Read the minidump. Locate the exception record and the + // memory list, and then ensure that there is a memory region + // in the memory list that covers the instruction pointer from + // the exception record. + Minidump minidump(minidump_file); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(exception); + ASSERT_TRUE(memory_list); + ASSERT_NE((unsigned int)0, memory_list->region_count()); + + MinidumpContext* context = exception->GetContext(); + ASSERT_TRUE(context); + + uint64_t instruction_pointer; + ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer)); + + MinidumpMemoryRegion* region = + memory_list->GetMemoryRegionForAddress(instruction_pointer); + EXPECT_TRUE(region); + + const size_t kPrefixSize = 128; // bytes + EXPECT_EQ(kPrefixSize + sizeof(instructions), region->GetSize()); + const uint8_t* bytes = region->GetMemory(); + ASSERT_TRUE(bytes); + + uint8_t prefix_bytes[kPrefixSize]; + memset(prefix_bytes, 0, sizeof(prefix_bytes)); + EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0); + EXPECT_TRUE(memcmp(bytes + kPrefixSize, + instructions, sizeof(instructions)) == 0); +} + +// Ensure that an extra memory block doesn't get added when the +// instruction pointer is not in mapped memory. +TEST_F(ExceptionHandlerTest, InstructionPointerMemoryNullPointer) { + // Give the child process a pipe to report back on. + int fds[2]; + ASSERT_EQ(0, pipe(fds)); + + pid_t pid = fork(); + if (pid == 0) { + close(fds[0]); + ExceptionHandler eh(tempDir.path(), NULL, MDCallback, &fds[1], true, NULL); + // Try calling a NULL pointer. + typedef void (*void_function)(void); + // Volatile markings are needed to keep Clang from generating invalid + // opcodes. See http://crbug.com/498354 for details. + volatile void_function memory_function = + reinterpret_cast<void_function>(NULL); + memory_function(); + // not reached + exit(1); + } + // In the parent process. + ASSERT_NE(-1, pid); + close(fds[1]); + + // Wait for the background process to return the minidump file. + close(fds[1]); + char minidump_file[PATH_MAX]; + ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file)); + ASSERT_NE(0, nbytes); + // Ensure that minidump file exists and is > 0 bytes. + struct stat st; + ASSERT_EQ(0, stat(minidump_file, &st)); + ASSERT_LT(0, st.st_size); + + // Child process should have exited with a zero status. + int ret; + ASSERT_EQ(pid, waitpid(pid, &ret, 0)); + EXPECT_NE(0, WIFEXITED(ret)); + EXPECT_EQ(0, WEXITSTATUS(ret)); + + // Read the minidump. Locate the exception record and the + // memory list, and then ensure that there is only one memory region + // in the memory list (the thread memory from the single thread). + Minidump minidump(minidump_file); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(exception); + ASSERT_TRUE(memory_list); + ASSERT_EQ((unsigned int)1, memory_list->region_count()); +} + +static void *Junk(void *) { + sleep(1000000); + return NULL; +} + +// Test that the memory list gets written correctly when multiple +// threads are running. +TEST_F(ExceptionHandlerTest, MemoryListMultipleThreads) { + // Give the child process a pipe to report back on. + int fds[2]; + ASSERT_EQ(0, pipe(fds)); + + pid_t pid = fork(); + if (pid == 0) { + close(fds[0]); + ExceptionHandler eh(tempDir.path(), NULL, MDCallback, &fds[1], true, NULL); + + // Run an extra thread so >2 memory regions will be written. + pthread_t junk_thread; + if (pthread_create(&junk_thread, NULL, Junk, NULL) == 0) + pthread_detach(junk_thread); + + // Just crash. + Crasher(); + + // not reached + exit(1); + } + // In the parent process. + ASSERT_NE(-1, pid); + close(fds[1]); + + // Wait for the background process to return the minidump file. + close(fds[1]); + char minidump_file[PATH_MAX]; + ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file)); + ASSERT_NE(0, nbytes); + // Ensure that minidump file exists and is > 0 bytes. + struct stat st; + ASSERT_EQ(0, stat(minidump_file, &st)); + ASSERT_LT(0, st.st_size); + + // Child process should have exited with a zero status. + int ret; + ASSERT_EQ(pid, waitpid(pid, &ret, 0)); + EXPECT_NE(0, WIFEXITED(ret)); + EXPECT_EQ(0, WEXITSTATUS(ret)); + + // Read the minidump, and verify that the memory list can be read. + Minidump minidump(minidump_file); + ASSERT_TRUE(minidump.Read()); + + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(memory_list); + // Verify that there are three memory regions: + // one per thread, and one for the instruction pointer memory. + ASSERT_EQ((unsigned int)3, memory_list->region_count()); +} + +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/tests/minidump_generator_test.cc b/toolkit/crashreporter/google-breakpad/src/client/mac/tests/minidump_generator_test.cc new file mode 100644 index 000000000..d40c7d986 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/tests/minidump_generator_test.cc @@ -0,0 +1,319 @@ +// Copyright (c) 2010, 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. + +// minidump_generator_test.cc: Unit tests for google_breakpad::MinidumpGenerator + +#include <AvailabilityMacros.h> +#ifndef MAC_OS_X_VERSION_10_6 +#define MAC_OS_X_VERSION_10_6 1060 +#endif +#include <sys/stat.h> +#include <unistd.h> + +#include <string> +#include <vector> + +#include "breakpad_googletest_includes.h" +#include "client/mac/handler/minidump_generator.h" +#include "client/mac/tests/spawn_child_process.h" +#include "common/mac/MachIPC.h" +#include "common/tests/auto_tempdir.h" +#include "google_breakpad/processor/minidump.h" + +namespace google_breakpad { +// This acts as the log sink for INFO logging from the processor +// logging code. The logging output confuses XCode and makes it think +// there are unit test failures. testlogging.h handles the overriding. +std::ostringstream info_log; +} + +namespace { +using std::string; +using std::vector; +using google_breakpad::AutoTempDir; +using google_breakpad::MinidumpGenerator; +using google_breakpad::MachPortSender; +using google_breakpad::MachReceiveMessage; +using google_breakpad::MachSendMessage; +using google_breakpad::Minidump; +using google_breakpad::MinidumpContext; +using google_breakpad::MinidumpException; +using google_breakpad::MinidumpModule; +using google_breakpad::MinidumpModuleList; +using google_breakpad::MinidumpSystemInfo; +using google_breakpad::MinidumpThread; +using google_breakpad::MinidumpThreadList; +using google_breakpad::ReceivePort; +using testing::Test; +using namespace google_breakpad_test; + +class MinidumpGeneratorTest : public Test { + public: + AutoTempDir tempDir; +}; + +static void *Junk(void* data) { + bool* wait = reinterpret_cast<bool*>(data); + while (!*wait) { + usleep(10000); + } + return NULL; +} + +TEST_F(MinidumpGeneratorTest, InProcess) { + MinidumpGenerator generator; + string dump_filename = + MinidumpGenerator::UniqueNameInDirectory(tempDir.path(), NULL); + + // Run an extra thread since MinidumpGenerator assumes there + // are 2 or more threads. + pthread_t junk_thread; + bool quit = false; + ASSERT_EQ(0, pthread_create(&junk_thread, NULL, Junk, &quit)); + + ASSERT_TRUE(generator.Write(dump_filename.c_str())); + // Ensure that minidump file exists and is > 0 bytes. + struct stat st; + ASSERT_EQ(0, stat(dump_filename.c_str(), &st)); + ASSERT_LT(0, st.st_size); + + // join the background thread + quit = true; + pthread_join(junk_thread, NULL); + + // Read the minidump, sanity check some data. + Minidump minidump(dump_filename.c_str()); + ASSERT_TRUE(minidump.Read()); + + MinidumpSystemInfo* system_info = minidump.GetSystemInfo(); + ASSERT_TRUE(system_info); + const MDRawSystemInfo* raw_info = system_info->system_info(); + ASSERT_TRUE(raw_info); + EXPECT_EQ(kNativeArchitecture, raw_info->processor_architecture); + + MinidumpThreadList* thread_list = minidump.GetThreadList(); + ASSERT_TRUE(thread_list); + ASSERT_EQ((unsigned int)1, thread_list->thread_count()); + + MinidumpThread* main_thread = thread_list->GetThreadAtIndex(0); + ASSERT_TRUE(main_thread); + MinidumpContext* context = main_thread->GetContext(); + ASSERT_TRUE(context); + EXPECT_EQ(kNativeContext, context->GetContextCPU()); + + MinidumpModuleList* module_list = minidump.GetModuleList(); + ASSERT_TRUE(module_list); + const MinidumpModule* main_module = module_list->GetMainModule(); + ASSERT_TRUE(main_module); + EXPECT_EQ(GetExecutablePath(), main_module->code_file()); +} + +TEST_F(MinidumpGeneratorTest, OutOfProcess) { + const int kTimeoutMs = 2000; + // Create a mach port to receive the child task on. + char machPortName[128]; + sprintf(machPortName, "MinidumpGeneratorTest.OutOfProcess.%d", getpid()); + ReceivePort parent_recv_port(machPortName); + + // Give the child process a pipe to block on. + int fds[2]; + ASSERT_EQ(0, pipe(fds)); + + // Fork off a child process to dump. + pid_t pid = fork(); + if (pid == 0) { + // In the child process + close(fds[1]); + + // Send parent process the task port. + MachSendMessage child_message(0); + child_message.AddDescriptor(mach_task_self()); + + MachPortSender child_sender(machPortName); + if (child_sender.SendMessage(child_message, kTimeoutMs) != KERN_SUCCESS) { + fprintf(stderr, "Error sending message from child process!\n"); + exit(1); + } + + // Wait for the parent process. + uint8_t data; + read(fds[0], &data, 1); + exit(0); + } + // In the parent process. + ASSERT_NE(-1, pid); + close(fds[0]); + + // Read the child's task port. + MachReceiveMessage child_message; + ASSERT_EQ(KERN_SUCCESS, + parent_recv_port.WaitForMessage(&child_message, kTimeoutMs)); + mach_port_t child_task = child_message.GetTranslatedPort(0); + ASSERT_NE((mach_port_t)MACH_PORT_NULL, child_task); + + // Write a minidump of the child process. + MinidumpGenerator generator(child_task, MACH_PORT_NULL); + string dump_filename = + MinidumpGenerator::UniqueNameInDirectory(tempDir.path(), NULL); + ASSERT_TRUE(generator.Write(dump_filename.c_str())); + + // Ensure that minidump file exists and is > 0 bytes. + struct stat st; + ASSERT_EQ(0, stat(dump_filename.c_str(), &st)); + ASSERT_LT(0, st.st_size); + + // Unblock child process + uint8_t data = 1; + (void)write(fds[1], &data, 1); + + // Child process should have exited with a zero status. + int ret; + ASSERT_EQ(pid, waitpid(pid, &ret, 0)); + EXPECT_NE(0, WIFEXITED(ret)); + EXPECT_EQ(0, WEXITSTATUS(ret)); + + // Read the minidump, sanity check some data. + Minidump minidump(dump_filename.c_str()); + ASSERT_TRUE(minidump.Read()); + + MinidumpSystemInfo* system_info = minidump.GetSystemInfo(); + ASSERT_TRUE(system_info); + const MDRawSystemInfo* raw_info = system_info->system_info(); + ASSERT_TRUE(raw_info); + EXPECT_EQ(kNativeArchitecture, raw_info->processor_architecture); + + MinidumpThreadList* thread_list = minidump.GetThreadList(); + ASSERT_TRUE(thread_list); + ASSERT_EQ((unsigned int)1, thread_list->thread_count()); + + MinidumpThread* main_thread = thread_list->GetThreadAtIndex(0); + ASSERT_TRUE(main_thread); + MinidumpContext* context = main_thread->GetContext(); + ASSERT_TRUE(context); + EXPECT_EQ(kNativeContext, context->GetContextCPU()); + + MinidumpModuleList* module_list = minidump.GetModuleList(); + ASSERT_TRUE(module_list); + const MinidumpModule* main_module = module_list->GetMainModule(); + ASSERT_TRUE(main_module); + EXPECT_EQ(GetExecutablePath(), main_module->code_file()); +} + +// This test fails on 10.5, but I don't have easy access to a 10.5 machine, +// so it's simpler to just limit it to 10.6 for now. +#if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6) && \ + (defined(__x86_64__) || defined(__i386__)) + +TEST_F(MinidumpGeneratorTest, CrossArchitectureDump) { + const int kTimeoutMs = 5000; + // Create a mach port to receive the child task on. + char machPortName[128]; + sprintf(machPortName, + "MinidumpGeneratorTest.CrossArchitectureDump.%d", getpid()); + + ReceivePort parent_recv_port(machPortName); + + // Spawn a child process to dump. + string helper_path = GetHelperPath(); + const char* argv[] = { + helper_path.c_str(), + machPortName, + NULL + }; + pid_t pid = spawn_child_process(argv); + ASSERT_NE(-1, pid); + + // Read the child's task port. + MachReceiveMessage child_message; + ASSERT_EQ(KERN_SUCCESS, + parent_recv_port.WaitForMessage(&child_message, kTimeoutMs)); + mach_port_t child_task = child_message.GetTranslatedPort(0); + ASSERT_NE((mach_port_t)MACH_PORT_NULL, child_task); + + // Write a minidump of the child process. + MinidumpGenerator generator(child_task, MACH_PORT_NULL); + string dump_filename = + MinidumpGenerator::UniqueNameInDirectory(tempDir.path(), NULL); + ASSERT_TRUE(generator.Write(dump_filename.c_str())); + + // Ensure that minidump file exists and is > 0 bytes. + struct stat st; + ASSERT_EQ(0, stat(dump_filename.c_str(), &st)); + ASSERT_LT(0, st.st_size); + + // Kill child process. + kill(pid, SIGKILL); + + int ret; + ASSERT_EQ(pid, waitpid(pid, &ret, 0)); + +const MDCPUArchitecture kExpectedArchitecture = +#if defined(__x86_64__) + MD_CPU_ARCHITECTURE_X86 +#elif defined(__i386__) + MD_CPU_ARCHITECTURE_AMD64 +#endif + ; +const uint32_t kExpectedContext = +#if defined(__i386__) + MD_CONTEXT_AMD64 +#elif defined(__x86_64__) + MD_CONTEXT_X86 +#endif + ; + + // Read the minidump, sanity check some data. + Minidump minidump(dump_filename.c_str()); + ASSERT_TRUE(minidump.Read()); + + MinidumpSystemInfo* system_info = minidump.GetSystemInfo(); + ASSERT_TRUE(system_info); + const MDRawSystemInfo* raw_info = system_info->system_info(); + ASSERT_TRUE(raw_info); + EXPECT_EQ(kExpectedArchitecture, raw_info->processor_architecture); + + MinidumpThreadList* thread_list = minidump.GetThreadList(); + ASSERT_TRUE(thread_list); + ASSERT_EQ((unsigned int)1, thread_list->thread_count()); + + MinidumpThread* main_thread = thread_list->GetThreadAtIndex(0); + ASSERT_TRUE(main_thread); + MinidumpContext* context = main_thread->GetContext(); + ASSERT_TRUE(context); + EXPECT_EQ(kExpectedContext, context->GetContextCPU()); + + MinidumpModuleList* module_list = minidump.GetModuleList(); + ASSERT_TRUE(module_list); + const MinidumpModule* main_module = module_list->GetMainModule(); + ASSERT_TRUE(main_module); + EXPECT_EQ(helper_path, main_module->code_file()); +} +#endif // 10.6 && (x86-64 || i386) + +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/tests/minidump_generator_test_helper.cc b/toolkit/crashreporter/google-breakpad/src/client/mac/tests/minidump_generator_test_helper.cc new file mode 100644 index 000000000..4e8ce3cf0 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/tests/minidump_generator_test_helper.cc @@ -0,0 +1,74 @@ +// Copyright (c) 2010, 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. + +// minidump_generator_test_helper.cc: A helper program that +// minidump_generator_test.cc can launch to test certain things +// that require a separate executable. + +#include <unistd.h> + +#include "client/mac/handler/exception_handler.h" +#include "common/mac/MachIPC.h" + +using google_breakpad::MachPortSender; +using google_breakpad::MachReceiveMessage; +using google_breakpad::MachSendMessage; +using google_breakpad::ReceivePort; + +int main(int argc, char** argv) { + if (argc < 2) + return 1; + + if (strcmp(argv[1], "crash") != 0) { + const int kTimeoutMs = 2000; + // Send parent process the task and thread ports. + MachSendMessage child_message(0); + child_message.AddDescriptor(mach_task_self()); + child_message.AddDescriptor(mach_thread_self()); + + MachPortSender child_sender(argv[1]); + if (child_sender.SendMessage(child_message, kTimeoutMs) != KERN_SUCCESS) { + fprintf(stderr, "Error sending message from child process!\n"); + exit(1); + } + + // Loop forever. + while (true) { + sleep(100); + } + } else if (argc == 3 && strcmp(argv[1], "crash") == 0) { + // Instantiate an OOP exception handler + google_breakpad::ExceptionHandler eh("", NULL, NULL, NULL, true, argv[2]); + // and crash. + int *a = (int*)0x42; + *a = 1; + } + + return 0; +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/tests/spawn_child_process.h b/toolkit/crashreporter/google-breakpad/src/client/mac/tests/spawn_child_process.h new file mode 100644 index 000000000..e52ff6b65 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/tests/spawn_child_process.h @@ -0,0 +1,149 @@ +// Copyright (c) 2010, 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. + +// Utility functions for spawning a helper process using a different +// CPU architecture. + +#ifndef GOOGLE_BREAKPAD_CLIENT_MAC_TESTS_SPAWN_CHILD_PROCESS +#define GOOGLE_BREAKPAD_CLIENT_MAC_TESTS_SPAWN_CHILD_PROCESS + +#include <AvailabilityMacros.h> +#ifndef MAC_OS_X_VERSION_10_6 +#define MAC_OS_X_VERSION_10_6 1060 +#endif +#include <crt_externs.h> +#include <mach-o/dyld.h> +#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6 +#include <spawn.h> +#endif + +#include <string> +#include <vector> + +#include "google_breakpad/common/minidump_format.h" + +namespace google_breakpad_test { + +using std::string; +using std::vector; + +const MDCPUArchitecture kNativeArchitecture = +#if defined(__i386__) + MD_CPU_ARCHITECTURE_X86 +#elif defined(__x86_64__) + MD_CPU_ARCHITECTURE_AMD64 +#elif defined(__ppc__) || defined(__ppc64__) + MD_CPU_ARCHITECTURE_PPC +#else +#error "This file has not been ported to this CPU architecture." +#endif + ; + +const uint32_t kNativeContext = +#if defined(__i386__) + MD_CONTEXT_X86 +#elif defined(__x86_64__) + MD_CONTEXT_AMD64 +#elif defined(__ppc__) || defined(__ppc64__) + MD_CONTEXT_PPC +#else +#error "This file has not been ported to this CPU architecture." +#endif + ; + +string GetExecutablePath() { + char self_path[PATH_MAX]; + uint32_t size = sizeof(self_path); + if (_NSGetExecutablePath(self_path, &size) != 0) + return ""; + return self_path; +} + +string GetHelperPath() { + string helper_path(GetExecutablePath()); + size_t pos = helper_path.rfind('/'); + if (pos == string::npos) + return ""; + + helper_path.erase(pos + 1); + helper_path += "minidump_generator_test_helper"; + return helper_path; +} + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6 + +pid_t spawn_child_process(const char** argv) { + posix_spawnattr_t spawnattr; + if (posix_spawnattr_init(&spawnattr) != 0) + return (pid_t)-1; + + cpu_type_t pref_cpu_types[2] = { +#if defined(__x86_64__) + CPU_TYPE_X86, +#elif defined(__i386__) + CPU_TYPE_X86_64, +#endif + CPU_TYPE_ANY + }; + + // Set spawn attributes. + size_t attr_count = sizeof(pref_cpu_types) / sizeof(pref_cpu_types[0]); + size_t attr_ocount = 0; + if (posix_spawnattr_setbinpref_np(&spawnattr, + attr_count, + pref_cpu_types, + &attr_ocount) != 0 || + attr_ocount != attr_count) { + posix_spawnattr_destroy(&spawnattr); + return (pid_t)-1; + } + + // Create an argv array. + vector<char*> argv_v; + while (*argv) { + argv_v.push_back(strdup(*argv)); + argv++; + } + argv_v.push_back(NULL); + pid_t new_pid = 0; + int result = posix_spawnp(&new_pid, argv_v[0], NULL, &spawnattr, + &argv_v[0], *_NSGetEnviron()); + posix_spawnattr_destroy(&spawnattr); + + for (unsigned i = 0; i < argv_v.size(); i++) { + free(argv_v[i]); + } + + return result == 0 ? new_pid : -1; +} +#endif + +} // namespace google_breakpad_test + +#endif // GOOGLE_BREAKPAD_CLIENT_MAC_TESTS_SPAWN_CHILD_PROCESS diff --git a/toolkit/crashreporter/google-breakpad/src/client/mac/tests/testlogging.h b/toolkit/crashreporter/google-breakpad/src/client/mac/tests/testlogging.h new file mode 100644 index 000000000..c6b6be699 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/mac/tests/testlogging.h @@ -0,0 +1,9 @@ +// This file exists to override the processor logging for unit tests, +// since it confuses XCode into thinking unit tests have failed. +#include <sstream> + +namespace google_breakpad { +extern std::ostringstream info_log; +} + +#define BPLOG_INFO_STREAM google_breakpad::info_log diff --git a/toolkit/crashreporter/google-breakpad/src/client/minidump_file_writer-inl.h b/toolkit/crashreporter/google-breakpad/src/client/minidump_file_writer-inl.h new file mode 100644 index 000000000..0e12e00b6 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/minidump_file_writer-inl.h @@ -0,0 +1,97 @@ +// Copyright (c) 2006, 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. + +// minidump_file_writer-inl.h: Minidump file writer implementation. +// +// See minidump_file_writer.h for documentation. + +#ifndef CLIENT_MINIDUMP_FILE_WRITER_INL_H__ +#define CLIENT_MINIDUMP_FILE_WRITER_INL_H__ + +#include <assert.h> + +#include "client/minidump_file_writer.h" +#include "google_breakpad/common/minidump_size.h" + +namespace google_breakpad { + +template<typename MDType> +inline bool TypedMDRVA<MDType>::Allocate() { + allocation_state_ = SINGLE_OBJECT; + return UntypedMDRVA::Allocate(minidump_size<MDType>::size()); +} + +template<typename MDType> +inline bool TypedMDRVA<MDType>::Allocate(size_t additional) { + allocation_state_ = SINGLE_OBJECT; + return UntypedMDRVA::Allocate(minidump_size<MDType>::size() + additional); +} + +template<typename MDType> +inline bool TypedMDRVA<MDType>::AllocateArray(size_t count) { + assert(count); + allocation_state_ = ARRAY; + return UntypedMDRVA::Allocate(minidump_size<MDType>::size() * count); +} + +template<typename MDType> +inline bool TypedMDRVA<MDType>::AllocateObjectAndArray(size_t count, + size_t length) { + assert(count && length); + allocation_state_ = SINGLE_OBJECT_WITH_ARRAY; + return UntypedMDRVA::Allocate(minidump_size<MDType>::size() + count * length); +} + +template<typename MDType> +inline bool TypedMDRVA<MDType>::CopyIndex(unsigned int index, MDType *item) { + assert(allocation_state_ == ARRAY); + return writer_->Copy( + static_cast<MDRVA>(position_ + index * minidump_size<MDType>::size()), + item, minidump_size<MDType>::size()); +} + +template<typename MDType> +inline bool TypedMDRVA<MDType>::CopyIndexAfterObject(unsigned int index, + const void *src, + size_t length) { + assert(allocation_state_ == SINGLE_OBJECT_WITH_ARRAY); + return writer_->Copy( + static_cast<MDRVA>(position_ + minidump_size<MDType>::size() + + index * length), + src, length); +} + +template<typename MDType> +inline bool TypedMDRVA<MDType>::Flush() { + return writer_->Copy(position_, &data_, minidump_size<MDType>::size()); +} + +} // namespace google_breakpad + +#endif // CLIENT_MINIDUMP_FILE_WRITER_INL_H__ diff --git a/toolkit/crashreporter/google-breakpad/src/client/minidump_file_writer.cc b/toolkit/crashreporter/google-breakpad/src/client/minidump_file_writer.cc new file mode 100644 index 000000000..a1957f324 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/minidump_file_writer.cc @@ -0,0 +1,350 @@ +// Copyright (c) 2006, 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. + +// minidump_file_writer.cc: Minidump file writer implementation. +// +// See minidump_file_writer.h for documentation. + +#include <fcntl.h> +#include <limits.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "client/minidump_file_writer-inl.h" +#include "common/linux/linux_libc_support.h" +#include "common/string_conversion.h" +#if defined(__linux__) && __linux__ +#include "third_party/lss/linux_syscall_support.h" +#endif + +#if defined(__ANDROID__) +#include <errno.h> + +namespace { + +bool g_need_ftruncate_workaround = false; +bool g_checked_need_ftruncate_workaround = false; + +void CheckNeedsFTruncateWorkAround(int file) { + if (g_checked_need_ftruncate_workaround) { + return; + } + g_checked_need_ftruncate_workaround = true; + + // Attempt an idempotent truncate that chops off nothing and see if we + // run into any sort of errors. + off_t offset = sys_lseek(file, 0, SEEK_END); + if (offset == -1) { + // lseek failed. Don't apply work around. It's unlikely that we can write + // to a minidump with either method. + return; + } + + int result = ftruncate(file, offset); + if (result == -1 && errno == EACCES) { + // It very likely that we are running into the kernel bug in M devices. + // We are going to deploy the workaround for writing minidump files + // without uses of ftruncate(). This workaround should be fine even + // for kernels without the bug. + // See http://crbug.com/542840 for more details. + g_need_ftruncate_workaround = true; + } +} + +bool NeedsFTruncateWorkAround() { + return g_need_ftruncate_workaround; +} + +} // namespace +#endif // defined(__ANDROID__) + +namespace google_breakpad { + +const MDRVA MinidumpFileWriter::kInvalidMDRVA = static_cast<MDRVA>(-1); + +MinidumpFileWriter::MinidumpFileWriter() + : file_(-1), + close_file_when_destroyed_(true), + position_(0), + size_(0) { +} + +MinidumpFileWriter::~MinidumpFileWriter() { + if (close_file_when_destroyed_) + Close(); +} + +bool MinidumpFileWriter::Open(const char *path) { + assert(file_ == -1); +#if defined(__linux__) && __linux__ + file_ = sys_open(path, O_WRONLY | O_CREAT | O_EXCL, 0600); +#else + file_ = open(path, O_WRONLY | O_CREAT | O_EXCL, 0600); +#endif + + return file_ != -1; +} + +void MinidumpFileWriter::SetFile(const int file) { + assert(file_ == -1); + file_ = file; + close_file_when_destroyed_ = false; +#if defined(__ANDROID__) + CheckNeedsFTruncateWorkAround(file); +#endif +} + +bool MinidumpFileWriter::Close() { + bool result = true; + + if (file_ != -1) { +#if defined(__ANDROID__) + if (!NeedsFTruncateWorkAround() && ftruncate(file_, position_)) { + return false; + } +#else + if (ftruncate(file_, position_)) { + return false; + } +#endif +#if defined(__linux__) && __linux__ + result = (sys_close(file_) == 0); +#else + result = (close(file_) == 0); +#endif + file_ = -1; + } + + return result; +} + +bool MinidumpFileWriter::CopyStringToMDString(const wchar_t *str, + unsigned int length, + TypedMDRVA<MDString> *mdstring) { + bool result = true; + if (sizeof(wchar_t) == sizeof(uint16_t)) { + // Shortcut if wchar_t is the same size as MDString's buffer + result = mdstring->Copy(str, mdstring->get()->length); + } else { + uint16_t out[2]; + int out_idx = 0; + + // Copy the string character by character + while (length && result) { + UTF32ToUTF16Char(*str, out); + if (!out[0]) + return false; + + // Process one character at a time + --length; + ++str; + + // Append the one or two UTF-16 characters. The first one will be non- + // zero, but the second one may be zero, depending on the conversion from + // UTF-32. + int out_count = out[1] ? 2 : 1; + size_t out_size = sizeof(uint16_t) * out_count; + result = mdstring->CopyIndexAfterObject(out_idx, out, out_size); + out_idx += out_count; + } + } + return result; +} + +bool MinidumpFileWriter::CopyStringToMDString(const char *str, + unsigned int length, + TypedMDRVA<MDString> *mdstring) { + bool result = true; + uint16_t out[2]; + int out_idx = 0; + + // Copy the string character by character + while (length && result) { + int conversion_count = UTF8ToUTF16Char(str, length, out); + if (!conversion_count) + return false; + + // Move the pointer along based on the nubmer of converted characters + length -= conversion_count; + str += conversion_count; + + // Append the one or two UTF-16 characters + int out_count = out[1] ? 2 : 1; + size_t out_size = sizeof(uint16_t) * out_count; + result = mdstring->CopyIndexAfterObject(out_idx, out, out_size); + out_idx += out_count; + } + return result; +} + +template <typename CharType> +bool MinidumpFileWriter::WriteStringCore(const CharType *str, + unsigned int length, + MDLocationDescriptor *location) { + assert(str); + assert(location); + // Calculate the mdstring length by either limiting to |length| as passed in + // or by finding the location of the NULL character. + unsigned int mdstring_length = 0; + if (!length) + length = INT_MAX; + for (; mdstring_length < length && str[mdstring_length]; ++mdstring_length) + ; + + // Allocate the string buffer + TypedMDRVA<MDString> mdstring(this); + if (!mdstring.AllocateObjectAndArray(mdstring_length + 1, sizeof(uint16_t))) + return false; + + // Set length excluding the NULL and copy the string + mdstring.get()->length = + static_cast<uint32_t>(mdstring_length * sizeof(uint16_t)); + bool result = CopyStringToMDString(str, mdstring_length, &mdstring); + + // NULL terminate + if (result) { + uint16_t ch = 0; + result = mdstring.CopyIndexAfterObject(mdstring_length, &ch, sizeof(ch)); + + if (result) + *location = mdstring.location(); + } + + return result; +} + +bool MinidumpFileWriter::WriteString(const wchar_t *str, unsigned int length, + MDLocationDescriptor *location) { + return WriteStringCore(str, length, location); +} + +bool MinidumpFileWriter::WriteString(const char *str, unsigned int length, + MDLocationDescriptor *location) { + return WriteStringCore(str, length, location); +} + +bool MinidumpFileWriter::WriteMemory(const void *src, size_t size, + MDMemoryDescriptor *output) { + assert(src); + assert(output); + UntypedMDRVA mem(this); + + if (!mem.Allocate(size)) + return false; + if (!mem.Copy(src, mem.size())) + return false; + + output->start_of_memory_range = reinterpret_cast<uint64_t>(src); + output->memory = mem.location(); + + return true; +} + +MDRVA MinidumpFileWriter::Allocate(size_t size) { + assert(size); + assert(file_ != -1); +#if defined(__ANDROID__) + if (NeedsFTruncateWorkAround()) { + // If ftruncate() is not available. We simply increase the size beyond the + // current file size. sys_write() will expand the file when data is written + // to it. Because we did not over allocate to fit memory pages, we also + // do not need to ftruncate() the file once we are done. + size_ += size; + + // We don't need to seek since the file is unchanged. + MDRVA current_position = position_; + position_ += static_cast<MDRVA>(size); + return current_position; + } +#endif + size_t aligned_size = (size + 7) & ~7; // 64-bit alignment + + if (position_ + aligned_size > size_) { + size_t growth = aligned_size; + size_t minimal_growth = getpagesize(); + + // Ensure that the file grows by at least the size of a memory page + if (growth < minimal_growth) + growth = minimal_growth; + + size_t new_size = size_ + growth; + if (ftruncate(file_, new_size) != 0) + return kInvalidMDRVA; + + size_ = new_size; + } + + MDRVA current_position = position_; + position_ += static_cast<MDRVA>(aligned_size); + + return current_position; +} + +bool MinidumpFileWriter::Copy(MDRVA position, const void *src, ssize_t size) { + assert(src); + assert(size); + assert(file_ != -1); + + // Ensure that the data will fit in the allocated space + if (static_cast<size_t>(size + position) > size_) + return false; + + // Seek and write the data +#if defined(__linux__) && __linux__ + if (sys_lseek(file_, position, SEEK_SET) == static_cast<off_t>(position)) { + if (sys_write(file_, src, size) == size) { + return true; + } + } +#else + if (lseek(file_, position, SEEK_SET) == static_cast<off_t>(position)) { + if (write(file_, src, size) == size) { + return true; + } + } +#endif + return false; +} + +bool UntypedMDRVA::Allocate(size_t size) { + assert(size_ == 0); + size_ = size; + position_ = writer_->Allocate(size_); + return position_ != MinidumpFileWriter::kInvalidMDRVA; +} + +bool UntypedMDRVA::Copy(MDRVA pos, const void *src, size_t size) { + assert(src); + assert(size); + assert(pos + size <= position_ + size_); + return writer_->Copy(pos, src, size); +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/minidump_file_writer.h b/toolkit/crashreporter/google-breakpad/src/client/minidump_file_writer.h new file mode 100644 index 000000000..ce32b6d08 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/minidump_file_writer.h @@ -0,0 +1,272 @@ +// Copyright (c) 2006, 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. + +// minidump_file_writer.h: Implements file-based minidump generation. It's +// intended to be used with the Google Breakpad open source crash handling +// project. + +#ifndef CLIENT_MINIDUMP_FILE_WRITER_H__ +#define CLIENT_MINIDUMP_FILE_WRITER_H__ + +#include <string> + +#include "google_breakpad/common/minidump_format.h" + +namespace google_breakpad { + +class UntypedMDRVA; +template<typename MDType> class TypedMDRVA; + +// The user of this class can Open() a file and add minidump streams, data, and +// strings using the definitions in minidump_format.h. Since this class is +// expected to be used in a situation where the current process may be +// damaged, it will not allocate heap memory. +// Sample usage: +// MinidumpFileWriter writer; +// writer.Open("/tmp/minidump.dmp"); +// TypedMDRVA<MDRawHeader> header(&writer_); +// header.Allocate(); +// header->get()->signature = MD_HEADER_SIGNATURE; +// : +// writer.Close(); +// +// An alternative is to use SetFile and provide a file descriptor: +// MinidumpFileWriter writer; +// writer.SetFile(minidump_fd); +// TypedMDRVA<MDRawHeader> header(&writer_); +// header.Allocate(); +// header->get()->signature = MD_HEADER_SIGNATURE; +// : +// writer.Close(); + +class MinidumpFileWriter { +public: + // Invalid MDRVA (Minidump Relative Virtual Address) + // returned on failed allocation + static const MDRVA kInvalidMDRVA; + + MinidumpFileWriter(); + ~MinidumpFileWriter(); + + // Open |path| as the destination of the minidump data. If |path| already + // exists, then Open() will fail. + // Return true on success, or false on failure. + bool Open(const char *path); + + // Sets the file descriptor |file| as the destination of the minidump data. + // Can be used as an alternative to Open() when a file descriptor is + // available. + // Note that |fd| is not closed when the instance of MinidumpFileWriter is + // destroyed. + void SetFile(const int file); + + // Close the current file (that was either created when Open was called, or + // specified with SetFile). + // Return true on success, or false on failure. + bool Close(); + + // Copy the contents of |str| to a MDString and write it to the file. + // |str| is expected to be either UTF-16 or UTF-32 depending on the size + // of wchar_t. + // Maximum |length| of characters to copy from |str|, or specify 0 to use the + // entire NULL terminated string. Copying will stop at the first NULL. + // |location| the allocated location + // Return true on success, or false on failure + bool WriteString(const wchar_t *str, unsigned int length, + MDLocationDescriptor *location); + + // Same as above, except with |str| as a UTF-8 string + bool WriteString(const char *str, unsigned int length, + MDLocationDescriptor *location); + + // Write |size| bytes starting at |src| into the current position. + // Return true on success and set |output| to position, or false on failure + bool WriteMemory(const void *src, size_t size, MDMemoryDescriptor *output); + + // Copies |size| bytes from |src| to |position| + // Return true on success, or false on failure + bool Copy(MDRVA position, const void *src, ssize_t size); + + // Return the current position for writing to the minidump + inline MDRVA position() const { return position_; } + + private: + friend class UntypedMDRVA; + + // Allocates an area of |size| bytes. + // Returns the position of the allocation, or kInvalidMDRVA if it was + // unable to allocate the bytes. + MDRVA Allocate(size_t size); + + // The file descriptor for the output file. + int file_; + + // Whether |file_| should be closed when the instance is destroyed. + bool close_file_when_destroyed_; + + // Current position in buffer + MDRVA position_; + + // Current allocated size + size_t size_; + + // Copy |length| characters from |str| to |mdstring|. These are distinct + // because the underlying MDString is a UTF-16 based string. The wchar_t + // variant may need to create a MDString that has more characters than the + // source |str|, whereas the UTF-8 variant may coalesce characters to form + // a single UTF-16 character. + bool CopyStringToMDString(const wchar_t *str, unsigned int length, + TypedMDRVA<MDString> *mdstring); + bool CopyStringToMDString(const char *str, unsigned int length, + TypedMDRVA<MDString> *mdstring); + + // The common templated code for writing a string + template <typename CharType> + bool WriteStringCore(const CharType *str, unsigned int length, + MDLocationDescriptor *location); +}; + +// Represents an untyped allocated chunk +class UntypedMDRVA { + public: + explicit UntypedMDRVA(MinidumpFileWriter *writer) + : writer_(writer), + position_(writer->position()), + size_(0) {} + + // Allocates |size| bytes. Must not call more than once. + // Return true on success, or false on failure + bool Allocate(size_t size); + + // Returns the current position or kInvalidMDRVA if allocation failed + inline MDRVA position() const { return position_; } + + // Number of bytes allocated + inline size_t size() const { return size_; } + + // Return size and position + inline MDLocationDescriptor location() const { + MDLocationDescriptor location = { static_cast<uint32_t>(size_), + position_ }; + return location; + } + + // Copy |size| bytes starting at |src| into the minidump at |position| + // Return true on success, or false on failure + bool Copy(MDRVA position, const void *src, size_t size); + + // Copy |size| bytes from |src| to the current position + inline bool Copy(const void *src, size_t size) { + return Copy(position_, src, size); + } + + protected: + // Writer we associate with + MinidumpFileWriter *writer_; + + // Position of the start of the data + MDRVA position_; + + // Allocated size + size_t size_; +}; + +// Represents a Minidump object chunk. Additional memory can be allocated at +// the end of the object as a: +// - single allocation +// - Array of MDType objects +// - A MDType object followed by an array +template<typename MDType> +class TypedMDRVA : public UntypedMDRVA { + public: + // Constructs an unallocated MDRVA + explicit TypedMDRVA(MinidumpFileWriter *writer) + : UntypedMDRVA(writer), + data_(), + allocation_state_(UNALLOCATED) {} + + inline ~TypedMDRVA() { + // Ensure that the data_ object is written out + if (allocation_state_ != ARRAY) + Flush(); + } + + // Address of object data_ of MDType. This is not declared const as the + // typical usage will be to access the underlying |data_| object as to + // alter its contents. + MDType *get() { return &data_; } + + // Allocates minidump_size<MDType>::size() bytes. + // Must not call more than once. + // Return true on success, or false on failure + bool Allocate(); + + // Allocates minidump_size<MDType>::size() + |additional| bytes. + // Must not call more than once. + // Return true on success, or false on failure + bool Allocate(size_t additional); + + // Allocate an array of |count| elements of MDType. + // Must not call more than once. + // Return true on success, or false on failure + bool AllocateArray(size_t count); + + // Allocate an array of |count| elements of |size| after object of MDType + // Must not call more than once. + // Return true on success, or false on failure + bool AllocateObjectAndArray(size_t count, size_t size); + + // Copy |item| to |index| + // Must have been allocated using AllocateArray(). + // Return true on success, or false on failure + bool CopyIndex(unsigned int index, MDType *item); + + // Copy |size| bytes starting at |str| to |index| + // Must have been allocated using AllocateObjectAndArray(). + // Return true on success, or false on failure + bool CopyIndexAfterObject(unsigned int index, const void *src, size_t size); + + // Write data_ + bool Flush(); + + private: + enum AllocationState { + UNALLOCATED = 0, + SINGLE_OBJECT, + ARRAY, + SINGLE_OBJECT_WITH_ARRAY + }; + + MDType data_; + AllocationState allocation_state_; +}; + +} // namespace google_breakpad + +#endif // CLIENT_MINIDUMP_FILE_WRITER_H__ diff --git a/toolkit/crashreporter/google-breakpad/src/client/minidump_file_writer_unittest.cc b/toolkit/crashreporter/google-breakpad/src/client/minidump_file_writer_unittest.cc new file mode 100644 index 000000000..60c364e68 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/minidump_file_writer_unittest.cc @@ -0,0 +1,179 @@ +// Copyright (c) 2006, 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. + +// Author: waylonis@google.com (Dan Waylonis) + +/* + g++ -I../ ../common/convert_UTF.c \ + ../common/string_conversion.cc \ + minidump_file_writer.cc \ + minidump_file_writer_unittest.cc \ + -o minidump_file_writer_unittest + */ + +#include <fcntl.h> +#include <unistd.h> + +#include "minidump_file_writer-inl.h" + +using google_breakpad::MinidumpFileWriter; + +#define ASSERT_TRUE(cond) \ +if (!(cond)) { \ + fprintf(stderr, "FAILED: %s at %s:%d\n", #cond, __FILE__, __LINE__); \ + return false; \ +} + +#define ASSERT_EQ(e1, e2) ASSERT_TRUE((e1) == (e2)) +#define ASSERT_NE(e1, e2) ASSERT_TRUE((e1) != (e2)) + +struct StringStructure { + unsigned long integer_value; + MDLocationDescriptor first_string; + MDLocationDescriptor second_string; +}; + +struct ArrayStructure { + unsigned char char_value; + unsigned short short_value; + unsigned long long_value; +}; + +typedef struct { + unsigned long count; + ArrayStructure array[0]; +} ObjectAndArrayStructure; + +static bool WriteFile(const char *path) { + MinidumpFileWriter writer; + if (writer.Open(path)) { + // Test a single structure + google_breakpad::TypedMDRVA<StringStructure> strings(&writer); + ASSERT_TRUE(strings.Allocate()); + strings.get()->integer_value = 0xBEEF; + const char *first = "First String"; + ASSERT_TRUE(writer.WriteString(first, 0, &strings.get()->first_string)); + const wchar_t *second = L"Second String"; + ASSERT_TRUE(writer.WriteString(second, 0, &strings.get()->second_string)); + + // Test an array structure + google_breakpad::TypedMDRVA<ArrayStructure> array(&writer); + unsigned int count = 10; + ASSERT_TRUE(array.AllocateArray(count)); + for (unsigned char i = 0; i < count; ++i) { + ArrayStructure local; + local.char_value = i; + local.short_value = i + 1; + local.long_value = i + 2; + ASSERT_TRUE(array.CopyIndex(i, &local)); + } + + // Test an object followed by an array + google_breakpad::TypedMDRVA<ObjectAndArrayStructure> obj_array(&writer); + ASSERT_TRUE(obj_array.AllocateObjectAndArray(count, + sizeof(ArrayStructure))); + obj_array.get()->count = count; + for (unsigned char i = 0; i < count; ++i) { + ArrayStructure local; + local.char_value = i; + local.short_value = i + 1; + local.long_value = i + 2; + ASSERT_TRUE(obj_array.CopyIndexAfterObject(i, &local, sizeof(local))); + } + } + + return writer.Close(); +} + +static bool CompareFile(const char *path) { + unsigned long expected[] = { +#if defined(__BIG_ENDIAN__) + 0x0000beef, 0x0000001e, 0x00000018, 0x00000020, 0x00000038, 0x00000000, + 0x00000018, 0x00460069, 0x00720073, 0x00740020, 0x00530074, 0x00720069, + 0x006e0067, 0x00000000, 0x0000001a, 0x00530065, 0x0063006f, 0x006e0064, + 0x00200053, 0x00740072, 0x0069006e, 0x00670000, 0x00000001, 0x00000002, + 0x01000002, 0x00000003, 0x02000003, 0x00000004, 0x03000004, 0x00000005, + 0x04000005, 0x00000006, 0x05000006, 0x00000007, 0x06000007, 0x00000008, + 0x07000008, 0x00000009, 0x08000009, 0x0000000a, 0x0900000a, 0x0000000b, + 0x0000000a, 0x00000001, 0x00000002, 0x01000002, 0x00000003, 0x02000003, + 0x00000004, 0x03000004, 0x00000005, 0x04000005, 0x00000006, 0x05000006, + 0x00000007, 0x06000007, 0x00000008, 0x07000008, 0x00000009, 0x08000009, + 0x0000000a, 0x0900000a, 0x0000000b, 0x00000000 +#else + 0x0000beef, 0x0000001e, 0x00000018, 0x00000020, + 0x00000038, 0x00000000, 0x00000018, 0x00690046, + 0x00730072, 0x00200074, 0x00740053, 0x00690072, + 0x0067006e, 0x00000000, 0x0000001a, 0x00650053, + 0x006f0063, 0x0064006e, 0x00530020, 0x00720074, + 0x006e0069, 0x00000067, 0x00011e00, 0x00000002, + 0x00021e01, 0x00000003, 0x00031e02, 0x00000004, + 0x00041e03, 0x00000005, 0x00051e04, 0x00000006, + 0x00061e05, 0x00000007, 0x00071e06, 0x00000008, + 0x00081e07, 0x00000009, 0x00091e08, 0x0000000a, + 0x000a1e09, 0x0000000b, 0x0000000a, 0x00011c00, + 0x00000002, 0x00021c01, 0x00000003, 0x00031c02, + 0x00000004, 0x00041c03, 0x00000005, 0x00051c04, + 0x00000006, 0x00061c05, 0x00000007, 0x00071c06, + 0x00000008, 0x00081c07, 0x00000009, 0x00091c08, + 0x0000000a, 0x000a1c09, 0x0000000b, 0x00000000, +#endif + }; + size_t expected_byte_count = sizeof(expected); + int fd = open(path, O_RDONLY, 0600); + void *buffer = malloc(expected_byte_count); + ASSERT_NE(fd, -1); + ASSERT_TRUE(buffer); + ASSERT_EQ(read(fd, buffer, expected_byte_count), + static_cast<ssize_t>(expected_byte_count)); + + char *b1, *b2; + b1 = reinterpret_cast<char*>(buffer); + b2 = reinterpret_cast<char*>(expected); + while (*b1 == *b2) { + b1++; + b2++; + } + + printf("%p\n", reinterpret_cast<void*>(b1 - (char*)buffer)); + + ASSERT_EQ(memcmp(buffer, expected, expected_byte_count), 0); + return true; +} + +static bool RunTests() { + const char *path = "/tmp/minidump_file_writer_unittest.dmp"; + ASSERT_TRUE(WriteFile(path)); + ASSERT_TRUE(CompareFile(path)); + unlink(path); + return true; +} + +extern "C" int main(int argc, const char *argv[]) { + return RunTests() ? 0 : 1; +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/moz.build b/toolkit/crashreporter/google-breakpad/src/client/moz.build new file mode 100644 index 000000000..49a1797be --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/moz.build @@ -0,0 +1,18 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SOURCES += [ + 'minidump_file_writer.cc', +] + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '..', +] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-shadow'] diff --git a/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/Makefile b/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/Makefile new file mode 100644 index 000000000..beeb9448f --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/Makefile @@ -0,0 +1,78 @@ +# Copyright (c) 2007, 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. + +# Author: Alfred Peng + +CC=cc +CXX=CC + +CPPFLAGS=-g -I../../.. -DNDEBUG -features=extensions -D_REENTRANT +LDFLAGS=-lpthread -lssl -lgnutls-openssl -lelf + +OBJ_DIR=. +BIN_DIR=. + +THREAD_SRC=solaris_lwp.cc +SHARE_SRC=../../minidump_file_writer.cc\ + ../../../common/md5.cc\ + ../../../common/string_conversion.cc\ + ../../../common/solaris/file_id.cc\ + minidump_generator.cc +HANDLER_SRC=exception_handler.cc\ + ../../../common/solaris/guid_creator.cc +SHARE_C_SRC=../../../common/convert_UTF.c + +MINIDUMP_TEST_SRC=minidump_test.cc +EXCEPTION_TEST_SRC=exception_handler_test.cc + +THREAD_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o,$(THREAD_SRC)) +SHARE_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o,$(SHARE_SRC)) +HANDLER_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o,$(HANDLER_SRC)) +SHARE_C_OBJ=$(patsubst %.c,$(OBJ_DIR)/%.o,$(SHARE_C_SRC)) +MINIDUMP_TEST_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o, $(MINIDUMP_TEST_SRC))\ + $(THREAD_OBJ) $(SHARE_OBJ) $(SHARE_C_OBJ) $(HANDLER_OBJ) +EXCEPTION_TEST_OBJ=$(patsubst %.cc,$(OBJ_DIR)/%.o, $(EXCEPTION_TEST_SRC))\ + $(THREAD_OBJ) $(SHARE_OBJ) $(SHARE_C_OBJ) $(HANDLER_OBJ) + +BIN=$(BIN_DIR)/minidump_test\ + $(BIN_DIR)/exception_handler_test + +.PHONY:all clean + +all:$(BIN) + +$(BIN_DIR)/minidump_test:$(MINIDUMP_TEST_OBJ) + $(CXX) $(CPPFLAGS) $(LDFLAGS) $^ -o $@ + +$(BIN_DIR)/exception_handler_test:$(EXCEPTION_TEST_OBJ) + $(CXX) $(CPPFLAGS) $(LDFLAGS) $^ -o $@ + +clean: + rm -f $(BIN) *.o *.out *.dmp core ../../minidump_file_writer.o\ + ../../../common/*.o ../../../common/solaris/*.o diff --git a/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/exception_handler.cc b/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/exception_handler.cc new file mode 100644 index 000000000..7fc8d2557 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/exception_handler.cc @@ -0,0 +1,258 @@ +// Copyright (c) 2007, 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. + +// Author: Alfred Peng + +#include <signal.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <cassert> +#include <cstdlib> +#include <ctime> + +#include "client/solaris/handler/exception_handler.h" +#include "common/solaris/guid_creator.h" +#include "common/solaris/message_output.h" +#include "google_breakpad/common/minidump_format.h" + +namespace google_breakpad { + +// Signals that we are interested. +static const int kSigTable[] = { + SIGSEGV, + SIGABRT, + SIGFPE, + SIGILL, + SIGBUS +}; + +std::vector<ExceptionHandler*> *ExceptionHandler::handler_stack_ = NULL; +int ExceptionHandler::handler_stack_index_ = 0; +pthread_mutex_t ExceptionHandler::handler_stack_mutex_ = + PTHREAD_MUTEX_INITIALIZER; + +ExceptionHandler::ExceptionHandler(const string &dump_path, + FilterCallback filter, + MinidumpCallback callback, + void *callback_context, + bool install_handler) + : filter_(filter), + callback_(callback), + callback_context_(callback_context), + dump_path_(), + installed_handler_(install_handler) { + set_dump_path(dump_path); + + if (install_handler) { + SetupHandler(); + } + + if (install_handler) { + pthread_mutex_lock(&handler_stack_mutex_); + + if (handler_stack_ == NULL) + handler_stack_ = new std::vector<ExceptionHandler *>; + handler_stack_->push_back(this); + pthread_mutex_unlock(&handler_stack_mutex_); + } +} + +ExceptionHandler::~ExceptionHandler() { + TeardownAllHandlers(); + pthread_mutex_lock(&handler_stack_mutex_); + if (handler_stack_->back() == this) { + handler_stack_->pop_back(); + } else { + print_message1(2, "warning: removing Breakpad handler out of order\n"); + for (std::vector<ExceptionHandler *>::iterator iterator = + handler_stack_->begin(); + iterator != handler_stack_->end(); + ++iterator) { + if (*iterator == this) { + handler_stack_->erase(iterator); + } + } + } + + if (handler_stack_->empty()) { + // When destroying the last ExceptionHandler that installed a handler, + // clean up the handler stack. + delete handler_stack_; + handler_stack_ = NULL; + } + pthread_mutex_unlock(&handler_stack_mutex_); +} + +bool ExceptionHandler::WriteMinidump() { + return InternalWriteMinidump(0, 0, NULL); +} + +// static +bool ExceptionHandler::WriteMinidump(const string &dump_path, + MinidumpCallback callback, + void *callback_context) { + ExceptionHandler handler(dump_path, NULL, callback, + callback_context, false); + return handler.InternalWriteMinidump(0, 0, NULL); +} + +void ExceptionHandler::SetupHandler() { + // Signal on a different stack to avoid using the stack + // of the crashing lwp. + struct sigaltstack sig_stack; + sig_stack.ss_sp = malloc(MINSIGSTKSZ); + if (sig_stack.ss_sp == NULL) + return; + sig_stack.ss_size = MINSIGSTKSZ; + sig_stack.ss_flags = 0; + + if (sigaltstack(&sig_stack, NULL) < 0) + return; + for (size_t i = 0; i < sizeof(kSigTable) / sizeof(kSigTable[0]); ++i) + SetupHandler(kSigTable[i]); +} + +void ExceptionHandler::SetupHandler(int signo) { + struct sigaction act, old_act; + act.sa_handler = HandleException; + act.sa_flags = SA_ONSTACK; + if (sigaction(signo, &act, &old_act) < 0) + return; + old_handlers_[signo] = old_act.sa_handler; +} + +void ExceptionHandler::TeardownHandler(int signo) { + if (old_handlers_.find(signo) != old_handlers_.end()) { + struct sigaction act; + act.sa_handler = old_handlers_[signo]; + act.sa_flags = 0; + sigaction(signo, &act, 0); + } +} + +void ExceptionHandler::TeardownAllHandlers() { + for (size_t i = 0; i < sizeof(kSigTable) / sizeof(kSigTable[0]); ++i) { + TeardownHandler(kSigTable[i]); + } +} + +// static +void ExceptionHandler::HandleException(int signo) { +//void ExceptionHandler::HandleException(int signo, siginfo_t *sip, ucontext_t *sig_ctx) { + // The context information about the signal is put on the stack of + // the signal handler frame as value parameter. For some reasons, the + // prototype of the handler doesn't declare this information as parameter, we + // will do it by hand. The stack layout for a signal handler frame is here: + // http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/lib/libproc/common/Pstack.c#81 + // + // However, if we are being called by another signal handler passing the + // signal up the chain, then we may not have this random extra parameter, + // so we may have to walk the stack to find it. We do the actual work + // on another thread, where it's a little safer, but we want the ebp + // from this frame to find it. + uintptr_t current_ebp = (uintptr_t)_getfp(); + + pthread_mutex_lock(&handler_stack_mutex_); + ExceptionHandler *current_handler = + handler_stack_->at(handler_stack_->size() - ++handler_stack_index_); + pthread_mutex_unlock(&handler_stack_mutex_); + + // Restore original handler. + current_handler->TeardownHandler(signo); + + ucontext_t *sig_ctx = NULL; + if (current_handler->InternalWriteMinidump(signo, current_ebp, &sig_ctx)) { +// if (current_handler->InternalWriteMinidump(signo, &sig_ctx)) { + // Fully handled this exception, safe to exit. + exit(EXIT_FAILURE); + } else { + // Exception not fully handled, will call the next handler in stack to + // process it. + typedef void (*SignalHandler)(int signo); + SignalHandler old_handler = + reinterpret_cast<SignalHandler>(current_handler->old_handlers_[signo]); + if (old_handler != NULL) + old_handler(signo); + } + + pthread_mutex_lock(&handler_stack_mutex_); + current_handler->SetupHandler(signo); + --handler_stack_index_; + // All the handlers in stack have been invoked to handle the exception, + // normally the process should be terminated and should not reach here. + // In case we got here, ask the OS to handle it to avoid endless loop, + // normally the OS will generate a core and termiate the process. This + // may be desired to debug the program. + if (handler_stack_index_ == 0) + signal(signo, SIG_DFL); + pthread_mutex_unlock(&handler_stack_mutex_); +} + +bool ExceptionHandler::InternalWriteMinidump(int signo, + uintptr_t sighandler_ebp, + ucontext_t **sig_ctx) { + if (filter_ && !filter_(callback_context_)) + return false; + + bool success = false; + GUID guid; + char guid_str[kGUIDStringLength + 1]; + if (CreateGUID(&guid) && GUIDToString(&guid, guid_str, sizeof(guid_str))) { + char minidump_path[PATH_MAX]; + snprintf(minidump_path, sizeof(minidump_path), "%s/%s.dmp", + dump_path_c_, guid_str); + + // Block all the signals we want to process when writing minidump. + // We don't want it to be interrupted. + sigset_t sig_blocked, sig_old; + bool blocked = true; + sigfillset(&sig_blocked); + for (size_t i = 0; i < sizeof(kSigTable) / sizeof(kSigTable[0]); ++i) + sigdelset(&sig_blocked, kSigTable[i]); + if (sigprocmask(SIG_BLOCK, &sig_blocked, &sig_old) != 0) { + blocked = false; + print_message1(2, "HandleException: failed to block signals.\n"); + } + + success = minidump_generator_.WriteMinidumpToFile( + minidump_path, signo, sighandler_ebp, sig_ctx); + + // Unblock the signals. + if (blocked) + sigprocmask(SIG_SETMASK, &sig_old, &sig_old); + + if (callback_) + success = callback_(dump_path_c_, guid_str, callback_context_, success); + } + return success; +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/exception_handler.h b/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/exception_handler.h new file mode 100644 index 000000000..4d72485fe --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/exception_handler.h @@ -0,0 +1,201 @@ +// Copyright (c) 2007, 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. +// +// Author: Alfred Peng + +#ifndef CLIENT_SOLARIS_HANDLER_EXCEPTION_HANDLER_H__ +#define CLIENT_SOLARIS_HANDLER_EXCEPTION_HANDLER_H__ + +#include <map> +#include <string> +#include <vector> + +#include "client/solaris/handler/minidump_generator.h" + +namespace google_breakpad { + +using std::string; + +// +// ExceptionHandler +// +// ExceptionHandler can write a minidump file when an exception occurs, +// or when WriteMinidump() is called explicitly by your program. +// +// To have the exception handler write minidumps when an uncaught exception +// (crash) occurs, you should create an instance early in the execution +// of your program, and keep it around for the entire time you want to +// have crash handling active (typically, until shutdown). +// (NOTE): There should be only one this kind of exception handler +// object per process. +// +// If you want to write minidumps without installing the exception handler, +// you can create an ExceptionHandler with install_handler set to false, +// then call WriteMinidump. You can also use this technique if you want to +// use different minidump callbacks for different call sites. +// +// In either case, a callback function is called when a minidump is written, +// which receives the unqiue id of the minidump. The caller can use this +// id to collect and write additional application state, and to launch an +// external crash-reporting application. +// +// Caller should try to make the callbacks as crash-friendly as possible, +// it should avoid use heap memory allocation as much as possible. +// +class ExceptionHandler { + public: + // A callback function to run before Breakpad performs any substantial + // processing of an exception. A FilterCallback is called before writing + // a minidump. context is the parameter supplied by the user as + // callback_context when the handler was created. + // + // If a FilterCallback returns true, Breakpad will continue processing, + // attempting to write a minidump. If a FilterCallback returns false, + // Breakpad will immediately report the exception as unhandled without + // writing a minidump, allowing another handler the opportunity to handle it. + typedef bool (*FilterCallback)(void *context); + + // A callback function to run after the minidump has been written. + // minidump_id is a unique id for the dump, so the minidump + // file is <dump_path>/<minidump_id>.dmp. context is the parameter supplied + // by the user as callback_context when the handler was created. succeeded + // indicates whether a minidump file was successfully written. + // + // If an exception occurred and the callback returns true, Breakpad will + // treat the exception as fully-handled, suppressing any other handlers from + // being notified of the exception. If the callback returns false, Breakpad + // will treat the exception as unhandled, and allow another handler to handle + // it. If there are no other handlers, Breakpad will report the exception to + // the system as unhandled, allowing a debugger or native crash dialog the + // opportunity to handle the exception. Most callback implementations + // should normally return the value of |succeeded|, or when they wish to + // not report an exception of handled, false. Callbacks will rarely want to + // return true directly (unless |succeeded| is true). + typedef bool (*MinidumpCallback)(const char *dump_path, + const char *minidump_id, + void *context, + bool succeeded); + + // Creates a new ExceptionHandler instance to handle writing minidumps. + // Before writing a minidump, the optional filter callback will be called. + // Its return value determines whether or not Breakpad should write a + // minidump. Minidump files will be written to dump_path, and the optional + // callback is called after writing the dump file, as described above. + // If install_handler is true, then a minidump will be written whenever + // an unhandled exception occurs. If it is false, minidumps will only + // be written when WriteMinidump is called. + ExceptionHandler(const string &dump_path, + FilterCallback filter, MinidumpCallback callback, + void *callback_context, + bool install_handler); + ~ExceptionHandler(); + + // Get and Set the minidump path. + string dump_path() const { return dump_path_; } + void set_dump_path(const string &dump_path) { + dump_path_ = dump_path; + dump_path_c_ = dump_path_.c_str(); + } + + // Writes a minidump immediately. This can be used to capture the + // execution state independently of a crash. Returns true on success. + bool WriteMinidump(); + + // Convenience form of WriteMinidump which does not require an + // ExceptionHandler instance. + static bool WriteMinidump(const string &dump_path, + MinidumpCallback callback, + void *callback_context); + + private: + // Setup crash handler. + void SetupHandler(); + // Setup signal handler for a signal. + void SetupHandler(int signo); + // Teardown the handler for a signal. + void TeardownHandler(int signo); + // Teardown all handlers. + void TeardownAllHandlers(); + + // Runs the main loop for the exception handler thread. + static void* ExceptionHandlerThreadMain(void *lpParameter); + + // Signal handler. + static void HandleException(int signo); + + // Write all the information to the dump file. + // If called from a signal handler, sighandler_ebp is the ebp of + // that signal handler's frame, and sig_ctx is an out parameter + // that will be set to point at the ucontext_t that was placed + // on the stack by the kernel. You can pass zero and NULL + // for the second and third parameters if you are not calling + // this from a signal handler. + bool InternalWriteMinidump(int signo, uintptr_t sighandler_ebp, + ucontext_t **sig_ctx); + + private: + // The callbacks before and after writing the dump file. + FilterCallback filter_; + MinidumpCallback callback_; + void *callback_context_; + + // The directory in which a minidump will be written, set by the dump_path + // argument to the constructor, or set_dump_path. + string dump_path_; + // C style dump path. Keep this when setting dump path, since calling + // c_str() of std::string when crashing may not be safe. + const char *dump_path_c_; + + // True if the ExceptionHandler installed an unhandled exception filter + // when created (with an install_handler parameter set to true). + bool installed_handler_; + + // Keep the previous handlers for the signal. + typedef void (*sighandler_t)(int); + std::map<int, sighandler_t> old_handlers_; + + // The global exception handler stack. This is need becuase there may exist + // multiple ExceptionHandler instances in a process. Each will have itself + // registered in this stack. + static std::vector<ExceptionHandler *> *handler_stack_; + // The index of the handler that should handle the next exception. + static int handler_stack_index_; + static pthread_mutex_t handler_stack_mutex_; + + // The minidump generator. + MinidumpGenerator minidump_generator_; + + // disallow copy ctor and operator= + explicit ExceptionHandler(const ExceptionHandler &); + void operator=(const ExceptionHandler &); +}; + +} // namespace google_breakpad + +#endif // CLIENT_SOLARIS_HANDLER_EXCEPTION_HANDLER_H__ diff --git a/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/exception_handler_test.cc b/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/exception_handler_test.cc new file mode 100644 index 000000000..6bb8e18d9 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/exception_handler_test.cc @@ -0,0 +1,119 @@ +// Copyright (c) 2007, 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. + +// Author: Alfred Peng + +#include <pthread.h> +#include <unistd.h> + +#include <cassert> +#include <cstdio> +#include <cstdlib> +#include <cstring> + +#include "client/solaris/handler/exception_handler.h" +#include "client/solaris/handler/solaris_lwp.h" + +using namespace google_breakpad; + +// Thread use this to see if it should stop working. +static bool should_exit = false; + +static int foo2(int arg) { + // Stack variable, used for debugging stack dumps. + int c = 0xcccccccc; + fprintf(stderr, "Thread trying to crash: %x\n", getpid()); + c = *reinterpret_cast<int *>(0x5); + return c; +} + +static int foo(int arg) { + // Stack variable, used for debugging stack dumps. + int b = 0xbbbbbbbb; + b = foo2(b); + return b; +} + +static void *thread_crash(void *) { + // Stack variable, used for debugging stack dumps. + int a = 0xaaaaaaaa; + sleep(3); + a = foo(a); + printf("%x\n", a); + return NULL; +} + +static void *thread_main(void *) { + while (!should_exit) + sleep(1); + return NULL; +} + +static void CreateCrashThread() { + pthread_t h; + pthread_create(&h, NULL, thread_crash, NULL); + pthread_detach(h); +} + +// Create working threads. +static void CreateThread(int num) { + pthread_t h; + for (int i = 0; i < num; ++i) { + pthread_create(&h, NULL, thread_main, NULL); + pthread_detach(h); + } +} + +// Callback when minidump written. +static bool MinidumpCallback(const char *dump_path, + const char *minidump_id, + void *context, + bool succeeded) { + int index = reinterpret_cast<int>(context); + if (index == 0) { + should_exit = true; + return true; + } + // Don't process it. + return false; +} + +int main(int argc, char *argv[]) { + int handler_index = 1; + ExceptionHandler handler_ignore(".", NULL, MinidumpCallback, + (void*)handler_index, true); + CreateCrashThread(); + CreateThread(10); + + while (true) + sleep(20); + should_exit = true; + + return 0; +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/minidump_generator.cc b/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/minidump_generator.cc new file mode 100644 index 000000000..7485025fe --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/minidump_generator.cc @@ -0,0 +1,786 @@ +// Copyright (c) 2007, 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. + +// Author: Alfred Peng + +#include <fcntl.h> +#include <sys/frame.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/utsname.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <cstdlib> +#include <ctime> + +#include "client/solaris/handler/minidump_generator.h" +#include "client/minidump_file_writer-inl.h" +#include "common/solaris/file_id.h" + +namespace { + +using namespace google_breakpad; + +// Argument for the writer function. +struct WriterArgument { + MinidumpFileWriter *minidump_writer; + + // Pid of the lwp who called WriteMinidumpToFile + int requester_pid; + + // The stack bottom of the lwp which caused the dump. + // Mainly used to find the lwp id of the crashed lwp since signal + // handler may not be called in the lwp who caused it. + uintptr_t crashed_stack_bottom; + + // Id of the crashing lwp. + int crashed_lwpid; + + // Signal number when crash happened. Can be 0 if this is a requested dump. + int signo; + + // The ebp of the signal handler frame on x86. Can be 0 if this is a + // requested dump. + uintptr_t sighandler_ebp; + + // User context when crash happens. Can be NULL if this is a requested dump. + // This is actually an out parameter, but it will be filled in at the start + // of the writer LWP. + ucontext_t *sig_ctx; + + // Used to get information about the lwps. + SolarisLwp *lwp_lister; +}; + +// Holding context information for the callback of finding the crashing lwp. +struct FindCrashLwpContext { + const SolarisLwp *lwp_lister; + uintptr_t crashing_stack_bottom; + int crashing_lwpid; + + FindCrashLwpContext() : + lwp_lister(NULL), + crashing_stack_bottom(0UL), + crashing_lwpid(-1) { + } +}; + +// Callback for list lwps. +// It will compare the stack bottom of the provided lwp with the stack +// bottom of the crashed lwp, it they are eqaul, this lwp is the one +// who crashed. +bool IsLwpCrashedCallback(lwpstatus_t *lsp, void *context) { + FindCrashLwpContext *crashing_context = + static_cast<FindCrashLwpContext *>(context); + const SolarisLwp *lwp_lister = crashing_context->lwp_lister; + const prgregset_t *gregs = &(lsp->pr_reg); +#if TARGET_CPU_SPARC + uintptr_t last_ebp = (*gregs)[R_FP]; +#elif TARGET_CPU_X86 + uintptr_t last_ebp = (*gregs)[EBP]; +#endif + uintptr_t stack_bottom = lwp_lister->GetLwpStackBottom(last_ebp); + if (stack_bottom > last_ebp && + stack_bottom == crashing_context->crashing_stack_bottom) { + // Got it. Stop iteration. + crashing_context->crashing_lwpid = lsp->pr_lwpid; + return false; + } + + return true; +} + +// Find the crashing lwpid. +// This is done based on stack bottom comparing. +int FindCrashingLwp(uintptr_t crashing_stack_bottom, + int requester_pid, + const SolarisLwp *lwp_lister) { + FindCrashLwpContext context; + context.lwp_lister = lwp_lister; + context.crashing_stack_bottom = crashing_stack_bottom; + CallbackParam<LwpCallback> callback_param(IsLwpCrashedCallback, + &context); + lwp_lister->Lwp_iter_all(lwp_lister->getpid(), &callback_param); + return context.crashing_lwpid; +} + +bool WriteLwpStack(const SolarisLwp *lwp_lister, + uintptr_t last_esp, + UntypedMDRVA *memory, + MDMemoryDescriptor *loc) { + uintptr_t stack_bottom = lwp_lister->GetLwpStackBottom(last_esp); + if (stack_bottom >= last_esp) { + int size = stack_bottom - last_esp; + if (size > 0) { + if (!memory->Allocate(size)) + return false; + memory->Copy(reinterpret_cast<void *>(last_esp), size); + loc->start_of_memory_range = last_esp; + loc->memory = memory->location(); + } + return true; + } + return false; +} + +#if TARGET_CPU_SPARC +bool WriteContext(MDRawContextSPARC *context, ucontext_t *sig_ctx) { + assert(sig_ctx != NULL); + int* regs = sig_ctx->uc_mcontext.gregs; + context->context_flags = MD_CONTEXT_SPARC_FULL; + + context->ccr = (unsigned int)(regs[0]); + context->pc = (unsigned int)(regs[REG_PC]); + context->npc = (unsigned int)(regs[REG_nPC]); + context->y = (unsigned int)(regs[REG_Y]); + context->asi = (unsigned int)(regs[19]); + context->fprs = (unsigned int)(regs[20]); + + for ( int i = 0 ; i < 32; ++i ) { + context->g_r[i] = 0; + } + + for ( int i = 1 ; i < 16; ++i ) { + context->g_r[i] = (uintptr_t)(sig_ctx->uc_mcontext.gregs[i + 3]); + } + context->g_r[30] = (uintptr_t)(((struct frame *)context->g_r[14])->fr_savfp); + + return true; +} + +bool WriteContext(MDRawContextSPARC *context, prgregset_t regs, + prfpregset_t *fp_regs) { + if (!context || !regs) + return false; + + context->context_flags = MD_CONTEXT_SPARC_FULL; + + context->ccr = (uintptr_t)(regs[32]); + context->pc = (uintptr_t)(regs[R_PC]); + context->npc = (uintptr_t)(regs[R_nPC]); + context->y = (uintptr_t)(regs[R_Y]); + context->asi = (uintptr_t)(regs[36]); + context->fprs = (uintptr_t)(regs[37]); + for ( int i = 0 ; i < 32 ; ++i ){ + context->g_r[i] = (uintptr_t)(regs[i]); + } + + return true; +} +#elif TARGET_CPU_X86 +bool WriteContext(MDRawContextX86 *context, prgregset_t regs, + prfpregset_t *fp_regs) { + if (!context || !regs) + return false; + + context->context_flags = MD_CONTEXT_X86_FULL; + + context->cs = regs[CS]; + context->ds = regs[DS]; + context->es = regs[ES]; + context->fs = regs[FS]; + context->gs = regs[GS]; + context->ss = regs[SS]; + context->edi = regs[EDI]; + context->esi = regs[ESI]; + context->ebx = regs[EBX]; + context->edx = regs[EDX]; + context->ecx = regs[ECX]; + context->eax = regs[EAX]; + context->ebp = regs[EBP]; + context->eip = regs[EIP]; + context->esp = regs[UESP]; + context->eflags = regs[EFL]; + + return true; +} +#endif /* TARGET_CPU_XXX */ + +// Write information about a crashed Lwp. +// When a lwp crash, kernel will write something on the stack for processing +// signal. This makes the current stack not reliable, and our stack walker +// won't figure out the whole call stack for this. So we write the stack at the +// time of the crash into the minidump file, not the current stack. +bool WriteCrashedLwpStream(MinidumpFileWriter *minidump_writer, + const WriterArgument *writer_args, + const lwpstatus_t *lsp, + MDRawThread *lwp) { + assert(writer_args->sig_ctx != NULL); + + lwp->thread_id = lsp->pr_lwpid; + +#if TARGET_CPU_SPARC + UntypedMDRVA memory(minidump_writer); + if (!WriteLwpStack(writer_args->lwp_lister, + writer_args->sig_ctx->uc_mcontext.gregs[REG_O6], + &memory, + &lwp->stack)) + return false; + + TypedMDRVA<MDRawContextSPARC> context(minidump_writer); + if (!context.Allocate()) + return false; + lwp->thread_context = context.location(); + memset(context.get(), 0, sizeof(MDRawContextSPARC)); + return WriteContext(context.get(), writer_args->sig_ctx); +#elif TARGET_CPU_X86 + UntypedMDRVA memory(minidump_writer); + if (!WriteLwpStack(writer_args->lwp_lister, + writer_args->sig_ctx->uc_mcontext.gregs[UESP], + &memory, + &lwp->stack)) + return false; + + TypedMDRVA<MDRawContextX86> context(minidump_writer); + if (!context.Allocate()) + return false; + lwp->thread_context = context.location(); + memset(context.get(), 0, sizeof(MDRawContextX86)); + return WriteContext(context.get(), + (int *)&writer_args->sig_ctx->uc_mcontext.gregs, + &writer_args->sig_ctx->uc_mcontext.fpregs); +#endif +} + +bool WriteLwpStream(MinidumpFileWriter *minidump_writer, + const SolarisLwp *lwp_lister, + const lwpstatus_t *lsp, MDRawThread *lwp) { + prfpregset_t fp_regs = lsp->pr_fpreg; + const prgregset_t *gregs = &(lsp->pr_reg); + UntypedMDRVA memory(minidump_writer); +#if TARGET_CPU_SPARC + if (!WriteLwpStack(lwp_lister, + (*gregs)[R_SP], + &memory, + &lwp->stack)) + return false; + + // Write context + TypedMDRVA<MDRawContextSPARC> context(minidump_writer); + if (!context.Allocate()) + return false; + // should be the thread_id + lwp->thread_id = lsp->pr_lwpid; + lwp->thread_context = context.location(); + memset(context.get(), 0, sizeof(MDRawContextSPARC)); +#elif TARGET_CPU_X86 + if (!WriteLwpStack(lwp_lister, + (*gregs)[UESP], + &memory, + &lwp->stack)) + return false; + + // Write context + TypedMDRVA<MDRawContextX86> context(minidump_writer); + if (!context.Allocate()) + return false; + // should be the thread_id + lwp->thread_id = lsp->pr_lwpid; + lwp->thread_context = context.location(); + memset(context.get(), 0, sizeof(MDRawContextX86)); +#endif /* TARGET_CPU_XXX */ + return WriteContext(context.get(), (int *)gregs, &fp_regs); +} + +bool WriteCPUInformation(MDRawSystemInfo *sys_info) { + struct utsname uts; + char *major, *minor, *build; + + sys_info->number_of_processors = sysconf(_SC_NPROCESSORS_CONF); + sys_info->processor_architecture = MD_CPU_ARCHITECTURE_UNKNOWN; + if (uname(&uts) != -1) { + // Match "i86pc" as X86 architecture. + if (strcmp(uts.machine, "i86pc") == 0) + sys_info->processor_architecture = MD_CPU_ARCHITECTURE_X86; + else if (strcmp(uts.machine, "sun4u") == 0) + sys_info->processor_architecture = MD_CPU_ARCHITECTURE_SPARC; + } + + major = uts.release; + minor = strchr(major, '.'); + *minor = '\0'; + ++minor; + sys_info->major_version = atoi(major); + sys_info->minor_version = atoi(minor); + + build = strchr(uts.version, '_'); + ++build; + sys_info->build_number = atoi(build); + + return true; +} + +bool WriteOSInformation(MinidumpFileWriter *minidump_writer, + MDRawSystemInfo *sys_info) { + sys_info->platform_id = MD_OS_SOLARIS; + + struct utsname uts; + if (uname(&uts) != -1) { + char os_version[512]; + size_t space_left = sizeof(os_version); + memset(os_version, 0, space_left); + const char *os_info_table[] = { + uts.sysname, + uts.release, + uts.version, + uts.machine, + "OpenSolaris", + NULL + }; + for (const char **cur_os_info = os_info_table; + *cur_os_info != NULL; + ++cur_os_info) { + if (cur_os_info != os_info_table && space_left > 1) { + strcat(os_version, " "); + --space_left; + } + if (space_left > strlen(*cur_os_info)) { + strcat(os_version, *cur_os_info); + space_left -= strlen(*cur_os_info); + } else { + break; + } + } + + MDLocationDescriptor location; + if (!minidump_writer->WriteString(os_version, 0, &location)) + return false; + sys_info->csd_version_rva = location.rva; + } + return true; +} + +// Callback context for get writting lwp information. +struct LwpInfoCallbackCtx { + MinidumpFileWriter *minidump_writer; + const WriterArgument *writer_args; + TypedMDRVA<MDRawThreadList> *list; + int lwp_index; +}; + +bool LwpInformationCallback(lwpstatus_t *lsp, void *context) { + bool success = true; + LwpInfoCallbackCtx *callback_context = + static_cast<LwpInfoCallbackCtx *>(context); + + // The current lwp is the one to handle the crash. Ignore it. + if (lsp->pr_lwpid != pthread_self()) { + LwpInfoCallbackCtx *callback_context = + static_cast<LwpInfoCallbackCtx *>(context); + MDRawThread lwp; + memset(&lwp, 0, sizeof(MDRawThread)); + + if (lsp->pr_lwpid != callback_context->writer_args->crashed_lwpid || + callback_context->writer_args->sig_ctx == NULL) { + success = WriteLwpStream(callback_context->minidump_writer, + callback_context->writer_args->lwp_lister, + lsp, &lwp); + } else { + success = WriteCrashedLwpStream(callback_context->minidump_writer, + callback_context->writer_args, + lsp, &lwp); + } + if (success) { + callback_context->list->CopyIndexAfterObject( + callback_context->lwp_index++, + &lwp, sizeof(MDRawThread)); + } + } + + return success; +} + +bool WriteLwpListStream(MinidumpFileWriter *minidump_writer, + const WriterArgument *writer_args, + MDRawDirectory *dir) { + // Get the lwp information. + const SolarisLwp *lwp_lister = writer_args->lwp_lister; + int lwp_count = lwp_lister->GetLwpCount(); + if (lwp_count < 0) + return false; + TypedMDRVA<MDRawThreadList> list(minidump_writer); + if (!list.AllocateObjectAndArray(lwp_count - 1, sizeof(MDRawThread))) + return false; + dir->stream_type = MD_THREAD_LIST_STREAM; + dir->location = list.location(); + list.get()->number_of_threads = lwp_count - 1; + + LwpInfoCallbackCtx context; + context.minidump_writer = minidump_writer; + context.writer_args = writer_args; + context.list = &list; + context.lwp_index = 0; + CallbackParam<LwpCallback> callback_param(LwpInformationCallback, + &context); + int written = + lwp_lister->Lwp_iter_all(lwp_lister->getpid(), &callback_param); + return written == lwp_count; +} + +bool WriteCVRecord(MinidumpFileWriter *minidump_writer, + MDRawModule *module, + const char *module_path, + char *realname) { + TypedMDRVA<MDCVInfoPDB70> cv(minidump_writer); + + char path[PATH_MAX]; + const char *module_name = module_path ? module_path : "<Unknown>"; + snprintf(path, sizeof(path), "/proc/self/object/%s", module_name); + + size_t module_name_length = strlen(realname); + if (!cv.AllocateObjectAndArray(module_name_length + 1, sizeof(uint8_t))) + return false; + if (!cv.CopyIndexAfterObject(0, realname, module_name_length)) + return false; + + module->cv_record = cv.location(); + MDCVInfoPDB70 *cv_ptr = cv.get(); + memset(cv_ptr, 0, sizeof(MDCVInfoPDB70)); + cv_ptr->cv_signature = MD_CVINFOPDB70_SIGNATURE; + cv_ptr->age = 0; + + // Get the module identifier + FileID file_id(path); + unsigned char identifier[16]; + + if (file_id.ElfFileIdentifier(identifier)) { + cv_ptr->signature.data1 = (uint32_t)identifier[0] << 24 | + (uint32_t)identifier[1] << 16 | (uint32_t)identifier[2] << 8 | + (uint32_t)identifier[3]; + cv_ptr->signature.data2 = (uint32_t)identifier[4] << 8 | identifier[5]; + cv_ptr->signature.data3 = (uint32_t)identifier[6] << 8 | identifier[7]; + cv_ptr->signature.data4[0] = identifier[8]; + cv_ptr->signature.data4[1] = identifier[9]; + cv_ptr->signature.data4[2] = identifier[10]; + cv_ptr->signature.data4[3] = identifier[11]; + cv_ptr->signature.data4[4] = identifier[12]; + cv_ptr->signature.data4[5] = identifier[13]; + cv_ptr->signature.data4[6] = identifier[14]; + cv_ptr->signature.data4[7] = identifier[15]; + } + return true; +} + +struct ModuleInfoCallbackCtx { + MinidumpFileWriter *minidump_writer; + const WriterArgument *writer_args; + TypedMDRVA<MDRawModuleList> *list; + int module_index; +}; + +bool ModuleInfoCallback(const ModuleInfo &module_info, void *context) { + ModuleInfoCallbackCtx *callback_context = + static_cast<ModuleInfoCallbackCtx *>(context); + // Skip those modules without name, or those that are not modules. + if (strlen(module_info.name) == 0) + return true; + + MDRawModule module; + memset(&module, 0, sizeof(module)); + MDLocationDescriptor loc; + char path[PATH_MAX]; + char buf[PATH_MAX]; + char *realname; + int count; + + snprintf(path, sizeof (path), "/proc/self/path/%s", module_info.name); + if ((count = readlink(path, buf, PATH_MAX)) < 0) + return false; + buf[count] = '\0'; + + if ((realname = strrchr(buf, '/')) == NULL) + return false; + realname++; + + if (!callback_context->minidump_writer->WriteString(realname, 0, &loc)) + return false; + + module.base_of_image = (uint64_t)module_info.start_addr; + module.size_of_image = module_info.size; + module.module_name_rva = loc.rva; + + if (!WriteCVRecord(callback_context->minidump_writer, &module, + module_info.name, realname)) + return false; + + callback_context->list->CopyIndexAfterObject( + callback_context->module_index++, &module, MD_MODULE_SIZE); + return true; +} + +bool WriteModuleListStream(MinidumpFileWriter *minidump_writer, + const WriterArgument *writer_args, + MDRawDirectory *dir) { + TypedMDRVA<MDRawModuleList> list(minidump_writer); + int module_count = writer_args->lwp_lister->GetModuleCount(); + + if (module_count <= 0 || + !list.AllocateObjectAndArray(module_count, MD_MODULE_SIZE)) { + return false; + } + + dir->stream_type = MD_MODULE_LIST_STREAM; + dir->location = list.location(); + list.get()->number_of_modules = module_count; + ModuleInfoCallbackCtx context; + context.minidump_writer = minidump_writer; + context.writer_args = writer_args; + context.list = &list; + context.module_index = 0; + CallbackParam<ModuleCallback> callback(ModuleInfoCallback, &context); + return writer_args->lwp_lister->ListModules(&callback) == module_count; +} + +bool WriteSystemInfoStream(MinidumpFileWriter *minidump_writer, + const WriterArgument *writer_args, + MDRawDirectory *dir) { + TypedMDRVA<MDRawSystemInfo> sys_info(minidump_writer); + + if (!sys_info.Allocate()) + return false; + + dir->stream_type = MD_SYSTEM_INFO_STREAM; + dir->location = sys_info.location(); + + return WriteCPUInformation(sys_info.get()) && + WriteOSInformation(minidump_writer, sys_info.get()); +} + +bool WriteExceptionStream(MinidumpFileWriter *minidump_writer, + const WriterArgument *writer_args, + MDRawDirectory *dir) { + // This happenes when this is not a crash, but a requested dump. + if (writer_args->sig_ctx == NULL) + return false; + + TypedMDRVA<MDRawExceptionStream> exception(minidump_writer); + if (!exception.Allocate()) + return false; + + dir->stream_type = MD_EXCEPTION_STREAM; + dir->location = exception.location(); + exception.get()->thread_id = writer_args->crashed_lwpid; + exception.get()->exception_record.exception_code = writer_args->signo; + exception.get()->exception_record.exception_flags = 0; + +#if TARGET_CPU_SPARC + if (writer_args->sig_ctx != NULL) { + exception.get()->exception_record.exception_address = + writer_args->sig_ctx->uc_mcontext.gregs[REG_PC]; + } else { + return true; + } + + // Write context of the exception. + TypedMDRVA<MDRawContextSPARC> context(minidump_writer); + if (!context.Allocate()) + return false; + exception.get()->thread_context = context.location(); + memset(context.get(), 0, sizeof(MDRawContextSPARC)); + return WriteContext(context.get(), writer_args->sig_ctx); +#elif TARGET_CPU_X86 + if (writer_args->sig_ctx != NULL) { + exception.get()->exception_record.exception_address = + writer_args->sig_ctx->uc_mcontext.gregs[EIP]; + } else { + return true; + } + + // Write context of the exception. + TypedMDRVA<MDRawContextX86> context(minidump_writer); + if (!context.Allocate()) + return false; + exception.get()->thread_context = context.location(); + memset(context.get(), 0, sizeof(MDRawContextX86)); + return WriteContext(context.get(), + (int *)&writer_args->sig_ctx->uc_mcontext.gregs, + NULL); +#endif +} + +bool WriteMiscInfoStream(MinidumpFileWriter *minidump_writer, + const WriterArgument *writer_args, + MDRawDirectory *dir) { + TypedMDRVA<MDRawMiscInfo> info(minidump_writer); + + if (!info.Allocate()) + return false; + + dir->stream_type = MD_MISC_INFO_STREAM; + dir->location = info.location(); + info.get()->size_of_info = sizeof(MDRawMiscInfo); + info.get()->flags1 = MD_MISCINFO_FLAGS1_PROCESS_ID; + info.get()->process_id = writer_args->requester_pid; + + return true; +} + +bool WriteBreakpadInfoStream(MinidumpFileWriter *minidump_writer, + const WriterArgument *writer_args, + MDRawDirectory *dir) { + TypedMDRVA<MDRawBreakpadInfo> info(minidump_writer); + + if (!info.Allocate()) + return false; + + dir->stream_type = MD_BREAKPAD_INFO_STREAM; + dir->location = info.location(); + + info.get()->validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID | + MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID; + info.get()->dump_thread_id = getpid(); + info.get()->requesting_thread_id = writer_args->requester_pid; + return true; +} + +class AutoLwpResumer { + public: + AutoLwpResumer(SolarisLwp *lwp) : lwp_(lwp) {} + ~AutoLwpResumer() { lwp_->ControlAllLwps(false); } + private: + SolarisLwp *lwp_; +}; + +// Prototype of writer functions. +typedef bool (*WriteStreamFN)(MinidumpFileWriter *, + const WriterArgument *, + MDRawDirectory *); + +// Function table to writer a full minidump. +const WriteStreamFN writers[] = { + WriteLwpListStream, + WriteModuleListStream, + WriteSystemInfoStream, + WriteExceptionStream, + WriteMiscInfoStream, + WriteBreakpadInfoStream, +}; + +// Will call each writer function in the writers table. +//void* MinidumpGenerator::Write(void *argument) { +void* Write(void *argument) { + WriterArgument *writer_args = static_cast<WriterArgument *>(argument); + + if (!writer_args->lwp_lister->ControlAllLwps(true)) + return NULL; + + AutoLwpResumer lwpResumer(writer_args->lwp_lister); + + if (writer_args->sighandler_ebp != 0 && + writer_args->lwp_lister->FindSigContext(writer_args->sighandler_ebp, + &writer_args->sig_ctx)) { + writer_args->crashed_stack_bottom = + writer_args->lwp_lister->GetLwpStackBottom( +#if TARGET_CPU_SPARC + writer_args->sig_ctx->uc_mcontext.gregs[REG_O6] +#elif TARGET_CPU_X86 + writer_args->sig_ctx->uc_mcontext.gregs[UESP] +#endif + ); + + int crashed_lwpid = FindCrashingLwp(writer_args->crashed_stack_bottom, + writer_args->requester_pid, + writer_args->lwp_lister); + if (crashed_lwpid > 0) + writer_args->crashed_lwpid = crashed_lwpid; + } + + MinidumpFileWriter *minidump_writer = writer_args->minidump_writer; + TypedMDRVA<MDRawHeader> header(minidump_writer); + TypedMDRVA<MDRawDirectory> dir(minidump_writer); + if (!header.Allocate()) + return 0; + + int writer_count = sizeof(writers) / sizeof(writers[0]); + // Need directory space for all writers. + if (!dir.AllocateArray(writer_count)) + return 0; + header.get()->signature = MD_HEADER_SIGNATURE; + header.get()->version = MD_HEADER_VERSION; + header.get()->time_date_stamp = time(NULL); + header.get()->stream_count = writer_count; + header.get()->stream_directory_rva = dir.position(); + + int dir_index = 0; + MDRawDirectory local_dir; + for (int i = 0; i < writer_count; ++i) { + if ((*writers[i])(minidump_writer, writer_args, &local_dir)) + dir.CopyIndex(dir_index++, &local_dir); + } + + return 0; +} + +} // namespace + +namespace google_breakpad { + +MinidumpGenerator::MinidumpGenerator() { +} + +MinidumpGenerator::~MinidumpGenerator() { +} + +// Write minidump into file. +// It runs in a different thread from the crashing thread. +bool MinidumpGenerator::WriteMinidumpToFile(const char *file_pathname, + int signo, + uintptr_t sighandler_ebp, + ucontext_t **sig_ctx) const { + // The exception handler thread. + pthread_t handler_thread; + + assert(file_pathname != NULL); + + if (file_pathname == NULL) + return false; + + MinidumpFileWriter minidump_writer; + if (minidump_writer.Open(file_pathname)) { + WriterArgument argument; + memset(&argument, 0, sizeof(argument)); + SolarisLwp lwp_lister(getpid()); + argument.lwp_lister = &lwp_lister; + argument.minidump_writer = &minidump_writer; + argument.requester_pid = getpid(); + argument.crashed_lwpid = pthread_self(); + argument.signo = signo; + argument.sighandler_ebp = sighandler_ebp; + argument.sig_ctx = NULL; + + pthread_create(&handler_thread, NULL, Write, (void *)&argument); + pthread_join(handler_thread, NULL); + return true; + } + + return false; +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/minidump_generator.h b/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/minidump_generator.h new file mode 100644 index 000000000..882f9e1de --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/minidump_generator.h @@ -0,0 +1,70 @@ +// Copyright (c) 2007, 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. + +// Author: Alfred Peng + +#ifndef CLIENT_SOLARIS_HANDLER_MINIDUMP_GENERATOR_H__ +#define CLIENT_SOLARIS_HANDLER_MINIDUMP_GENERATOR_H__ + +#include <ucontext.h> + +#include "client/minidump_file_writer.h" +#include "client/solaris/handler/solaris_lwp.h" +#include "google_breakpad/common/breakpad_types.h" +#include "google_breakpad/common/minidump_format.h" + +namespace google_breakpad { + +// +// MinidumpGenerator +// +// A minidump generator should be created before any exception happen. +// +class MinidumpGenerator { + // Callback run for writing lwp information in the process. + friend bool LwpInformationCallback(lwpstatus_t *lsp, void *context); + + // Callback run for writing module information in the process. + friend bool ModuleInfoCallback(const ModuleInfo &module_info, void *context); + + public: + MinidumpGenerator(); + + ~MinidumpGenerator(); + + // Write minidump. + bool WriteMinidumpToFile(const char *file_pathname, + int signo, + uintptr_t sighandler_ebp, + ucontext_t **sig_ctx) const; +}; + +} // namespace google_breakpad + +#endif // CLIENT_SOLARIS_HANDLER_MINIDUMP_GENERATOR_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/minidump_test.cc b/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/minidump_test.cc new file mode 100644 index 000000000..33302d86a --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/minidump_test.cc @@ -0,0 +1,75 @@ +// Copyright (c) 2007, 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. + +// Author: Alfred Peng + +#include <pthread.h> +#include <unistd.h> + +#include "client/minidump_file_writer.h" +#include "client/solaris/handler/minidump_generator.h" + +using std::string; +using google_breakpad::MinidumpGenerator; + +static bool doneWritingReport = false; + +static void *Reporter(void *) { + char buffer[PATH_MAX]; + MinidumpGenerator md; + + // Write it to the desktop + snprintf(buffer, sizeof(buffer), "./minidump_test.out"); + fprintf(stdout, "Writing %s\n", buffer); + + md.WriteMinidumpToFile(buffer, 0, 0, NULL); + doneWritingReport = true; + + return NULL; +} + +static void SleepyFunction() { + while (!doneWritingReport) { + usleep(100); + } +} + +int main(int argc, char * const argv[]) { + pthread_t reporter_thread; + + if (pthread_create(&reporter_thread, NULL, Reporter, NULL) == 0) { + pthread_detach(reporter_thread); + } else { + perror("pthread_create"); + } + + SleepyFunction(); + + return 0; +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/moz.build b/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/moz.build new file mode 100644 index 000000000..3442ac0f8 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/moz.build @@ -0,0 +1,18 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SOURCES += [ + 'exception_handler.cc', + 'minidump_generator.cc', + 'solaris_lwp.cc', +] + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '../../..', +] + diff --git a/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/solaris_lwp.cc b/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/solaris_lwp.cc new file mode 100644 index 000000000..0148997ad --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/solaris_lwp.cc @@ -0,0 +1,436 @@ +// Copyright (c) 2007, 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. + +// Author: Alfred Peng + +#include <dirent.h> +#include <elf.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <sys/frame.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <algorithm> +#include <cassert> +#include <cstdio> +#include <cstdlib> +#include <functional> + +#include "client/solaris/handler/solaris_lwp.h" +#include "common/solaris/message_output.h" + +using namespace google_breakpad; + +// This unamed namespace contains helper function. +namespace { + +uintptr_t stack_base_address = 0; +static const int HEADER_MAX = 2000; +static const int MAP_MAX = 1000; + +// Context information for the callbacks when validating address by listing +// modules. +struct AddressValidatingContext { + uintptr_t address; + bool is_mapped; + + AddressValidatingContext() : address(0UL), is_mapped(false) { + } +}; + +// Convert from string to int. +static bool LocalAtoi(char *s, int *r) { + assert(s != NULL); + assert(r != NULL); + char *endptr = NULL; + int ret = strtol(s, &endptr, 10); + if (endptr == s) + return false; + *r = ret; + return true; +} + +// Callback invoked for each mapped module. +// It uses the module's adderss range to validate the address. +static bool AddressNotInModuleCallback(const ModuleInfo &module_info, + void *context) { + AddressValidatingContext *addr = + reinterpret_cast<AddressValidatingContext *>(context); + if (addr->is_mapped = ((module_info.start_addr > 0) && + (addr->address >= module_info.start_addr) && + (addr->address <= module_info.start_addr + + module_info.size))) { + stack_base_address = module_info.start_addr + module_info.size; + } + + return !addr->is_mapped; +} + +static int IterateLwpAll(int pid, + CallbackParam<LwpidCallback> *callback_param) { + char lwp_path[40]; + DIR *dir; + int count = 0; + + snprintf(lwp_path, sizeof (lwp_path), "/proc/%d/lwp", (int)pid); + if ((dir = opendir(lwp_path)) == NULL) + return -1; + + struct dirent *entry = NULL; + while ((entry = readdir(dir)) != NULL) { + if ((strcmp(entry->d_name, ".") != 0) && + (strcmp(entry->d_name, "..") != 0)) { + int lwpid = 0; + int last_pid = 0; + if (LocalAtoi(entry->d_name, &lwpid) && last_pid != lwpid) { + last_pid = lwpid; + ++count; + if (callback_param && + !(callback_param->call_back)(lwpid, callback_param->context)) { + break; + } + } + } + } + + closedir(dir); + return count; +} + +#if defined(__i386) && !defined(NO_FRAME_POINTER) +void *GetNextFrame(void **last_ebp) { + void *sp = *last_ebp; + if ((unsigned long)sp == (unsigned long)last_ebp) + return NULL; + if ((unsigned long)sp & (sizeof(void *) - 1)) + return NULL; + if ((unsigned long)sp - (unsigned long)last_ebp > 100000) + return NULL; + return sp; +} +#elif defined(__sparc) +void *GetNextFrame(void *last_ebp) { + return reinterpret_cast<struct frame *>(last_ebp)->fr_savfp; +} +#else +void *GetNextFrame(void **last_ebp) { + return reinterpret_cast<void*>(last_ebp); +} +#endif + + +class AutoCloser { + public: + AutoCloser(int fd) : fd_(fd) {} + ~AutoCloser() { if (fd_) close(fd_); } + private: + int fd_; +}; + +// Control the execution of the lwp. +// Suspend/Resume lwp based on the value of context. +static bool ControlLwp(int lwpid, void *context) { + // The current thread is the one to handle the crash. Ignore it. + if (lwpid != pthread_self()) { + int ctlfd; + char procname[PATH_MAX]; + bool suspend = *(bool *)context; + + // Open the /proc/$pid/lwp/$lwpid/lwpctl files + snprintf(procname, sizeof (procname), "/proc/self/lwp/%d/lwpctl", lwpid); + + if ((ctlfd = open(procname, O_WRONLY|O_EXCL)) < 0) { + print_message2(2, "failed to open %s in ControlLwp\n", procname); + return false; + } + + AutoCloser autocloser(ctlfd); + + long ctl[2]; + ctl[0] = suspend ? PCSTOP : PCRUN; + ctl[1] = 0; + if (write(ctlfd, ctl, sizeof (ctl)) != sizeof (ctl)) { + print_message2(2, "failed in lwp %d\n", lwpid); + return false; + } + } + + return true; +} + +/* + * Utility function to read the contents of a file that contains a + * prheader_t at the start (/proc/$pid/lstatus or /proc/$pid/lpsinfo). + * Return true on success. + */ +static bool read_lfile(int pid, const char *lname, prheader_t *lhp) { + char lpath[PATH_MAX]; + struct stat statb; + int fd; + size_t size; + + snprintf(lpath, sizeof (lpath), "/proc/%d/%s", pid, lname); + if ((fd = open(lpath, O_RDONLY)) < 0) { + print_message2(2, "failed to open %s in read_lfile\n", lpath); + return false; + } + + AutoCloser autocloser(fd); + + if (fstat(fd, &statb) != 0) + return false; + + size = statb.st_size; + if ((size / sizeof (prheader_t)) + 32 > HEADER_MAX) { + print_message1(2, "map size overflow\n"); + return false; + } + + if (pread(fd, lhp, size, 0) <= sizeof (prheader_t)) + return false; + + return true; +} + +} // namespace + +namespace google_breakpad { + +SolarisLwp::SolarisLwp(int pid) : pid_(pid) { +} + +SolarisLwp::~SolarisLwp() { +} + +int SolarisLwp::ControlAllLwps(bool suspend) { + CallbackParam<LwpidCallback> callback_param(ControlLwp, &suspend); + return IterateLwpAll(pid_, &callback_param); +} + +int SolarisLwp::GetLwpCount() const { + return IterateLwpAll(pid_, NULL); +} + +int SolarisLwp::Lwp_iter_all(int pid, + CallbackParam<LwpCallback> *callback_param) const { + lwpstatus_t *Lsp; + lwpstatus_t *sp; + prheader_t lphp[HEADER_MAX]; + prheader_t lhp[HEADER_MAX]; + prheader_t *Lphp = lphp; + prheader_t *Lhp = lhp; + lwpsinfo_t *Lpsp; + long nstat; + long ninfo; + int rv = 0; + + /* + * The /proc/pid/lstatus file has the array of lwpstatus_t's and the + * /proc/pid/lpsinfo file has the array of lwpsinfo_t's. + */ + if (read_lfile(pid, "lstatus", Lhp) == NULL) + return -1; + if (read_lfile(pid, "lpsinfo", Lphp) == NULL) { + return -1; + } + + Lsp = (lwpstatus_t *)(uintptr_t)(Lhp + 1); + Lpsp = (lwpsinfo_t *)(uintptr_t)(Lphp + 1); + + for (ninfo = Lphp->pr_nent; ninfo != 0; --ninfo) { + if (Lpsp->pr_sname != 'Z') { + sp = Lsp; + Lsp = (lwpstatus_t *)((uintptr_t)Lsp + Lhp->pr_entsize); + } else { + sp = NULL; + } + if (callback_param && + !(callback_param->call_back)(sp, callback_param->context)) + break; + ++rv; + Lpsp = (lwpsinfo_t *)((uintptr_t)Lpsp + Lphp->pr_entsize); + } + + return rv; +} + +uintptr_t SolarisLwp::GetLwpStackBottom(uintptr_t current_esp) const { + AddressValidatingContext addr; + addr.address = current_esp; + CallbackParam<ModuleCallback> callback_param(AddressNotInModuleCallback, + &addr); + ListModules(&callback_param); + return stack_base_address; +} + +int SolarisLwp::GetModuleCount() const { + return ListModules(NULL); +} + +int SolarisLwp::ListModules( + CallbackParam<ModuleCallback> *callback_param) const { + const char *maps_path = "/proc/self/map"; + struct stat status; + int fd = 0, num; + prmap_t map_array[MAP_MAX]; + prmap_t *maps = map_array; + size_t size; + + if ((fd = open(maps_path, O_RDONLY)) == -1) { + print_message2(2, "failed to open %s in ListModules\n", maps_path); + return -1; + } + + AutoCloser autocloser(fd); + + if (fstat(fd, &status)) + return -1; + + /* + * Determine number of mappings, this value must be + * larger than the actual module count + */ + size = status.st_size; + if ((num = (int)(size / sizeof (prmap_t))) > MAP_MAX) { + print_message1(2, "map size overflow\n"); + return -1; + } + + if (read(fd, (void *)maps, size) < 0) { + print_message2(2, "failed to read %d\n", fd); + return -1; + } + + prmap_t *_maps; + int _num; + int module_count = 0; + + /* + * Scan each mapping - note it is assummed that the mappings are + * presented in order. We fill holes between mappings. On intel + * the last mapping is usually the data segment of ld.so.1, after + * this comes a red zone into which non-fixed mapping won't get + * place. Thus we can simply bail from the loop after seeing the + * last mapping. + */ + for (_num = 0, _maps = maps; _num < num; ++_num, ++_maps) { + ModuleInfo module; + char *name = _maps->pr_mapname; + + memset(&module, 0, sizeof (module)); + module.start_addr = _maps->pr_vaddr; + module.size = _maps->pr_size; + if (strlen(name) > 0) { + int objectfd = 0; + char path[PATH_MAX]; + char buf[SELFMAG]; + + snprintf(path, sizeof (path), "/proc/self/object/%s", name); + if ((objectfd = open(path, O_RDONLY)) < 0) { + print_message1(2, "can't open module file\n"); + continue; + } + + AutoCloser autocloser(objectfd); + + if (read(objectfd, buf, SELFMAG) != SELFMAG) { + print_message1(2, "can't read module file\n"); + continue; + } + if (buf[0] != ELFMAG0 || buf[1] != ELFMAG1 || + buf[2] != ELFMAG2 || buf[3] != ELFMAG3) { + continue; + } + + strncpy(module.name, name, sizeof (module.name) - 1); + ++module_count; + } + if (callback_param && + (!callback_param->call_back(module, callback_param->context))) { + break; + } + } + + return module_count; +} + +// Check if the address is a valid virtual address. +// If the address is in any of the mapped modules, we take it as valid. +// Otherwise it is invalid. +bool SolarisLwp::IsAddressMapped(uintptr_t address) const { + AddressValidatingContext addr; + addr.address = address; + CallbackParam<ModuleCallback> callback_param(AddressNotInModuleCallback, + &addr); + ListModules(&callback_param); + return addr.is_mapped; +} + +// We're looking for a ucontext_t as the second parameter +// to a signal handler function call. Luckily, the ucontext_t +// has an ebp(fp on SPARC) member which should match the ebp(fp) +// pointed to by the ebp(fp) of the signal handler frame. +// The Solaris stack looks like this: +// http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/lib/libproc/common/Pstack.c#81 +bool SolarisLwp::FindSigContext(uintptr_t sighandler_ebp, + ucontext_t **sig_ctx) { + uintptr_t previous_ebp; + uintptr_t sig_ebp; + const int MAX_STACK_DEPTH = 50; + int depth_counter = 0; + + do { +#if TARGET_CPU_SPARC + previous_ebp = reinterpret_cast<uintptr_t>(GetNextFrame( + reinterpret_cast<void*>(sighandler_ebp))); + *sig_ctx = reinterpret_cast<ucontext_t*>(sighandler_ebp + sizeof (struct frame)); + uintptr_t sig_esp = (*sig_ctx)->uc_mcontext.gregs[REG_O6]; + if (sig_esp < previous_ebp && sig_esp > sighandler_ebp) + sig_ebp = (uintptr_t)(((struct frame *)sig_esp)->fr_savfp); + +#elif TARGET_CPU_X86 + previous_ebp = reinterpret_cast<uintptr_t>(GetNextFrame( + reinterpret_cast<void**>(sighandler_ebp))); + *sig_ctx = reinterpret_cast<ucontext_t*>(sighandler_ebp + sizeof (struct frame) + + 3 * sizeof(uintptr_t)); + sig_ebp = (*sig_ctx)->uc_mcontext.gregs[EBP]; +#endif + sighandler_ebp = previous_ebp; + depth_counter++; + } while(previous_ebp != sig_ebp && sighandler_ebp != 0 && + IsAddressMapped(sighandler_ebp) && depth_counter < MAX_STACK_DEPTH); + + return previous_ebp == sig_ebp && previous_ebp != 0; +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/solaris_lwp.h b/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/solaris_lwp.h new file mode 100644 index 000000000..0914cfcd8 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/solaris/handler/solaris_lwp.h @@ -0,0 +1,160 @@ +// Copyright (c) 2007, 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. + +// Author: Alfred Peng + +#ifndef CLIENT_SOLARIS_HANDLER_SOLARIS_LWP_H__ +#define CLIENT_SOLARIS_HANDLER_SOLARIS_LWP_H__ + +#if defined(sparc) || defined(__sparc) +#define TARGET_CPU_SPARC 1 +#elif defined(i386) || defined(__i386) +#define TARGET_CPU_X86 1 +#else +#error "cannot determine cpu type" +#endif + +#include <signal.h> +#include <stdint.h> +#include <sys/user.h> +#include <ucontext.h> + +#ifndef _KERNEL +#define _KERNEL +#define MUST_UNDEF_KERNEL +#endif // _KERNEL +#include <sys/procfs.h> +#ifdef MUST_UNDEF_KERNEL +#undef _KERNEL +#undef MUST_UNDEF_KERNEL +#endif // MUST_UNDEF_KERNEL + +namespace google_breakpad { + +// Max module path name length. +static const int kMaxModuleNameLength = 256; + +// Holding infomaton about a module in the process. +struct ModuleInfo { + char name[kMaxModuleNameLength]; + uintptr_t start_addr; + int size; +}; + +// A callback to run when getting a lwp in the process. +// Return true will go on to the next lwp while return false will stop the +// iteration. +typedef bool (*LwpCallback)(lwpstatus_t* lsp, void *context); + +// A callback to run when a new module is found in the process. +// Return true will go on to the next module while return false will stop the +// iteration. +typedef bool (*ModuleCallback)(const ModuleInfo &module_info, void *context); + +// A callback to run when getting a lwpid in the process. +// Return true will go on to the next lwp while return false will stop the +// iteration. +typedef bool (*LwpidCallback)(int lwpid, void *context); + +// Holding the callback information. +template<class CallbackFunc> +struct CallbackParam { + // Callback function address. + CallbackFunc call_back; + // Callback context; + void *context; + + CallbackParam() : call_back(NULL), context(NULL) { + } + + CallbackParam(CallbackFunc func, void *func_context) : + call_back(func), context(func_context) { + } +}; + +/////////////////////////////////////////////////////////////////////////////// + +// +// SolarisLwp +// +// Provides handy support for operation on Solaris lwps. +// It uses proc file system to get lwp information. +// +// TODO(Alfred): Currently it only supports x86. Add SPARC support. +// +class SolarisLwp { + public: + // Create a SolarisLwp instance to list all the lwps in a process. + explicit SolarisLwp(int pid); + ~SolarisLwp(); + + int getpid() const { return this->pid_; } + + // Control all the lwps in the process. + // Return the number of suspended/resumed lwps in the process. + // Return -1 means failed to control lwps. + int ControlAllLwps(bool suspend); + + // Get the count of lwps in the process. + // Return -1 means error. + int GetLwpCount() const; + + // Iterate the lwps of process. + // Whenever there is a lwp found, the callback will be invoked to process + // the information. + // Return the callback return value or -1 on error. + int Lwp_iter_all(int pid, CallbackParam<LwpCallback> *callback_param) const; + + // Get the module count of the current process. + int GetModuleCount() const; + + // Get the mapped modules in the address space. + // Whenever a module is found, the callback will be invoked to process the + // information. + // Return how may modules are found. + int ListModules(CallbackParam<ModuleCallback> *callback_param) const; + + // Get the bottom of the stack from esp. + uintptr_t GetLwpStackBottom(uintptr_t current_esp) const; + + // Finds a signal context on the stack given the ebp of our signal handler. + bool FindSigContext(uintptr_t sighandler_ebp, ucontext_t **sig_ctx); + + private: + // Check if the address is a valid virtual address. + bool IsAddressMapped(uintptr_t address) const; + + private: + // The pid of the process we are listing lwps. + int pid_; +}; + +} // namespace google_breakpad + +#endif // CLIENT_SOLARIS_HANDLER_SOLARIS_LWP_H__ diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/breakpad_client.gyp b/toolkit/crashreporter/google-breakpad/src/client/windows/breakpad_client.gyp new file mode 100644 index 000000000..647975342 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/breakpad_client.gyp @@ -0,0 +1,66 @@ +# Copyright 2010 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. + +{ + 'includes': [ + '../../build/common.gypi', + ], + 'targets': [ + { + 'target_name': 'build_all', + 'type': 'none', + 'dependencies': [ + './crash_generation/crash_generation.gyp:*', + './handler/exception_handler.gyp:*', + './sender/crash_report_sender.gyp:*', + './unittests/client_tests.gyp:*', + './unittests/testing.gyp:*', + './tests/crash_generation_app/crash_generation_app.gyp:*', + ] + }, + { + 'target_name': 'common', + 'type': 'static_library', + 'include_dirs': [ + '<(DEPTH)', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '<(DEPTH)', + ] + }, + 'sources': [ + '<(DEPTH)/common/windows/guid_string.cc', + '<(DEPTH)/common/windows/guid_string.h', + '<(DEPTH)/common/windows/http_upload.cc', + '<(DEPTH)/common/windows/http_upload.h', + '<(DEPTH)/common/windows/string_utils.cc', + ] + } + ] +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/common/auto_critical_section.h b/toolkit/crashreporter/google-breakpad/src/client/windows/common/auto_critical_section.h new file mode 100644 index 000000000..3fd4b9b7e --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/common/auto_critical_section.h @@ -0,0 +1,81 @@ +// Copyright (c) 2008, 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_WINDOWS_COMMON_AUTO_CRITICAL_SECTION_H__ +#define CLIENT_WINDOWS_COMMON_AUTO_CRITICAL_SECTION_H__ + +#include <windows.h> + +namespace google_breakpad { + +// Automatically enters the critical section in the constructor and leaves +// the critical section in the destructor. +class AutoCriticalSection { + public: + // Creates a new instance with the given critical section object + // and enters the critical section immediately. + explicit AutoCriticalSection(CRITICAL_SECTION* cs) : cs_(cs), taken_(false) { + assert(cs_); + Acquire(); + } + + // Destructor: leaves the critical section. + ~AutoCriticalSection() { + if (taken_) { + Release(); + } + } + + // Enters the critical section. Recursive Acquire() calls are not allowed. + void Acquire() { + assert(!taken_); + EnterCriticalSection(cs_); + taken_ = true; + } + + // Leaves the critical section. The caller should not call Release() unless + // the critical seciton has been entered already. + void Release() { + assert(taken_); + taken_ = false; + LeaveCriticalSection(cs_); + } + + private: + // Disable copy ctor and operator=. + AutoCriticalSection(const AutoCriticalSection&); + AutoCriticalSection& operator=(const AutoCriticalSection&); + + CRITICAL_SECTION* cs_; + bool taken_; +}; + +} // namespace google_breakpad + +#endif // CLIENT_WINDOWS_COMMON_AUTO_CRITICAL_SECTION_H__ diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/common/ipc_protocol.h b/toolkit/crashreporter/google-breakpad/src/client/windows/common/ipc_protocol.h new file mode 100644 index 000000000..c74868198 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/common/ipc_protocol.h @@ -0,0 +1,181 @@ +// Copyright (c) 2008, 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_WINDOWS_COMMON_IPC_PROTOCOL_H__ +#define CLIENT_WINDOWS_COMMON_IPC_PROTOCOL_H__ + +#include <windows.h> +#include <dbghelp.h> +#include <string> +#include <utility> +#include "common/windows/string_utils-inl.h" +#include "google_breakpad/common/minidump_format.h" + +namespace google_breakpad { + +// Name/value pair for custom client information. +struct CustomInfoEntry { + // Maximum length for name and value for client custom info. + static const int kNameMaxLength = 64; + static const int kValueMaxLength = 64; + + CustomInfoEntry() { + // Putting name and value in initializer list makes VC++ show warning 4351. + set_name(NULL); + set_value(NULL); + } + + CustomInfoEntry(const wchar_t* name_arg, const wchar_t* value_arg) { + set_name(name_arg); + set_value(value_arg); + } + + void set_name(const wchar_t* name_arg) { + if (!name_arg) { + name[0] = L'\0'; + return; + } + WindowsStringUtils::safe_wcscpy(name, kNameMaxLength, name_arg); + } + + void set_value(const wchar_t* value_arg) { + if (!value_arg) { + value[0] = L'\0'; + return; + } + + WindowsStringUtils::safe_wcscpy(value, kValueMaxLength, value_arg); + } + + void set(const wchar_t* name_arg, const wchar_t* value_arg) { + set_name(name_arg); + set_value(value_arg); + } + + wchar_t name[kNameMaxLength]; + wchar_t value[kValueMaxLength]; +}; + +// Constants for the protocol between client and the server. + +// Tags sent with each message indicating the purpose of +// the message. +enum MessageTag { + MESSAGE_TAG_NONE = 0, + MESSAGE_TAG_REGISTRATION_REQUEST = 1, + MESSAGE_TAG_REGISTRATION_RESPONSE = 2, + MESSAGE_TAG_REGISTRATION_ACK = 3, + MESSAGE_TAG_UPLOAD_REQUEST = 4 +}; + +struct CustomClientInfo { + const CustomInfoEntry* entries; + size_t count; +}; + +// Message structure for IPC between crash client and crash server. +struct ProtocolMessage { + ProtocolMessage() + : tag(MESSAGE_TAG_NONE), + id(0), + dump_type(MiniDumpNormal), + thread_id(0), + exception_pointers(NULL), + assert_info(NULL), + custom_client_info(), + dump_request_handle(NULL), + dump_generated_handle(NULL), + server_alive_handle(NULL) { + } + + // Creates an instance with the given parameters. + ProtocolMessage(MessageTag arg_tag, + DWORD arg_id, + MINIDUMP_TYPE arg_dump_type, + DWORD* arg_thread_id, + EXCEPTION_POINTERS** arg_exception_pointers, + MDRawAssertionInfo* arg_assert_info, + const CustomClientInfo& custom_info, + HANDLE arg_dump_request_handle, + HANDLE arg_dump_generated_handle, + HANDLE arg_server_alive) + : tag(arg_tag), + id(arg_id), + dump_type(arg_dump_type), + thread_id(arg_thread_id), + exception_pointers(arg_exception_pointers), + assert_info(arg_assert_info), + custom_client_info(custom_info), + dump_request_handle(arg_dump_request_handle), + dump_generated_handle(arg_dump_generated_handle), + server_alive_handle(arg_server_alive) { + } + + // Tag in the message. + MessageTag tag; + + // The id for this message. This may be either a process id or a crash id + // depending on the type of message. + DWORD id; + + // Dump type requested. + MINIDUMP_TYPE dump_type; + + // Client thread id pointer. + DWORD* thread_id; + + // Exception information. + EXCEPTION_POINTERS** exception_pointers; + + // Assert information in case of an invalid parameter or + // pure call failure. + MDRawAssertionInfo* assert_info; + + // Custom client information. + CustomClientInfo custom_client_info; + + // Handle to signal the crash event. + HANDLE dump_request_handle; + + // Handle to check if server is done generating crash. + HANDLE dump_generated_handle; + + // Handle to a mutex that becomes signaled (WAIT_ABANDONED) + // if server process goes down. + HANDLE server_alive_handle; + + private: + // Disable copy ctor and operator=. + ProtocolMessage(const ProtocolMessage& msg); + ProtocolMessage& operator=(const ProtocolMessage& msg); +}; + +} // namespace google_breakpad + +#endif // CLIENT_WINDOWS_COMMON_IPC_PROTOCOL_H__ diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/ReadMe.txt b/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/ReadMe.txt new file mode 100644 index 000000000..b54d0e11b --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/ReadMe.txt @@ -0,0 +1,58 @@ +========================================================================= + State machine transitions for the Crash Generation Server +========================================================================= + +========================================================================= + | + STATE | ACTIONS + | +========================================================================= + ERROR | Clean up resources used to serve clients. + | Always remain in ERROR state. +------------------------------------------------------------------------- + INITIAL | Connect to the pipe asynchronously. + | If connection is successfully queued up asynchronously, + | go into CONNECTING state. + | If connection is done synchronously, go into CONNECTED + | state. + | For any unexpected problems, go into ERROR state. +------------------------------------------------------------------------- + CONNECTING | Get the result of async connection request. + | If I/O is still incomplete, remain in the CONNECTING + | state. + | If connection is complete, go into CONNECTED state. + | For any unexpected problems, go into DISCONNECTING state. +------------------------------------------------------------------------- + CONNECTED | Read from the pipe asynchronously. + | If read request is successfully queued up asynchronously, + | go into READING state. + | For any unexpected problems, go into DISCONNECTING state. +------------------------------------------------------------------------- + READING | Get the result of async read request. + | If read is done, go into READ_DONE state. + | For any unexpected problems, go into DISCONNECTING state. +------------------------------------------------------------------------- + READ_DONE | Register the client, prepare the reply and write the + | reply to the pipe asynchronously. + | If write request is successfully queued up asynchronously, + | go into WRITING state. + | For any unexpected problems, go into DISCONNECTING state. +------------------------------------------------------------------------- + WRITING | Get the result of the async write request. + | If write is done, go into WRITE_DONE state. + | For any unexpected problems, go into DISCONNECTING state. +------------------------------------------------------------------------- + WRITE_DONE | Read from the pipe asynchronously (for an ACK). + | If read request is successfully queued up asynchonously, + | go into READING_ACK state. + | For any unexpected problems, go into DISCONNECTING state. +------------------------------------------------------------------------- + READING_ACK | Get the result of the async read request. + | If read is done, perform action for successful client + | connection. + | Go into DISCONNECTING state. +------------------------------------------------------------------------- + DISCONNECTING | Disconnect from the pipe, reset the event and go into + | INITIAL state and signal the event again. If anything + | fails, go into ERROR state. +========================================================================= diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/client_info.cc b/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/client_info.cc new file mode 100644 index 000000000..ed3126381 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/client_info.cc @@ -0,0 +1,223 @@ +// Copyright (c) 2008, 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/windows/crash_generation/client_info.h" +#include "client/windows/common/ipc_protocol.h" + +static const wchar_t kCustomInfoProcessUptimeName[] = L"ptime"; +static const size_t kMaxCustomInfoEntries = 4096; + +namespace google_breakpad { + +ClientInfo::ClientInfo(CrashGenerationServer* crash_server, + DWORD pid, + MINIDUMP_TYPE dump_type, + DWORD* thread_id, + EXCEPTION_POINTERS** ex_info, + MDRawAssertionInfo* assert_info, + const CustomClientInfo& custom_client_info) + : crash_server_(crash_server), + pid_(pid), + dump_type_(dump_type), + ex_info_(ex_info), + assert_info_(assert_info), + custom_client_info_(custom_client_info), + thread_id_(thread_id), + process_handle_(NULL), + dump_requested_handle_(NULL), + dump_generated_handle_(NULL), + dump_request_wait_handle_(NULL), + process_exit_wait_handle_(NULL), + crash_id_(NULL) { + GetSystemTimeAsFileTime(&start_time_); +} + +bool ClientInfo::Initialize() { + process_handle_ = OpenProcess(GENERIC_ALL, FALSE, pid_); + if (!process_handle_) { + return false; + } + + // The crash_id will be the low order word of the process creation time. + FILETIME creation_time, exit_time, kernel_time, user_time; + if (GetProcessTimes(process_handle_, &creation_time, &exit_time, + &kernel_time, &user_time)) { + start_time_ = creation_time; + } + crash_id_ = start_time_.dwLowDateTime; + + dump_requested_handle_ = CreateEvent(NULL, // Security attributes. + TRUE, // Manual reset. + FALSE, // Initial state. + NULL); // Name. + if (!dump_requested_handle_) { + return false; + } + + dump_generated_handle_ = CreateEvent(NULL, // Security attributes. + TRUE, // Manual reset. + FALSE, // Initial state. + NULL); // Name. + return dump_generated_handle_ != NULL; +} + +void ClientInfo::UnregisterDumpRequestWaitAndBlockUntilNoPending() { + if (dump_request_wait_handle_) { + // Wait for callbacks that might already be running to finish. + UnregisterWaitEx(dump_request_wait_handle_, INVALID_HANDLE_VALUE); + dump_request_wait_handle_ = NULL; + } +} + +void ClientInfo::UnregisterProcessExitWait(bool block_until_no_pending) { + if (process_exit_wait_handle_) { + if (block_until_no_pending) { + // Wait for the callback that might already be running to finish. + UnregisterWaitEx(process_exit_wait_handle_, INVALID_HANDLE_VALUE); + } else { + UnregisterWait(process_exit_wait_handle_); + } + process_exit_wait_handle_ = NULL; + } +} + +ClientInfo::~ClientInfo() { + // Waiting for the callback to finish here is safe because ClientInfo's are + // never destroyed from the dump request handling callback. + UnregisterDumpRequestWaitAndBlockUntilNoPending(); + + // This is a little tricky because ClientInfo's may be destroyed by the same + // callback (OnClientEnd) and waiting for it to finish will cause a deadlock. + // Regardless of this complication, wait for any running callbacks to finish + // so that the common case is properly handled. In order to avoid deadlocks, + // the OnClientEnd callback must call UnregisterProcessExitWait(false) + // before deleting the ClientInfo. + UnregisterProcessExitWait(true); + + if (process_handle_) { + CloseHandle(process_handle_); + } + + if (dump_requested_handle_) { + CloseHandle(dump_requested_handle_); + } + + if (dump_generated_handle_) { + CloseHandle(dump_generated_handle_); + } +} + +bool ClientInfo::GetClientExceptionInfo(EXCEPTION_POINTERS** ex_info) const { + SIZE_T bytes_count = 0; + if (!ReadProcessMemory(process_handle_, + ex_info_, + ex_info, + sizeof(*ex_info), + &bytes_count)) { + return false; + } + + return bytes_count == sizeof(*ex_info); +} + +bool ClientInfo::GetClientThreadId(DWORD* thread_id) const { + SIZE_T bytes_count = 0; + if (!ReadProcessMemory(process_handle_, + thread_id_, + thread_id, + sizeof(*thread_id), + &bytes_count)) { + return false; + } + + return bytes_count == sizeof(*thread_id); +} + +void ClientInfo::SetProcessUptime() { + FILETIME now = {0}; + GetSystemTimeAsFileTime(&now); + + ULARGE_INTEGER time_start; + time_start.HighPart = start_time_.dwHighDateTime; + time_start.LowPart = start_time_.dwLowDateTime; + + ULARGE_INTEGER time_now; + time_now.HighPart = now.dwHighDateTime; + time_now.LowPart = now.dwLowDateTime; + + // Calculate the delay and convert it from 100-nanoseconds to milliseconds. + __int64 delay = (time_now.QuadPart - time_start.QuadPart) / 10 / 1000; + + // Convert it to a string. + wchar_t* value = custom_info_entries_.get()[custom_client_info_.count].value; + _i64tow_s(delay, value, CustomInfoEntry::kValueMaxLength, 10); +} + +bool ClientInfo::PopulateCustomInfo() { + if (custom_client_info_.count > kMaxCustomInfoEntries) + return false; + + SIZE_T bytes_count = 0; + SIZE_T read_count = sizeof(CustomInfoEntry) * custom_client_info_.count; + + // If the scoped array for custom info already has an array, it will be + // the same size as what we need. This is because the number of custom info + // entries is always the same. So allocate memory only if scoped array has + // a NULL pointer. + if (!custom_info_entries_.get()) { + // Allocate an extra entry for reporting uptime for the client process. + custom_info_entries_.reset( + new CustomInfoEntry[custom_client_info_.count + 1]); + // Use the last element in the array for uptime. + custom_info_entries_.get()[custom_client_info_.count].set_name( + kCustomInfoProcessUptimeName); + } + + if (!ReadProcessMemory(process_handle_, + custom_client_info_.entries, + custom_info_entries_.get(), + read_count, + &bytes_count)) { + return false; + } + + SetProcessUptime(); + return (bytes_count == read_count); +} + +CustomClientInfo ClientInfo::GetCustomInfo() const { + CustomClientInfo custom_info; + custom_info.entries = custom_info_entries_.get(); + // Add 1 to the count from the client process to account for extra entry for + // process uptime. + custom_info.count = custom_client_info_.count + 1; + return custom_info; +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/client_info.h b/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/client_info.h new file mode 100644 index 000000000..6a8fba31f --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/client_info.h @@ -0,0 +1,177 @@ +// Copyright (c) 2008, 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_WINDOWS_CRASH_GENERATION_CLIENT_INFO_H__ +#define CLIENT_WINDOWS_CRASH_GENERATION_CLIENT_INFO_H__ + +#include <windows.h> +#include <dbghelp.h> +#include "client/windows/common/ipc_protocol.h" +#include "common/scoped_ptr.h" +#include "google_breakpad/common/minidump_format.h" + +namespace google_breakpad { + +class CrashGenerationServer; + +// Abstraction for a crash client process. +class ClientInfo { + public: + // Creates an instance with the given values. Gets the process + // handle for the given process id and creates necessary event + // objects. + ClientInfo(CrashGenerationServer* crash_server, + DWORD pid, + MINIDUMP_TYPE dump_type, + DWORD* thread_id, + EXCEPTION_POINTERS** ex_info, + MDRawAssertionInfo* assert_info, + const CustomClientInfo& custom_client_info); + + ~ClientInfo(); + + CrashGenerationServer* crash_server() const { return crash_server_; } + DWORD pid() const { return pid_; } + MINIDUMP_TYPE dump_type() const { return dump_type_; } + EXCEPTION_POINTERS** ex_info() const { return ex_info_; } + MDRawAssertionInfo* assert_info() const { return assert_info_; } + DWORD* thread_id() const { return thread_id_; } + HANDLE process_handle() const { return process_handle_; } + HANDLE dump_requested_handle() const { return dump_requested_handle_; } + HANDLE dump_generated_handle() const { return dump_generated_handle_; } + DWORD crash_id() const { return crash_id_; } + const CustomClientInfo& custom_client_info() const { + return custom_client_info_; + } + + void set_dump_request_wait_handle(HANDLE value) { + dump_request_wait_handle_ = value; + } + + void set_process_exit_wait_handle(HANDLE value) { + process_exit_wait_handle_ = value; + } + + // Unregister the dump request wait operation and wait for all callbacks + // that might already be running to complete before returning. + void UnregisterDumpRequestWaitAndBlockUntilNoPending(); + + // Unregister the process exit wait operation. If block_until_no_pending is + // true, wait for all callbacks that might already be running to complete + // before returning. + void UnregisterProcessExitWait(bool block_until_no_pending); + + bool Initialize(); + bool GetClientExceptionInfo(EXCEPTION_POINTERS** ex_info) const; + bool GetClientThreadId(DWORD* thread_id) const; + + // Reads the custom information from the client process address space. + bool PopulateCustomInfo(); + + // Returns the client custom information. + CustomClientInfo GetCustomInfo() const; + + private: + // Calcualtes the uptime for the client process, converts it to a string and + // stores it in the last entry of client custom info. + void SetProcessUptime(); + + // Crash generation server. + CrashGenerationServer* crash_server_; + + // Client process ID. + DWORD pid_; + + // Dump type requested by the client. + MINIDUMP_TYPE dump_type_; + + // Address of an EXCEPTION_POINTERS* variable in the client + // process address space that will point to an instance of + // EXCEPTION_POINTERS containing information about crash. + // + // WARNING: Do not dereference these pointers as they are pointers + // in the address space of another process. + EXCEPTION_POINTERS** ex_info_; + + // Address of an instance of MDRawAssertionInfo in the client + // process address space that will contain information about + // non-exception related crashes like invalid parameter assertion + // failures and pure calls. + // + // WARNING: Do not dereference these pointers as they are pointers + // in the address space of another process. + MDRawAssertionInfo* assert_info_; + + // Custom information about the client. + CustomClientInfo custom_client_info_; + + // Contains the custom client info entries read from the client process + // memory. This will be populated only if the method GetClientCustomInfo + // is called. + scoped_array<CustomInfoEntry> custom_info_entries_; + + // Address of a variable in the client process address space that + // will contain the thread id of the crashing client thread. + // + // WARNING: Do not dereference these pointers as they are pointers + // in the address space of another process. + DWORD* thread_id_; + + // Client process handle. + HANDLE process_handle_; + + // Dump request event handle. + HANDLE dump_requested_handle_; + + // Dump generated event handle. + HANDLE dump_generated_handle_; + + // Wait handle for dump request event. + HANDLE dump_request_wait_handle_; + + // Wait handle for process exit event. + HANDLE process_exit_wait_handle_; + + // Time when the client process started. It is used to determine the uptime + // for the client process when it signals a crash. + FILETIME start_time_; + + // The crash id which can be used to request an upload. This will be the + // value of the low order dword of the process creation time for the process + // being dumped. + DWORD crash_id_; + + // Disallow copy ctor and operator=. + ClientInfo(const ClientInfo& client_info); + ClientInfo& operator=(const ClientInfo& client_info); +}; + +} // namespace google_breakpad + +#endif // CLIENT_WINDOWS_CRASH_GENERATION_CLIENT_INFO_H__ diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/crash_generation.gyp b/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/crash_generation.gyp new file mode 100644 index 000000000..ba343768a --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/crash_generation.gyp @@ -0,0 +1,63 @@ +# Copyright 2010 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. + +{ + 'includes': [ + '../../../build/common.gypi', + ], + 'targets': [ + { + 'target_name': 'crash_generation_server', + 'type': 'static_library', + 'sources': [ + 'client_info.cc', + 'crash_generation_server.cc', + 'minidump_generator.cc', + 'client_info.h', + 'crash_generation_client.h', + 'crash_generation_server.h', + 'minidump_generator.h', + ], + 'dependencies': [ + '../breakpad_client.gyp:common' + ], + }, + { + 'target_name': 'crash_generation_client', + 'type': 'static_library', + 'include_dirs': [ + '<(DEPTH)', + ], + 'sources': [ + 'crash_generation_client.h', + 'crash_generation_client.cc', + 'crash_generation_server.h', + ], + }, + ], +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/crash_generation_client.cc b/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/crash_generation_client.cc new file mode 100644 index 000000000..3ba5d4e4f --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/crash_generation_client.cc @@ -0,0 +1,405 @@ +// Copyright (c) 2008, 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/windows/crash_generation/crash_generation_client.h" +#include <cassert> +#include <utility> +#include "client/windows/common/ipc_protocol.h" + +namespace google_breakpad { + +const int kPipeBusyWaitTimeoutMs = 2000; + +#ifdef _DEBUG +const DWORD kWaitForServerTimeoutMs = INFINITE; +#else +const DWORD kWaitForServerTimeoutMs = 15000; +#endif + +const int kPipeConnectMaxAttempts = 2; + +const DWORD kPipeDesiredAccess = FILE_READ_DATA | + FILE_WRITE_DATA | + FILE_WRITE_ATTRIBUTES; + +const DWORD kPipeFlagsAndAttributes = SECURITY_IDENTIFICATION | + SECURITY_SQOS_PRESENT; + +const DWORD kPipeMode = PIPE_READMODE_MESSAGE; + +const size_t kWaitEventCount = 2; + +// This function is orphan for production code. It can be used +// for debugging to help repro some scenarios like the client +// is slow in writing to the pipe after connecting, the client +// is slow in reading from the pipe after writing, etc. The parameter +// overlapped below is not used and it is present to match the signature +// of this function to TransactNamedPipe Win32 API. Uncomment if needed +// for debugging. +/** +static bool TransactNamedPipeDebugHelper(HANDLE pipe, + const void* in_buffer, + DWORD in_size, + void* out_buffer, + DWORD out_size, + DWORD* bytes_count, + LPOVERLAPPED) { + // Uncomment the next sleep to create a gap before writing + // to pipe. + // Sleep(5000); + + if (!WriteFile(pipe, + in_buffer, + in_size, + bytes_count, + NULL)) { + return false; + } + + // Uncomment the next sleep to create a gap between write + // and read. + // Sleep(5000); + + return ReadFile(pipe, out_buffer, out_size, bytes_count, NULL) != FALSE; +} +**/ + +CrashGenerationClient::CrashGenerationClient( + const wchar_t* pipe_name, + MINIDUMP_TYPE dump_type, + const CustomClientInfo* custom_info) + : pipe_name_(pipe_name), + pipe_handle_(NULL), + custom_info_(), + dump_type_(dump_type), + crash_event_(NULL), + crash_generated_(NULL), + server_alive_(NULL), + server_process_id_(0), + thread_id_(0), + exception_pointers_(NULL) { + memset(&assert_info_, 0, sizeof(assert_info_)); + if (custom_info) { + custom_info_ = *custom_info; + } +} + +CrashGenerationClient::CrashGenerationClient( + HANDLE pipe_handle, + MINIDUMP_TYPE dump_type, + const CustomClientInfo* custom_info) + : pipe_name_(), + pipe_handle_(pipe_handle), + custom_info_(), + dump_type_(dump_type), + crash_event_(NULL), + crash_generated_(NULL), + server_alive_(NULL), + server_process_id_(0), + thread_id_(0), + exception_pointers_(NULL) { + memset(&assert_info_, 0, sizeof(assert_info_)); + if (custom_info) { + custom_info_ = *custom_info; + } +} + +CrashGenerationClient::~CrashGenerationClient() { + if (crash_event_) { + CloseHandle(crash_event_); + } + + if (crash_generated_) { + CloseHandle(crash_generated_); + } + + if (server_alive_) { + CloseHandle(server_alive_); + } +} + +// Performs the registration step with the server process. +// The registration step involves communicating with the server +// via a named pipe. The client sends the following pieces of +// data to the server: +// +// * Message tag indicating the client is requesting registration. +// * Process id of the client process. +// * Address of a DWORD variable in the client address space +// that will contain the thread id of the client thread that +// caused the crash. +// * Address of a EXCEPTION_POINTERS* variable in the client +// address space that will point to an instance of EXCEPTION_POINTERS +// when the crash happens. +// * Address of an instance of MDRawAssertionInfo that will contain +// relevant information in case of non-exception crashes like assertion +// failures and pure calls. +// +// In return the client expects the following information from the server: +// +// * Message tag indicating successful registration. +// * Server process id. +// * Handle to an object that client can signal to request dump +// generation from the server. +// * Handle to an object that client can wait on after requesting +// dump generation for the server to finish dump generation. +// * Handle to a mutex object that client can wait on to make sure +// server is still alive. +// +// If any step of the expected behavior mentioned above fails, the +// registration step is not considered successful and hence out-of-process +// dump generation service is not available. +// +// Returns true if the registration is successful; false otherwise. +bool CrashGenerationClient::Register() { + if (IsRegistered()) { + return true; + } + + HANDLE pipe = ConnectToServer(); + if (!pipe) { + return false; + } + + bool success = RegisterClient(pipe); + CloseHandle(pipe); + return success; +} + +bool CrashGenerationClient::RequestUpload(DWORD crash_id) { + HANDLE pipe = ConnectToServer(); + if (!pipe) { + return false; + } + + CustomClientInfo custom_info = {NULL, 0}; + ProtocolMessage msg(MESSAGE_TAG_UPLOAD_REQUEST, crash_id, + static_cast<MINIDUMP_TYPE>(NULL), NULL, NULL, NULL, + custom_info, NULL, NULL, NULL); + DWORD bytes_count = 0; + bool success = WriteFile(pipe, &msg, sizeof(msg), &bytes_count, NULL) != 0; + + CloseHandle(pipe); + return success; +} + +HANDLE CrashGenerationClient::ConnectToServer() { + HANDLE pipe = ConnectToPipe(pipe_name_.c_str(), + kPipeDesiredAccess, + kPipeFlagsAndAttributes); + if (!pipe) { + return NULL; + } + + DWORD mode = kPipeMode; + if (!SetNamedPipeHandleState(pipe, &mode, NULL, NULL)) { + CloseHandle(pipe); + pipe = NULL; + } + + return pipe; +} + +bool CrashGenerationClient::RegisterClient(HANDLE pipe) { + ProtocolMessage msg(MESSAGE_TAG_REGISTRATION_REQUEST, + GetCurrentProcessId(), + dump_type_, + &thread_id_, + &exception_pointers_, + &assert_info_, + custom_info_, + NULL, + NULL, + NULL); + ProtocolMessage reply; + DWORD bytes_count = 0; + // The call to TransactNamedPipe below can be changed to a call + // to TransactNamedPipeDebugHelper to help repro some scenarios. + // For details see comments for TransactNamedPipeDebugHelper. + if (!TransactNamedPipe(pipe, + &msg, + sizeof(msg), + &reply, + sizeof(ProtocolMessage), + &bytes_count, + NULL)) { + return false; + } + + if (!ValidateResponse(reply)) { + return false; + } + + ProtocolMessage ack_msg; + ack_msg.tag = MESSAGE_TAG_REGISTRATION_ACK; + + if (!WriteFile(pipe, &ack_msg, sizeof(ack_msg), &bytes_count, NULL)) { + return false; + } + crash_event_ = reply.dump_request_handle; + crash_generated_ = reply.dump_generated_handle; + server_alive_ = reply.server_alive_handle; + server_process_id_ = reply.id; + + return true; +} + +HANDLE CrashGenerationClient::ConnectToPipe(const wchar_t* pipe_name, + DWORD pipe_access, + DWORD flags_attrs) { + if (pipe_handle_) { + HANDLE t = pipe_handle_; + pipe_handle_ = NULL; + return t; + } + + for (int i = 0; i < kPipeConnectMaxAttempts; ++i) { + HANDLE pipe = CreateFile(pipe_name, + pipe_access, + 0, + NULL, + OPEN_EXISTING, + flags_attrs, + NULL); + if (pipe != INVALID_HANDLE_VALUE) { + return pipe; + } + + // Cannot continue retrying if error is something other than + // ERROR_PIPE_BUSY. + if (GetLastError() != ERROR_PIPE_BUSY) { + break; + } + + // Cannot continue retrying if wait on pipe fails. + if (!WaitNamedPipe(pipe_name, kPipeBusyWaitTimeoutMs)) { + break; + } + } + + return NULL; +} + +bool CrashGenerationClient::ValidateResponse( + const ProtocolMessage& msg) const { + return (msg.tag == MESSAGE_TAG_REGISTRATION_RESPONSE) && + (msg.id != 0) && + (msg.dump_request_handle != NULL) && + (msg.dump_generated_handle != NULL) && + (msg.server_alive_handle != NULL); +} + +bool CrashGenerationClient::IsRegistered() const { + return crash_event_ != NULL; +} + +bool CrashGenerationClient::RequestDump(EXCEPTION_POINTERS* ex_info, + MDRawAssertionInfo* assert_info) { + if (!IsRegistered()) { + return false; + } + + exception_pointers_ = ex_info; + thread_id_ = GetCurrentThreadId(); + + if (assert_info) { + memcpy(&assert_info_, assert_info, sizeof(assert_info_)); + } else { + memset(&assert_info_, 0, sizeof(assert_info_)); + } + + return SignalCrashEventAndWait(); +} + +bool CrashGenerationClient::RequestDump(EXCEPTION_POINTERS* ex_info) { + return RequestDump(ex_info, NULL); +} + +bool CrashGenerationClient::RequestDump(MDRawAssertionInfo* assert_info) { + return RequestDump(NULL, assert_info); +} + +bool CrashGenerationClient::SignalCrashEventAndWait() { + assert(crash_event_); + assert(crash_generated_); + assert(server_alive_); + + // Reset the dump generated event before signaling the crash + // event so that the server can set the dump generated event + // once it is done generating the event. + if (!ResetEvent(crash_generated_)) { + return false; + } + + if (!SetEvent(crash_event_)) { + return false; + } + + HANDLE wait_handles[kWaitEventCount] = {crash_generated_, server_alive_}; + + DWORD result = WaitForMultipleObjects(kWaitEventCount, + wait_handles, + FALSE, + kWaitForServerTimeoutMs); + + // Crash dump was successfully generated only if the server + // signaled the crash generated event. + return result == WAIT_OBJECT_0; +} + +HANDLE CrashGenerationClient::DuplicatePipeToClientProcess(const wchar_t* pipe_name, + HANDLE hProcess) { + for (int i = 0; i < kPipeConnectMaxAttempts; ++i) { + HANDLE local_pipe = CreateFile(pipe_name, kPipeDesiredAccess, + 0, NULL, OPEN_EXISTING, + kPipeFlagsAndAttributes, NULL); + if (local_pipe != INVALID_HANDLE_VALUE) { + HANDLE remotePipe = INVALID_HANDLE_VALUE; + if (DuplicateHandle(GetCurrentProcess(), local_pipe, + hProcess, &remotePipe, 0, FALSE, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { + return remotePipe; + } else { + return INVALID_HANDLE_VALUE; + } + } + + // Cannot continue retrying if the error wasn't a busy pipe. + if (GetLastError() != ERROR_PIPE_BUSY) { + return INVALID_HANDLE_VALUE; + } + + if (!WaitNamedPipe(pipe_name, kPipeBusyWaitTimeoutMs)) { + return INVALID_HANDLE_VALUE; + } + } + return INVALID_HANDLE_VALUE; +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/crash_generation_client.h b/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/crash_generation_client.h new file mode 100644 index 000000000..457f73195 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/crash_generation_client.h @@ -0,0 +1,182 @@ +// Copyright (c) 2008, 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_WINDOWS_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_ +#define CLIENT_WINDOWS_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_ + +#include <windows.h> +#include <dbghelp.h> +#include <string> +#include <utility> +#include "client/windows/common/ipc_protocol.h" +#include "common/scoped_ptr.h" + +namespace google_breakpad { + +struct CustomClientInfo; + +// Abstraction of client-side implementation of out of process +// crash generation. +// +// The process that desires to have out-of-process crash dump +// generation service can use this class in the following way: +// +// * Create an instance. +// * Call Register method so that the client tries to register +// with the server process and check the return value. If +// registration is not successful, out-of-process crash dump +// generation will not be available +// * Request dump generation by calling either of the two +// overloaded RequestDump methods - one in case of exceptions +// and the other in case of assertion failures +// +// Note that it is the responsibility of the client code of +// this class to set the unhandled exception filter with the +// system by calling the SetUnhandledExceptionFilter function +// and the client code should explicitly request dump generation. +class CrashGenerationClient { + public: + CrashGenerationClient(const wchar_t* pipe_name, + MINIDUMP_TYPE dump_type, + const CustomClientInfo* custom_info); + + CrashGenerationClient(HANDLE pipe_handle, + MINIDUMP_TYPE dump_type, + const CustomClientInfo* custom_info); + + ~CrashGenerationClient(); + + // Registers the client process with the crash server. + // + // Returns true if the registration is successful; false otherwise. + bool Register(); + + // Requests the crash server to upload a previous dump with the + // given crash id. + bool RequestUpload(DWORD crash_id); + + bool RequestDump(EXCEPTION_POINTERS* ex_info, + MDRawAssertionInfo* assert_info); + + // Requests the crash server to generate a dump with the given + // exception information. + // + // Returns true if the dump was successful; false otherwise. Note that + // if the registration step was not performed or it was not successful, + // false will be returned. + bool RequestDump(EXCEPTION_POINTERS* ex_info); + + // Requests the crash server to generate a dump with the given + // assertion information. + // + // Returns true if the dump was successful; false otherwise. Note that + // if the registration step was not performed or it was not successful, + // false will be returned. + bool RequestDump(MDRawAssertionInfo* assert_info); + + // If the crash generation client is running in a sandbox that prevents it + // from opening the named pipe directly, the server process may open the + // handle and duplicate it into the client process with this helper method. + // Returns INVALID_HANDLE_VALUE on failure. The process must have been opened + // with the PROCESS_DUP_HANDLE access right. + static HANDLE DuplicatePipeToClientProcess(const wchar_t* pipe_name, + HANDLE hProcess); + + private: + // Connects to the appropriate pipe and sets the pipe handle state. + // + // Returns the pipe handle if everything goes well; otherwise Returns NULL. + HANDLE ConnectToServer(); + + // Performs a handshake with the server over the given pipe which should be + // already connected to the server. + // + // Returns true if handshake with the server was successful; false otherwise. + bool RegisterClient(HANDLE pipe); + + // Validates the given server response. + bool ValidateResponse(const ProtocolMessage& msg) const; + + // Returns true if the registration step succeeded; false otherwise. + bool IsRegistered() const; + + // Connects to the given named pipe with given parameters. + // + // Returns true if the connection is successful; false otherwise. + HANDLE ConnectToPipe(const wchar_t* pipe_name, + DWORD pipe_access, + DWORD flags_attrs); + + // Signals the crash event and wait for the server to generate crash. + bool SignalCrashEventAndWait(); + + // Pipe name to use to talk to server. + std::wstring pipe_name_; + + // Pipe handle duplicated from server process. Only valid before + // Register is called. + HANDLE pipe_handle_; + + // Custom client information + CustomClientInfo custom_info_; + + // Type of dump to generate. + MINIDUMP_TYPE dump_type_; + + // Event to signal in case of a crash. + HANDLE crash_event_; + + // Handle to wait on after signaling a crash for the server + // to finish generating crash dump. + HANDLE crash_generated_; + + // Handle to a mutex that will become signaled with WAIT_ABANDONED + // if the server process goes down. + HANDLE server_alive_; + + // Server process id. + DWORD server_process_id_; + + // Id of the thread that caused the crash. + DWORD thread_id_; + + // Exception pointers for an exception crash. + EXCEPTION_POINTERS* exception_pointers_; + + // Assertion info for an invalid parameter or pure call crash. + MDRawAssertionInfo assert_info_; + + // Disable copy ctor and operator=. + CrashGenerationClient(const CrashGenerationClient& crash_client); + CrashGenerationClient& operator=(const CrashGenerationClient& crash_client); +}; + +} // namespace google_breakpad + +#endif // CLIENT_WINDOWS_CRASH_GENERATION_CRASH_GENERATION_CLIENT_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/crash_generation_server.cc b/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/crash_generation_server.cc new file mode 100644 index 000000000..bb0968fe0 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/crash_generation_server.cc @@ -0,0 +1,931 @@ +// Copyright (c) 2008, 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/windows/crash_generation/crash_generation_server.h" +#include <windows.h> +#include <cassert> +#include <list> +#include "client/windows/common/auto_critical_section.h" +#include "common/scoped_ptr.h" + +#include "client/windows/crash_generation/client_info.h" + +namespace google_breakpad { + +// Output buffer size. +static const size_t kOutBufferSize = 64; + +// Input buffer size. +static const size_t kInBufferSize = 64; + +// Access flags for the client on the dump request event. +static const DWORD kDumpRequestEventAccess = EVENT_MODIFY_STATE; + +// Access flags for the client on the dump generated event. +static const DWORD kDumpGeneratedEventAccess = EVENT_MODIFY_STATE | + SYNCHRONIZE; + +// Access flags for the client on the mutex. +static const DWORD kMutexAccess = SYNCHRONIZE; + +// Attribute flags for the pipe. +static const DWORD kPipeAttr = FILE_FLAG_FIRST_PIPE_INSTANCE | + PIPE_ACCESS_DUPLEX | + FILE_FLAG_OVERLAPPED; + +// Mode for the pipe. +static const DWORD kPipeMode = PIPE_TYPE_MESSAGE | + PIPE_READMODE_MESSAGE | + PIPE_WAIT; + +// For pipe I/O, execute the callback in the wait thread itself, +// since the callback does very little work. The callback executes +// the code for one of the states of the server state machine and +// the code for all of the states perform async I/O and hence +// finish very quickly. +static const ULONG kPipeIOThreadFlags = WT_EXECUTEINWAITTHREAD; + +// Dump request threads will, most likely, generate dumps. That may +// take some time to finish, so specify WT_EXECUTELONGFUNCTION flag. +static const ULONG kDumpRequestThreadFlags = WT_EXECUTEINWAITTHREAD | + WT_EXECUTELONGFUNCTION; + +static bool IsClientRequestValid(const ProtocolMessage& msg) { + return msg.tag == MESSAGE_TAG_UPLOAD_REQUEST || + (msg.tag == MESSAGE_TAG_REGISTRATION_REQUEST && + msg.id != 0 && + msg.thread_id != NULL && + msg.exception_pointers != NULL && + msg.assert_info != NULL); +} + +#ifndef NDEBUG +static bool CheckForIOIncomplete(bool success) { + // We should never get an I/O incomplete since we should not execute this + // unless the operation has finished and the overlapped event is signaled. If + // we do get INCOMPLETE, we have a bug in our code. + return success ? false : (GetLastError() == ERROR_IO_INCOMPLETE); +} +#endif + +CrashGenerationServer::CrashGenerationServer( + const std::wstring& pipe_name, + SECURITY_ATTRIBUTES* pipe_sec_attrs, + OnClientConnectedCallback connect_callback, + void* connect_context, + OnClientDumpRequestCallback dump_callback, + void* dump_context, + OnClientExitedCallback exit_callback, + void* exit_context, + OnClientUploadRequestCallback upload_request_callback, + void* upload_context, + bool generate_dumps, + const std::wstring* dump_path) + : pipe_name_(pipe_name), + pipe_sec_attrs_(pipe_sec_attrs), + pipe_(NULL), + pipe_wait_handle_(NULL), + server_alive_handle_(NULL), + connect_callback_(connect_callback), + connect_context_(connect_context), + dump_callback_(dump_callback), + dump_context_(dump_context), + exit_callback_(exit_callback), + exit_context_(exit_context), + upload_request_callback_(upload_request_callback), + upload_context_(upload_context), + generate_dumps_(generate_dumps), + pre_fetch_custom_info_(true), + dump_path_(dump_path ? *dump_path : L""), + server_state_(IPC_SERVER_STATE_UNINITIALIZED), + shutting_down_(false), + overlapped_(), + client_info_(NULL) { + InitializeCriticalSection(&sync_); +} + +// This should never be called from the OnPipeConnected callback. +// Otherwise the UnregisterWaitEx call below will cause a deadlock. +CrashGenerationServer::~CrashGenerationServer() { + // New scope to release the lock automatically. + { + // Make sure no clients are added or removed beyond this point. + // Before adding or removing any clients, the critical section + // must be entered and the shutting_down_ flag checked. The + // critical section is then exited only after the clients_ list + // modifications are done and the list is in a consistent state. + AutoCriticalSection lock(&sync_); + + // Indicate to existing threads that server is shutting down. + shutting_down_ = true; + } + // No one will modify the clients_ list beyond this point - + // not even from another thread. + + // Even if there are no current worker threads running, it is possible that + // an I/O request is pending on the pipe right now but not yet done. + // In fact, it's very likely this is the case unless we are in an ERROR + // state. If we don't wait for the pending I/O to be done, then when the I/O + // completes, it may write to invalid memory. AppVerifier will flag this + // problem too. So we disconnect from the pipe and then wait for the server + // to get into error state so that the pending I/O will fail and get + // cleared. + DisconnectNamedPipe(pipe_); + int num_tries = 100; + while (num_tries-- && server_state_ != IPC_SERVER_STATE_ERROR) { + Sleep(10); + } + + // Unregister wait on the pipe. + if (pipe_wait_handle_) { + // Wait for already executing callbacks to finish. + UnregisterWaitEx(pipe_wait_handle_, INVALID_HANDLE_VALUE); + } + + // Close the pipe to avoid further client connections. + if (pipe_) { + CloseHandle(pipe_); + } + + // Request all ClientInfo objects to unregister all waits. + // No need to enter the critical section because no one is allowed to modify + // the clients_ list once the shutting_down_ flag is set. + std::list<ClientInfo*>::iterator iter; + for (iter = clients_.begin(); iter != clients_.end(); ++iter) { + ClientInfo* client_info = *iter; + // Unregister waits. Wait for already executing callbacks to finish. + // Unregister the client process exit wait first and only then unregister + // the dump request wait. The reason is that the OnClientExit callback + // also unregisters the dump request wait and such a race (doing the same + // unregistration from two threads) is undesirable. + client_info->UnregisterProcessExitWait(true); + client_info->UnregisterDumpRequestWaitAndBlockUntilNoPending(); + + // Destroying the ClientInfo here is safe because all wait operations for + // this ClientInfo were unregistered and no pending or running callbacks + // for this ClientInfo can possible exist (block_until_no_pending option + // was used). + delete client_info; + } + + if (server_alive_handle_) { + // Release the mutex before closing the handle so that clients requesting + // dumps wait for a long time for the server to generate a dump. + ReleaseMutex(server_alive_handle_); + CloseHandle(server_alive_handle_); + } + + if (overlapped_.hEvent) { + CloseHandle(overlapped_.hEvent); + } + + DeleteCriticalSection(&sync_); +} + +bool CrashGenerationServer::Start() { + if (server_state_ != IPC_SERVER_STATE_UNINITIALIZED) { + return false; + } + + server_state_ = IPC_SERVER_STATE_INITIAL; + + server_alive_handle_ = CreateMutex(NULL, TRUE, NULL); + if (!server_alive_handle_) { + return false; + } + + // Event to signal the client connection and pipe reads and writes. + overlapped_.hEvent = CreateEvent(NULL, // Security descriptor. + TRUE, // Manual reset. + FALSE, // Initially nonsignaled. + NULL); // Name. + if (!overlapped_.hEvent) { + return false; + } + + // Register a callback with the thread pool for the client connection. + if (!RegisterWaitForSingleObject(&pipe_wait_handle_, + overlapped_.hEvent, + OnPipeConnected, + this, + INFINITE, + kPipeIOThreadFlags)) { + return false; + } + + pipe_ = CreateNamedPipe(pipe_name_.c_str(), + kPipeAttr, + kPipeMode, + 1, + kOutBufferSize, + kInBufferSize, + 0, + pipe_sec_attrs_); + if (pipe_ == INVALID_HANDLE_VALUE) { + return false; + } + + // Kick-start the state machine. This will initiate an asynchronous wait + // for client connections. + if (!SetEvent(overlapped_.hEvent)) { + server_state_ = IPC_SERVER_STATE_ERROR; + return false; + } + + // If we are in error state, it's because we failed to start listening. + return true; +} + +// If the server thread serving clients ever gets into the +// ERROR state, reset the event, close the pipe and remain +// in the error state forever. Error state means something +// that we didn't account for has happened, and it's dangerous +// to do anything unknowingly. +void CrashGenerationServer::HandleErrorState() { + assert(server_state_ == IPC_SERVER_STATE_ERROR); + + // If the server is shutting down anyway, don't clean up + // here since shut down process will clean up. + if (shutting_down_) { + return; + } + + if (pipe_wait_handle_) { + UnregisterWait(pipe_wait_handle_); + pipe_wait_handle_ = NULL; + } + + if (pipe_) { + CloseHandle(pipe_); + pipe_ = NULL; + } + + if (overlapped_.hEvent) { + CloseHandle(overlapped_.hEvent); + overlapped_.hEvent = NULL; + } +} + +// When the server thread serving clients is in the INITIAL state, +// try to connect to the pipe asynchronously. If the connection +// finishes synchronously, directly go into the CONNECTED state; +// otherwise go into the CONNECTING state. For any problems, go +// into the ERROR state. +void CrashGenerationServer::HandleInitialState() { + assert(server_state_ == IPC_SERVER_STATE_INITIAL); + + if (!ResetEvent(overlapped_.hEvent)) { + EnterErrorState(); + return; + } + + bool success = ConnectNamedPipe(pipe_, &overlapped_) != FALSE; + DWORD error_code = success ? ERROR_SUCCESS : GetLastError(); + + // From MSDN, it is not clear that when ConnectNamedPipe is used + // in an overlapped mode, will it ever return non-zero value, and + // if so, in what cases. + assert(!success); + + switch (error_code) { + case ERROR_IO_PENDING: + EnterStateWhenSignaled(IPC_SERVER_STATE_CONNECTING); + break; + + case ERROR_PIPE_CONNECTED: + EnterStateImmediately(IPC_SERVER_STATE_CONNECTED); + break; + + default: + EnterErrorState(); + break; + } +} + +// When the server thread serving the clients is in the CONNECTING state, +// try to get the result of the asynchronous connection request using +// the OVERLAPPED object. If the result indicates the connection is done, +// go into the CONNECTED state. If the result indicates I/O is still +// INCOMPLETE, remain in the CONNECTING state. For any problems, +// go into the DISCONNECTING state. +void CrashGenerationServer::HandleConnectingState() { + assert(server_state_ == IPC_SERVER_STATE_CONNECTING); + + DWORD bytes_count = 0; + bool success = GetOverlappedResult(pipe_, + &overlapped_, + &bytes_count, + FALSE) != FALSE; + DWORD error_code = success ? ERROR_SUCCESS : GetLastError(); + + if (success) { + EnterStateImmediately(IPC_SERVER_STATE_CONNECTED); + } else if (error_code != ERROR_IO_INCOMPLETE) { + EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING); + } else { + // remain in CONNECTING state + } +} + +// When the server thread serving the clients is in the CONNECTED state, +// try to issue an asynchronous read from the pipe. If read completes +// synchronously or if I/O is pending then go into the READING state. +// For any problems, go into the DISCONNECTING state. +void CrashGenerationServer::HandleConnectedState() { + assert(server_state_ == IPC_SERVER_STATE_CONNECTED); + + DWORD bytes_count = 0; + memset(&msg_, 0, sizeof(msg_)); + bool success = ReadFile(pipe_, + &msg_, + sizeof(msg_), + &bytes_count, + &overlapped_) != FALSE; + DWORD error_code = success ? ERROR_SUCCESS : GetLastError(); + + // Note that the asynchronous read issued above can finish before the + // code below executes. But, it is okay to change state after issuing + // the asynchronous read. This is because even if the asynchronous read + // is done, the callback for it would not be executed until the current + // thread finishes its execution. + if (success || error_code == ERROR_IO_PENDING) { + EnterStateWhenSignaled(IPC_SERVER_STATE_READING); + } else { + EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING); + } +} + +// When the server thread serving the clients is in the READING state, +// try to get the result of the async read. If async read is done, +// go into the READ_DONE state. For any problems, go into the +// DISCONNECTING state. +void CrashGenerationServer::HandleReadingState() { + assert(server_state_ == IPC_SERVER_STATE_READING); + + DWORD bytes_count = 0; + bool success = GetOverlappedResult(pipe_, + &overlapped_, + &bytes_count, + FALSE) != FALSE; + if (success && bytes_count == sizeof(ProtocolMessage)) { + EnterStateImmediately(IPC_SERVER_STATE_READ_DONE); + return; + } + + assert(!CheckForIOIncomplete(success)); + EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING); +} + +// When the server thread serving the client is in the READ_DONE state, +// validate the client's request message, register the client by +// creating appropriate objects and prepare the response. Then try to +// write the response to the pipe asynchronously. If that succeeds, +// go into the WRITING state. For any problems, go into the DISCONNECTING +// state. +void CrashGenerationServer::HandleReadDoneState() { + assert(server_state_ == IPC_SERVER_STATE_READ_DONE); + + if (!IsClientRequestValid(msg_)) { + EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING); + return; + } + + if (msg_.tag == MESSAGE_TAG_UPLOAD_REQUEST) { + if (upload_request_callback_) + upload_request_callback_(upload_context_, msg_.id); + EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING); + return; + } + + scoped_ptr<ClientInfo> client_info( + new ClientInfo(this, + msg_.id, + msg_.dump_type, + msg_.thread_id, + msg_.exception_pointers, + msg_.assert_info, + msg_.custom_client_info)); + + if (!client_info->Initialize()) { + EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING); + return; + } + + // Issues an asynchronous WriteFile call if successful. + // Iff successful, assigns ownership of the client_info pointer to the server + // instance, in which case we must be sure not to free it in this function. + if (!RespondToClient(client_info.get())) { + EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING); + return; + } + + // This is only valid as long as it can be found in the clients_ list + client_info_ = client_info.release(); + + // Note that the asynchronous write issued by RespondToClient function + // can finish before the code below executes. But it is okay to change + // state after issuing the asynchronous write. This is because even if + // the asynchronous write is done, the callback for it would not be + // executed until the current thread finishes its execution. + EnterStateWhenSignaled(IPC_SERVER_STATE_WRITING); +} + +// When the server thread serving the clients is in the WRITING state, +// try to get the result of the async write. If the async write is done, +// go into the WRITE_DONE state. For any problems, go into the +// DISONNECTING state. +void CrashGenerationServer::HandleWritingState() { + assert(server_state_ == IPC_SERVER_STATE_WRITING); + + DWORD bytes_count = 0; + bool success = GetOverlappedResult(pipe_, + &overlapped_, + &bytes_count, + FALSE) != FALSE; + if (success) { + EnterStateImmediately(IPC_SERVER_STATE_WRITE_DONE); + return; + } + + assert(!CheckForIOIncomplete(success)); + EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING); +} + +// When the server thread serving the clients is in the WRITE_DONE state, +// try to issue an async read on the pipe. If the read completes synchronously +// or if I/O is still pending then go into the READING_ACK state. For any +// issues, go into the DISCONNECTING state. +void CrashGenerationServer::HandleWriteDoneState() { + assert(server_state_ == IPC_SERVER_STATE_WRITE_DONE); + + DWORD bytes_count = 0; + bool success = ReadFile(pipe_, + &msg_, + sizeof(msg_), + &bytes_count, + &overlapped_) != FALSE; + DWORD error_code = success ? ERROR_SUCCESS : GetLastError(); + + if (success) { + EnterStateImmediately(IPC_SERVER_STATE_READING_ACK); + } else if (error_code == ERROR_IO_PENDING) { + EnterStateWhenSignaled(IPC_SERVER_STATE_READING_ACK); + } else { + EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING); + } +} + +// When the server thread serving the clients is in the READING_ACK state, +// try to get result of async read. Go into the DISCONNECTING state. +void CrashGenerationServer::HandleReadingAckState() { + assert(server_state_ == IPC_SERVER_STATE_READING_ACK); + + DWORD bytes_count = 0; + bool success = GetOverlappedResult(pipe_, + &overlapped_, + &bytes_count, + FALSE) != FALSE; + if (success) { + // The connection handshake with the client is now complete; perform + // the callback. + if (connect_callback_) { + // Note that there is only a single copy of the ClientInfo of the + // currently connected client. However it is being referenced from + // two different places: + // - the client_info_ member + // - the clients_ list + // The lifetime of this ClientInfo depends on the lifetime of the + // client process - basically it can go away at any time. + // However, as long as it is referenced by the clients_ list it + // is guaranteed to be valid. Enter the critical section and check + // to see whether the client_info_ can be found in the list. + // If found, execute the callback and only then leave the critical + // section. + AutoCriticalSection lock(&sync_); + + bool client_is_still_alive = false; + std::list<ClientInfo*>::iterator iter; + for (iter = clients_.begin(); iter != clients_.end(); ++iter) { + if (client_info_ == *iter) { + client_is_still_alive = true; + break; + } + } + + if (client_is_still_alive) { + connect_callback_(connect_context_, client_info_); + } + } + } else { + assert(!CheckForIOIncomplete(success)); + } + + EnterStateImmediately(IPC_SERVER_STATE_DISCONNECTING); +} + +// When the server thread serving the client is in the DISCONNECTING state, +// disconnect from the pipe and reset the event. If anything fails, go into +// the ERROR state. If it goes well, go into the INITIAL state and set the +// event to start all over again. +void CrashGenerationServer::HandleDisconnectingState() { + assert(server_state_ == IPC_SERVER_STATE_DISCONNECTING); + + // Done serving the client. + client_info_ = NULL; + + overlapped_.Internal = NULL; + overlapped_.InternalHigh = NULL; + overlapped_.Offset = 0; + overlapped_.OffsetHigh = 0; + overlapped_.Pointer = NULL; + + if (!ResetEvent(overlapped_.hEvent)) { + EnterErrorState(); + return; + } + + if (!DisconnectNamedPipe(pipe_)) { + EnterErrorState(); + return; + } + + // If the server is shutting down do not connect to the + // next client. + if (shutting_down_) { + return; + } + + EnterStateImmediately(IPC_SERVER_STATE_INITIAL); +} + +void CrashGenerationServer::EnterErrorState() { + SetEvent(overlapped_.hEvent); + server_state_ = IPC_SERVER_STATE_ERROR; +} + +void CrashGenerationServer::EnterStateWhenSignaled(IPCServerState state) { + server_state_ = state; +} + +void CrashGenerationServer::EnterStateImmediately(IPCServerState state) { + server_state_ = state; + + if (!SetEvent(overlapped_.hEvent)) { + server_state_ = IPC_SERVER_STATE_ERROR; + } +} + +bool CrashGenerationServer::PrepareReply(const ClientInfo& client_info, + ProtocolMessage* reply) const { + reply->tag = MESSAGE_TAG_REGISTRATION_RESPONSE; + reply->id = GetCurrentProcessId(); + + if (CreateClientHandles(client_info, reply)) { + return true; + } + + // Closing of remote handles (belonging to a different process) can + // only be done through DuplicateHandle. + if (reply->dump_request_handle) { + DuplicateHandle(client_info.process_handle(), // hSourceProcessHandle + reply->dump_request_handle, // hSourceHandle + NULL, // hTargetProcessHandle + 0, // lpTargetHandle + 0, // dwDesiredAccess + FALSE, // bInheritHandle + DUPLICATE_CLOSE_SOURCE); // dwOptions + reply->dump_request_handle = NULL; + } + + if (reply->dump_generated_handle) { + DuplicateHandle(client_info.process_handle(), // hSourceProcessHandle + reply->dump_generated_handle, // hSourceHandle + NULL, // hTargetProcessHandle + 0, // lpTargetHandle + 0, // dwDesiredAccess + FALSE, // bInheritHandle + DUPLICATE_CLOSE_SOURCE); // dwOptions + reply->dump_generated_handle = NULL; + } + + if (reply->server_alive_handle) { + DuplicateHandle(client_info.process_handle(), // hSourceProcessHandle + reply->server_alive_handle, // hSourceHandle + NULL, // hTargetProcessHandle + 0, // lpTargetHandle + 0, // dwDesiredAccess + FALSE, // bInheritHandle + DUPLICATE_CLOSE_SOURCE); // dwOptions + reply->server_alive_handle = NULL; + } + + return false; +} + +bool CrashGenerationServer::CreateClientHandles(const ClientInfo& client_info, + ProtocolMessage* reply) const { + HANDLE current_process = GetCurrentProcess(); + if (!DuplicateHandle(current_process, + client_info.dump_requested_handle(), + client_info.process_handle(), + &reply->dump_request_handle, + kDumpRequestEventAccess, + FALSE, + 0)) { + return false; + } + + if (!DuplicateHandle(current_process, + client_info.dump_generated_handle(), + client_info.process_handle(), + &reply->dump_generated_handle, + kDumpGeneratedEventAccess, + FALSE, + 0)) { + return false; + } + + if (!DuplicateHandle(current_process, + server_alive_handle_, + client_info.process_handle(), + &reply->server_alive_handle, + kMutexAccess, + FALSE, + 0)) { + return false; + } + + return true; +} + +bool CrashGenerationServer::RespondToClient(ClientInfo* client_info) { + ProtocolMessage reply; + if (!PrepareReply(*client_info, &reply)) { + return false; + } + + DWORD bytes_count = 0; + bool success = WriteFile(pipe_, + &reply, + sizeof(reply), + &bytes_count, + &overlapped_) != FALSE; + DWORD error_code = success ? ERROR_SUCCESS : GetLastError(); + + if (!success && error_code != ERROR_IO_PENDING) { + return false; + } + + // Takes over ownership of client_info. We MUST return true if AddClient + // succeeds. + return AddClient(client_info); +} + +// The server thread servicing the clients runs this method. The method +// implements the state machine described in ReadMe.txt along with the +// helper methods HandleXXXState. +void CrashGenerationServer::HandleConnectionRequest() { + // If the server is shutting down, get into ERROR state, reset the event so + // more workers don't run and return immediately. + if (shutting_down_) { + server_state_ = IPC_SERVER_STATE_ERROR; + ResetEvent(overlapped_.hEvent); + return; + } + + switch (server_state_) { + case IPC_SERVER_STATE_ERROR: + HandleErrorState(); + break; + + case IPC_SERVER_STATE_INITIAL: + HandleInitialState(); + break; + + case IPC_SERVER_STATE_CONNECTING: + HandleConnectingState(); + break; + + case IPC_SERVER_STATE_CONNECTED: + HandleConnectedState(); + break; + + case IPC_SERVER_STATE_READING: + HandleReadingState(); + break; + + case IPC_SERVER_STATE_READ_DONE: + HandleReadDoneState(); + break; + + case IPC_SERVER_STATE_WRITING: + HandleWritingState(); + break; + + case IPC_SERVER_STATE_WRITE_DONE: + HandleWriteDoneState(); + break; + + case IPC_SERVER_STATE_READING_ACK: + HandleReadingAckState(); + break; + + case IPC_SERVER_STATE_DISCONNECTING: + HandleDisconnectingState(); + break; + + default: + assert(false); + // This indicates that we added one more state without + // adding handling code. + server_state_ = IPC_SERVER_STATE_ERROR; + break; + } +} + +bool CrashGenerationServer::AddClient(ClientInfo* client_info) { + HANDLE request_wait_handle = NULL; + if (!RegisterWaitForSingleObject(&request_wait_handle, + client_info->dump_requested_handle(), + OnDumpRequest, + client_info, + INFINITE, + kDumpRequestThreadFlags)) { + return false; + } + + client_info->set_dump_request_wait_handle(request_wait_handle); + + // OnClientEnd will be called when the client process terminates. + HANDLE process_wait_handle = NULL; + if (!RegisterWaitForSingleObject(&process_wait_handle, + client_info->process_handle(), + OnClientEnd, + client_info, + INFINITE, + WT_EXECUTEONLYONCE)) { + return false; + } + + client_info->set_process_exit_wait_handle(process_wait_handle); + + // New scope to hold the lock for the shortest time. + { + AutoCriticalSection lock(&sync_); + if (shutting_down_) { + // If server is shutting down, don't add new clients + return false; + } + clients_.push_back(client_info); + } + + return true; +} + +// static +void CALLBACK CrashGenerationServer::OnPipeConnected(void* context, BOOLEAN) { + assert(context); + + CrashGenerationServer* obj = + reinterpret_cast<CrashGenerationServer*>(context); + obj->HandleConnectionRequest(); +} + +// static +void CALLBACK CrashGenerationServer::OnDumpRequest(void* context, BOOLEAN) { + assert(context); + ClientInfo* client_info = reinterpret_cast<ClientInfo*>(context); + + CrashGenerationServer* crash_server = client_info->crash_server(); + assert(crash_server); + if (crash_server->pre_fetch_custom_info_) { + client_info->PopulateCustomInfo(); + } + crash_server->HandleDumpRequest(*client_info); + + ResetEvent(client_info->dump_requested_handle()); +} + +// static +void CALLBACK CrashGenerationServer::OnClientEnd(void* context, BOOLEAN) { + assert(context); + ClientInfo* client_info = reinterpret_cast<ClientInfo*>(context); + + CrashGenerationServer* crash_server = client_info->crash_server(); + assert(crash_server); + + crash_server->HandleClientProcessExit(client_info); +} + +void CrashGenerationServer::HandleClientProcessExit(ClientInfo* client_info) { + assert(client_info); + + // Must unregister the dump request wait operation and wait for any + // dump requests that might be pending to finish before proceeding + // with the client_info cleanup. + client_info->UnregisterDumpRequestWaitAndBlockUntilNoPending(); + + if (exit_callback_) { + exit_callback_(exit_context_, client_info); + } + + // Start a new scope to release lock automatically. + { + AutoCriticalSection lock(&sync_); + if (shutting_down_) { + // The crash generation server is shutting down and as part of the + // shutdown process it will delete all clients from the clients_ list. + return; + } + clients_.remove(client_info); + } + + // Explicitly unregister the process exit wait using the non-blocking method. + // Otherwise, the destructor will attempt to unregister it using the blocking + // method which will lead to a deadlock because it is being called from the + // callback of the same wait operation + client_info->UnregisterProcessExitWait(false); + + delete client_info; +} + +void CrashGenerationServer::HandleDumpRequest(const ClientInfo& client_info) { + bool execute_callback = true; + // Generate the dump only if it's explicitly requested by the + // server application; otherwise the server might want to generate + // dump in the callback. + std::wstring dump_path; + if (generate_dumps_) { + if (!GenerateDump(client_info, &dump_path)) { + // client proccess terminated or some other error + execute_callback = false; + } + } + + if (dump_callback_ && execute_callback) { + std::wstring* ptr_dump_path = (dump_path == L"") ? NULL : &dump_path; + dump_callback_(dump_context_, &client_info, ptr_dump_path); + } + + SetEvent(client_info.dump_generated_handle()); +} + +bool CrashGenerationServer::GenerateDump(const ClientInfo& client, + std::wstring* dump_path) { + assert(client.pid() != 0); + assert(client.process_handle()); + + // We have to get the address of EXCEPTION_INFORMATION from + // the client process address space. + EXCEPTION_POINTERS* client_ex_info = NULL; + if (!client.GetClientExceptionInfo(&client_ex_info)) { + return false; + } + + DWORD client_thread_id = 0; + if (!client.GetClientThreadId(&client_thread_id)) { + return false; + } + + MinidumpGenerator dump_generator(dump_path_, + client.process_handle(), + client.pid(), + client_thread_id, + GetCurrentThreadId(), + client_ex_info, + client.assert_info(), + client.dump_type(), + true); + if (!dump_generator.GenerateDumpFile(dump_path)) { + return false; + } + return dump_generator.WriteMinidump(); +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/crash_generation_server.h b/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/crash_generation_server.h new file mode 100644 index 000000000..0ea90e510 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/crash_generation_server.h @@ -0,0 +1,299 @@ +// Copyright (c) 2008, 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_WINDOWS_CRASH_GENERATION_CRASH_GENERATION_SERVER_H__ +#define CLIENT_WINDOWS_CRASH_GENERATION_CRASH_GENERATION_SERVER_H__ + +#include <list> +#include <string> +#include "client/windows/common/ipc_protocol.h" +#include "client/windows/crash_generation/minidump_generator.h" +#include "common/scoped_ptr.h" + +namespace google_breakpad { +class ClientInfo; + +// Abstraction for server side implementation of out-of-process crash +// generation protocol for Windows platform only. It generates Windows +// minidump files for client processes that request dump generation. When +// the server is requested to start listening for clients (by calling the +// Start method), it creates a named pipe and waits for the clients to +// register. In response, it hands them event handles that the client can +// signal to request dump generation. When the clients request dump +// generation in this way, the server generates Windows minidump files. +class CrashGenerationServer { + public: + typedef void (*OnClientConnectedCallback)(void* context, + const ClientInfo* client_info); + + typedef void (*OnClientDumpRequestCallback)(void* context, + const ClientInfo* client_info, + const std::wstring* file_path); + + typedef void (*OnClientExitedCallback)(void* context, + const ClientInfo* client_info); + + typedef void (*OnClientUploadRequestCallback)(void* context, + const DWORD crash_id); + + // Creates an instance with the given parameters. + // + // Parameter pipe_name: Name of the Windows named pipe + // Parameter pipe_sec_attrs Security attributes to set on the pipe. Pass + // NULL to use default security on the pipe. By default, the pipe created + // allows Local System, Administrators and the Creator full control and + // the Everyone group read access on the pipe. + // Parameter connect_callback: Callback for a new client connection. + // Parameter connect_context: Context for client connection callback. + // Parameter crash_callback: Callback for a client crash dump request. + // Parameter crash_context: Context for client crash dump request callback. + // Parameter exit_callback: Callback for client process exit. + // Parameter exit_context: Context for client exit callback. + // Parameter generate_dumps: Whether to automatically generate dumps. + // Client code of this class might want to generate dumps explicitly in the + // crash dump request callback. In that case, false can be passed for this + // parameter. + // Parameter dump_path: Path for generating dumps; required only if true is + // passed for generateDumps parameter; NULL can be passed otherwise. + CrashGenerationServer(const std::wstring& pipe_name, + SECURITY_ATTRIBUTES* pipe_sec_attrs, + OnClientConnectedCallback connect_callback, + void* connect_context, + OnClientDumpRequestCallback dump_callback, + void* dump_context, + OnClientExitedCallback exit_callback, + void* exit_context, + OnClientUploadRequestCallback upload_request_callback, + void* upload_context, + bool generate_dumps, + const std::wstring* dump_path); + + ~CrashGenerationServer(); + + // Performs initialization steps needed to start listening to clients. Upon + // successful return clients may connect to this server's pipe. + // + // Returns true if initialization is successful; false otherwise. + bool Start(); + + void pre_fetch_custom_info(bool do_pre_fetch) { + pre_fetch_custom_info_ = do_pre_fetch; + } + + private: + // Various states the client can be in during the handshake with + // the server. + enum IPCServerState { + // Server starts in this state. + IPC_SERVER_STATE_UNINITIALIZED, + + // Server is in error state and it cannot serve any clients. + IPC_SERVER_STATE_ERROR, + + // Server starts in this state. + IPC_SERVER_STATE_INITIAL, + + // Server has issued an async connect to the pipe and it is waiting + // for the connection to be established. + IPC_SERVER_STATE_CONNECTING, + + // Server is connected successfully. + IPC_SERVER_STATE_CONNECTED, + + // Server has issued an async read from the pipe and it is waiting for + // the read to finish. + IPC_SERVER_STATE_READING, + + // Server is done reading from the pipe. + IPC_SERVER_STATE_READ_DONE, + + // Server has issued an async write to the pipe and it is waiting for + // the write to finish. + IPC_SERVER_STATE_WRITING, + + // Server is done writing to the pipe. + IPC_SERVER_STATE_WRITE_DONE, + + // Server has issued an async read from the pipe for an ack and it + // is waiting for the read to finish. + IPC_SERVER_STATE_READING_ACK, + + // Server is done writing to the pipe and it is now ready to disconnect + // and reconnect. + IPC_SERVER_STATE_DISCONNECTING + }; + + // + // Helper methods to handle various server IPC states. + // + void HandleErrorState(); + void HandleInitialState(); + void HandleConnectingState(); + void HandleConnectedState(); + void HandleReadingState(); + void HandleReadDoneState(); + void HandleWritingState(); + void HandleWriteDoneState(); + void HandleReadingAckState(); + void HandleDisconnectingState(); + + // Prepares reply for a client from the given parameters. + bool PrepareReply(const ClientInfo& client_info, + ProtocolMessage* reply) const; + + // Duplicates various handles in the ClientInfo object for the client + // process and stores them in the given ProtocolMessage instance. If + // creating any handle fails, ProtocolMessage will contain the handles + // already created successfully, which should be closed by the caller. + bool CreateClientHandles(const ClientInfo& client_info, + ProtocolMessage* reply) const; + + // Response to the given client. Return true if all steps of + // responding to the client succeed, false otherwise. + bool RespondToClient(ClientInfo* client_info); + + // Handles a connection request from the client. + void HandleConnectionRequest(); + + // Handles a dump request from the client. + void HandleDumpRequest(const ClientInfo& client_info); + + // Callback for pipe connected event. + static void CALLBACK OnPipeConnected(void* context, BOOLEAN timer_or_wait); + + // Callback for a dump request. + static void CALLBACK OnDumpRequest(void* context, BOOLEAN timer_or_wait); + + // Callback for client process exit event. + static void CALLBACK OnClientEnd(void* context, BOOLEAN timer_or_wait); + + // Handles client process exit. + void HandleClientProcessExit(ClientInfo* client_info); + + // Adds the given client to the list of registered clients. + bool AddClient(ClientInfo* client_info); + + // Generates dump for the given client. + bool GenerateDump(const ClientInfo& client, std::wstring* dump_path); + + // Puts the server in a permanent error state and sets a signal such that + // the state will be immediately entered after the current state transition + // is complete. + void EnterErrorState(); + + // Puts the server in the specified state and sets a signal such that the + // state is immediately entered after the current state transition is + // complete. + void EnterStateImmediately(IPCServerState state); + + // Puts the server in the specified state. No signal will be set, so the state + // transition will only occur when signaled manually or by completion of an + // asynchronous IO operation. + void EnterStateWhenSignaled(IPCServerState state); + + // Sync object for thread-safe access to the shared list of clients. + CRITICAL_SECTION sync_; + + // List of clients. + std::list<ClientInfo*> clients_; + + // Pipe name. + std::wstring pipe_name_; + + // Pipe security attributes + SECURITY_ATTRIBUTES* pipe_sec_attrs_; + + // Handle to the pipe used for handshake with clients. + HANDLE pipe_; + + // Pipe wait handle. + HANDLE pipe_wait_handle_; + + // Handle to server-alive mutex. + HANDLE server_alive_handle_; + + // Callback for a successful client connection. + OnClientConnectedCallback connect_callback_; + + // Context for client connected callback. + void* connect_context_; + + // Callback for a client dump request. + OnClientDumpRequestCallback dump_callback_; + + // Context for client dump request callback. + void* dump_context_; + + // Callback for client process exit. + OnClientExitedCallback exit_callback_; + + // Context for client process exit callback. + void* exit_context_; + + // Callback for upload request. + OnClientUploadRequestCallback upload_request_callback_; + + // Context for upload request callback. + void* upload_context_; + + // Whether to generate dumps. + bool generate_dumps_; + + // Wether to populate custom information up-front. + bool pre_fetch_custom_info_; + + // The dump path for the server. + const std::wstring dump_path_; + + // State of the server in performing the IPC with the client. + // Note that since we restrict the pipe to one instance, we + // only need to keep one state of the server. Otherwise, server + // would have one state per client it is talking to. + IPCServerState server_state_; + + // Whether the server is shutting down. + bool shutting_down_; + + // Overlapped instance for async I/O on the pipe. + OVERLAPPED overlapped_; + + // Message object used in IPC with the client. + ProtocolMessage msg_; + + // Client Info for the client that's connecting to the server. + ClientInfo* client_info_; + + // Disable copy ctor and operator=. + CrashGenerationServer(const CrashGenerationServer& crash_server); + CrashGenerationServer& operator=(const CrashGenerationServer& crash_server); +}; + +} // namespace google_breakpad + +#endif // CLIENT_WINDOWS_CRASH_GENERATION_CRASH_GENERATION_SERVER_H__ diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/minidump_generator.cc b/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/minidump_generator.cc new file mode 100644 index 000000000..786c9b937 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/minidump_generator.cc @@ -0,0 +1,579 @@ +// Copyright (c) 2008, 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/windows/crash_generation/minidump_generator.h" + +#include <assert.h> +#include <avrfsdk.h> + +#include <algorithm> +#include <iterator> +#include <list> +#include <vector> + +#include "client/windows/common/auto_critical_section.h" +#include "common/scoped_ptr.h" +#include "common/windows/guid_string.h" + +using std::wstring; + +namespace { + +// A helper class used to collect handle operations data. Unlike +// |MiniDumpWithHandleData| it records the operations for a single handle value +// only, making it possible to include this information to a minidump. +class HandleTraceData { + public: + HandleTraceData(); + ~HandleTraceData(); + + // Collects the handle operations data and formats a user stream to be added + // to the minidump. + bool CollectHandleData(HANDLE process_handle, + EXCEPTION_POINTERS* exception_pointers); + + // Fills the user dump entry with a pointer to the collected handle operations + // data. Returns |true| if the entry was initialized successfully, or |false| + // if no trace data is available. + bool GetUserStream(MINIDUMP_USER_STREAM* user_stream); + + private: + // Reads the exception code from the client process's address space. + // This routine assumes that the client process's pointer width matches ours. + static bool ReadExceptionCode(HANDLE process_handle, + EXCEPTION_POINTERS* exception_pointers, + DWORD* exception_code); + + // Stores handle operations retrieved by VerifierEnumerateResource(). + static ULONG CALLBACK RecordHandleOperations(void* resource_description, + void* enumeration_context, + ULONG* enumeration_level); + + // Function pointer type for VerifierEnumerateResource, which is looked up + // dynamically. + typedef BOOL (WINAPI* VerifierEnumerateResourceType)( + HANDLE Process, + ULONG Flags, + ULONG ResourceType, + AVRF_RESOURCE_ENUMERATE_CALLBACK ResourceCallback, + PVOID EnumerationContext); + + // Handle to dynamically loaded verifier.dll. + HMODULE verifier_module_; + + // Pointer to the VerifierEnumerateResource function. + VerifierEnumerateResourceType enumerate_resource_; + + // Handle value to look for. + ULONG64 handle_; + + // List of handle operations for |handle_|. + std::list<AVRF_HANDLE_OPERATION> operations_; + + // Minidump stream data. + std::vector<char> stream_; +}; + +HandleTraceData::HandleTraceData() + : verifier_module_(NULL), + enumerate_resource_(NULL), + handle_(NULL) { +} + +HandleTraceData::~HandleTraceData() { + if (verifier_module_) { + FreeLibrary(verifier_module_); + } +} + +bool HandleTraceData::CollectHandleData( + HANDLE process_handle, + EXCEPTION_POINTERS* exception_pointers) { + DWORD exception_code; + if (!ReadExceptionCode(process_handle, exception_pointers, &exception_code)) { + return false; + } + + // Verify whether the execption is STATUS_INVALID_HANDLE. Do not record any + // handle information if it is a different exception to keep the minidump + // small. + if (exception_code != STATUS_INVALID_HANDLE) { + return true; + } + + // Load verifier!VerifierEnumerateResource() dynamically. + verifier_module_ = LoadLibrary(TEXT("verifier.dll")); + if (!verifier_module_) { + return false; + } + + enumerate_resource_ = reinterpret_cast<VerifierEnumerateResourceType>( + GetProcAddress(verifier_module_, "VerifierEnumerateResource")); + if (!enumerate_resource_) { + return false; + } + + // STATUS_INVALID_HANDLE does not provide the offending handle value in + // the exception parameters so we have to guess. At the moment we scan + // the handle operations trace looking for the last invalid handle operation + // and record only the operations for that handle value. + if (enumerate_resource_(process_handle, + 0, + AvrfResourceHandleTrace, + &RecordHandleOperations, + this) != ERROR_SUCCESS) { + // The handle tracing must have not been enabled. + return true; + } + + // Now that |handle_| is initialized, purge all irrelevant operations. + std::list<AVRF_HANDLE_OPERATION>::iterator i = operations_.begin(); + std::list<AVRF_HANDLE_OPERATION>::iterator i_end = operations_.end(); + while (i != i_end) { + if (i->Handle == handle_) { + ++i; + } else { + i = operations_.erase(i); + } + } + + // Convert the list of recorded operations to a minidump stream. + stream_.resize(sizeof(MINIDUMP_HANDLE_OPERATION_LIST) + + sizeof(AVRF_HANDLE_OPERATION) * operations_.size()); + + MINIDUMP_HANDLE_OPERATION_LIST* stream_data = + reinterpret_cast<MINIDUMP_HANDLE_OPERATION_LIST*>( + &stream_.front()); + stream_data->SizeOfHeader = sizeof(MINIDUMP_HANDLE_OPERATION_LIST); + stream_data->SizeOfEntry = sizeof(AVRF_HANDLE_OPERATION); + stream_data->NumberOfEntries = static_cast<ULONG32>(operations_.size()); + stream_data->Reserved = 0; + std::copy(operations_.begin(), + operations_.end(), +#ifdef _MSC_VER + stdext::checked_array_iterator<AVRF_HANDLE_OPERATION*>( + reinterpret_cast<AVRF_HANDLE_OPERATION*>(stream_data + 1), + operations_.size()) +#else + reinterpret_cast<AVRF_HANDLE_OPERATION*>(stream_data + 1) +#endif + ); + + return true; +} + +bool HandleTraceData::GetUserStream(MINIDUMP_USER_STREAM* user_stream) { + if (stream_.empty()) { + return false; + } else { + user_stream->Type = HandleOperationListStream; + user_stream->BufferSize = static_cast<ULONG>(stream_.size()); + user_stream->Buffer = &stream_.front(); + return true; + } +} + +bool HandleTraceData::ReadExceptionCode( + HANDLE process_handle, + EXCEPTION_POINTERS* exception_pointers, + DWORD* exception_code) { + EXCEPTION_POINTERS pointers; + if (!ReadProcessMemory(process_handle, + exception_pointers, + &pointers, + sizeof(pointers), + NULL)) { + return false; + } + + if (!ReadProcessMemory(process_handle, + pointers.ExceptionRecord, + exception_code, + sizeof(*exception_code), + NULL)) { + return false; + } + + return true; +} + +ULONG CALLBACK HandleTraceData::RecordHandleOperations( + void* resource_description, + void* enumeration_context, + ULONG* enumeration_level) { + AVRF_HANDLE_OPERATION* description = + reinterpret_cast<AVRF_HANDLE_OPERATION*>(resource_description); + HandleTraceData* self = + reinterpret_cast<HandleTraceData*>(enumeration_context); + + // Remember the last invalid handle operation. + if (description->OperationType == OperationDbBADREF) { + self->handle_ = description->Handle; + } + + // Record all handle operations. + self->operations_.push_back(*description); + + *enumeration_level = HeapEnumerationEverything; + return ERROR_SUCCESS; +} + +} // namespace + +namespace google_breakpad { + +MinidumpGenerator::MinidumpGenerator( + const std::wstring& dump_path, + const HANDLE process_handle, + const DWORD process_id, + const DWORD thread_id, + const DWORD requesting_thread_id, + EXCEPTION_POINTERS* exception_pointers, + MDRawAssertionInfo* assert_info, + const MINIDUMP_TYPE dump_type, + const bool is_client_pointers) + : dbghelp_module_(NULL), + write_dump_(NULL), + rpcrt4_module_(NULL), + create_uuid_(NULL), + process_handle_(process_handle), + process_id_(process_id), + thread_id_(thread_id), + requesting_thread_id_(requesting_thread_id), + exception_pointers_(exception_pointers), + assert_info_(assert_info), + dump_type_(dump_type), + is_client_pointers_(is_client_pointers), + dump_path_(dump_path), + dump_file_(INVALID_HANDLE_VALUE), + full_dump_file_(INVALID_HANDLE_VALUE), + dump_file_is_internal_(false), + full_dump_file_is_internal_(false), + additional_streams_(NULL), + callback_info_(NULL) { + InitializeCriticalSection(&module_load_sync_); + InitializeCriticalSection(&get_proc_address_sync_); +} + +MinidumpGenerator::~MinidumpGenerator() { + if (dump_file_is_internal_ && dump_file_ != INVALID_HANDLE_VALUE) { + CloseHandle(dump_file_); + } + + if (full_dump_file_is_internal_ && full_dump_file_ != INVALID_HANDLE_VALUE) { + CloseHandle(full_dump_file_); + } + + if (dbghelp_module_) { + FreeLibrary(dbghelp_module_); + } + + if (rpcrt4_module_) { + FreeLibrary(rpcrt4_module_); + } + + DeleteCriticalSection(&get_proc_address_sync_); + DeleteCriticalSection(&module_load_sync_); +} + +bool MinidumpGenerator::WriteMinidump() { + bool full_memory_dump = (dump_type_ & MiniDumpWithFullMemory) != 0; + if (dump_file_ == INVALID_HANDLE_VALUE || + (full_memory_dump && full_dump_file_ == INVALID_HANDLE_VALUE)) { + return false; + } + + MiniDumpWriteDumpType write_dump = GetWriteDump(); + if (!write_dump) { + return false; + } + + MINIDUMP_EXCEPTION_INFORMATION* dump_exception_pointers = NULL; + MINIDUMP_EXCEPTION_INFORMATION dump_exception_info; + + // Setup the exception information object only if it's a dump + // due to an exception. + if (exception_pointers_) { + dump_exception_pointers = &dump_exception_info; + dump_exception_info.ThreadId = thread_id_; + dump_exception_info.ExceptionPointers = exception_pointers_; + dump_exception_info.ClientPointers = is_client_pointers_; + } + + // Add an MDRawBreakpadInfo stream to the minidump, to provide additional + // information about the exception handler to the Breakpad processor. + // The information will help the processor determine which threads are + // relevant. The Breakpad processor does not require this information but + // can function better with Breakpad-generated dumps when it is present. + // The native debugger is not harmed by the presence of this information. + MDRawBreakpadInfo breakpad_info = {0}; + if (!is_client_pointers_) { + // Set the dump thread id and requesting thread id only in case of + // in-process dump generation. + breakpad_info.validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID | + MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID; + breakpad_info.dump_thread_id = thread_id_; + breakpad_info.requesting_thread_id = requesting_thread_id_; + } + + int additional_streams_count = additional_streams_ ? + additional_streams_->UserStreamCount : 0; + scoped_array<MINIDUMP_USER_STREAM> user_stream_array( + new MINIDUMP_USER_STREAM[3 + additional_streams_count]); + user_stream_array[0].Type = MD_BREAKPAD_INFO_STREAM; + user_stream_array[0].BufferSize = sizeof(breakpad_info); + user_stream_array[0].Buffer = &breakpad_info; + + MINIDUMP_USER_STREAM_INFORMATION user_streams; + user_streams.UserStreamCount = 1; + user_streams.UserStreamArray = user_stream_array.get(); + + MDRawAssertionInfo* actual_assert_info = assert_info_; + MDRawAssertionInfo client_assert_info = {{0}}; + + if (assert_info_) { + // If the assertion info object lives in the client process, + // read the memory of the client process. + if (is_client_pointers_) { + SIZE_T bytes_read = 0; + if (!ReadProcessMemory(process_handle_, + assert_info_, + &client_assert_info, + sizeof(client_assert_info), + &bytes_read)) { + if (dump_file_is_internal_) + CloseHandle(dump_file_); + if (full_dump_file_is_internal_ && + full_dump_file_ != INVALID_HANDLE_VALUE) + CloseHandle(full_dump_file_); + return false; + } + + if (bytes_read != sizeof(client_assert_info)) { + if (dump_file_is_internal_) + CloseHandle(dump_file_); + if (full_dump_file_is_internal_ && + full_dump_file_ != INVALID_HANDLE_VALUE) + CloseHandle(full_dump_file_); + return false; + } + + actual_assert_info = &client_assert_info; + } + + user_stream_array[1].Type = MD_ASSERTION_INFO_STREAM; + user_stream_array[1].BufferSize = sizeof(MDRawAssertionInfo); + user_stream_array[1].Buffer = actual_assert_info; + ++user_streams.UserStreamCount; + } + + if (additional_streams_) { + for (size_t i = 0; + i < additional_streams_->UserStreamCount; + i++, user_streams.UserStreamCount++) { + user_stream_array[user_streams.UserStreamCount].Type = + additional_streams_->UserStreamArray[i].Type; + user_stream_array[user_streams.UserStreamCount].BufferSize = + additional_streams_->UserStreamArray[i].BufferSize; + user_stream_array[user_streams.UserStreamCount].Buffer = + additional_streams_->UserStreamArray[i].Buffer; + } + } + + // If the process is terminated by STATUS_INVALID_HANDLE exception store + // the trace of operations for the offending handle value. Do nothing special + // if the client already requested the handle trace to be stored in the dump. + HandleTraceData handle_trace_data; + if (exception_pointers_ && (dump_type_ & MiniDumpWithHandleData) == 0) { + if (!handle_trace_data.CollectHandleData(process_handle_, + exception_pointers_)) { + if (dump_file_is_internal_) + CloseHandle(dump_file_); + if (full_dump_file_is_internal_ && + full_dump_file_ != INVALID_HANDLE_VALUE) + CloseHandle(full_dump_file_); + return false; + } + } + + bool result_full_memory = true; + if (full_memory_dump) { + result_full_memory = write_dump( + process_handle_, + process_id_, + full_dump_file_, + static_cast<MINIDUMP_TYPE>((dump_type_ & (~MiniDumpNormal)) + | MiniDumpWithHandleData), + exception_pointers_ ? &dump_exception_info : NULL, + &user_streams, + NULL) != FALSE; + } + + // Add handle operations trace stream to the minidump if it was collected. + if (handle_trace_data.GetUserStream( + &user_stream_array[user_streams.UserStreamCount])) { + ++user_streams.UserStreamCount; + } + + bool result_minidump = write_dump( + process_handle_, + process_id_, + dump_file_, + static_cast<MINIDUMP_TYPE>((dump_type_ & (~MiniDumpWithFullMemory)) + | MiniDumpNormal), + exception_pointers_ ? &dump_exception_info : NULL, + &user_streams, + callback_info_) != FALSE; + + return result_minidump && result_full_memory; +} + +bool MinidumpGenerator::GenerateDumpFile(wstring* dump_path) { + // The dump file was already set by handle or this function was previously + // called. + if (dump_file_ != INVALID_HANDLE_VALUE) { + return false; + } + + wstring dump_file_path; + if (!GenerateDumpFilePath(&dump_file_path)) { + return false; + } + + dump_file_ = CreateFile(dump_file_path.c_str(), + GENERIC_WRITE, + 0, + NULL, + CREATE_NEW, + FILE_ATTRIBUTE_NORMAL, + NULL); + if (dump_file_ == INVALID_HANDLE_VALUE) { + return false; + } + + dump_file_is_internal_ = true; + *dump_path = dump_file_path; + return true; +} + +bool MinidumpGenerator::GenerateFullDumpFile(wstring* full_dump_path) { + // A full minidump was not requested. + if ((dump_type_ & MiniDumpWithFullMemory) == 0) { + return false; + } + + // The dump file was already set by handle or this function was previously + // called. + if (full_dump_file_ != INVALID_HANDLE_VALUE) { + return false; + } + + wstring full_dump_file_path; + if (!GenerateDumpFilePath(&full_dump_file_path)) { + return false; + } + full_dump_file_path.resize(full_dump_file_path.size() - 4); // strip .dmp + full_dump_file_path.append(TEXT("-full.dmp")); + + full_dump_file_ = CreateFile(full_dump_file_path.c_str(), + GENERIC_WRITE, + 0, + NULL, + CREATE_NEW, + FILE_ATTRIBUTE_NORMAL, + NULL); + if (full_dump_file_ == INVALID_HANDLE_VALUE) { + return false; + } + + full_dump_file_is_internal_ = true; + *full_dump_path = full_dump_file_path; + return true; +} + +HMODULE MinidumpGenerator::GetDbghelpModule() { + AutoCriticalSection lock(&module_load_sync_); + if (!dbghelp_module_) { + dbghelp_module_ = LoadLibrary(TEXT("dbghelp.dll")); + } + + return dbghelp_module_; +} + +MinidumpGenerator::MiniDumpWriteDumpType MinidumpGenerator::GetWriteDump() { + AutoCriticalSection lock(&get_proc_address_sync_); + if (!write_dump_) { + HMODULE module = GetDbghelpModule(); + if (module) { + FARPROC proc = GetProcAddress(module, "MiniDumpWriteDump"); + write_dump_ = reinterpret_cast<MiniDumpWriteDumpType>(proc); + } + } + + return write_dump_; +} + +HMODULE MinidumpGenerator::GetRpcrt4Module() { + AutoCriticalSection lock(&module_load_sync_); + if (!rpcrt4_module_) { + rpcrt4_module_ = LoadLibrary(TEXT("rpcrt4.dll")); + } + + return rpcrt4_module_; +} + +MinidumpGenerator::UuidCreateType MinidumpGenerator::GetCreateUuid() { + AutoCriticalSection lock(&module_load_sync_); + if (!create_uuid_) { + HMODULE module = GetRpcrt4Module(); + if (module) { + FARPROC proc = GetProcAddress(module, "UuidCreate"); + create_uuid_ = reinterpret_cast<UuidCreateType>(proc); + } + } + + return create_uuid_; +} + +bool MinidumpGenerator::GenerateDumpFilePath(wstring* file_path) { + UUID id = {0}; + + UuidCreateType create_uuid = GetCreateUuid(); + if (!create_uuid) { + return false; + } + + create_uuid(&id); + wstring id_str = GUIDString::GUIDToWString(&id); + + *file_path = dump_path_ + TEXT("\\") + id_str + TEXT(".dmp"); + return true; +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/minidump_generator.h b/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/minidump_generator.h new file mode 100644 index 000000000..a3c123056 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/minidump_generator.h @@ -0,0 +1,199 @@ +// Copyright (c) 2008, 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_WINDOWS_CRASH_GENERATION_MINIDUMP_GENERATOR_H_ +#define CLIENT_WINDOWS_CRASH_GENERATION_MINIDUMP_GENERATOR_H_ + +#include <windows.h> +#include <dbghelp.h> +#include <rpc.h> +#include <list> +#include <string> +#include "google_breakpad/common/minidump_format.h" + +namespace google_breakpad { + +// Abstraction for various objects and operations needed to generate +// minidump on Windows. This abstraction is useful to hide all the gory +// details for minidump generation and provide a clean interface to +// the clients to generate minidumps. +class MinidumpGenerator { + public: + // Creates an instance with the given parameters. + // is_client_pointers specifies whether the exception_pointers and + // assert_info point into the process that is being dumped. + // Before calling WriteMinidump on the returned instance a dump file muct be + // specified by a call to either SetDumpFile() or GenerateDumpFile(). + // If a full dump file will be requested via a subsequent call to either + // SetFullDumpFile or GenerateFullDumpFile() dump_type must include + // MiniDumpWithFullMemory. + MinidumpGenerator(const std::wstring& dump_path, + const HANDLE process_handle, + const DWORD process_id, + const DWORD thread_id, + const DWORD requesting_thread_id, + EXCEPTION_POINTERS* exception_pointers, + MDRawAssertionInfo* assert_info, + const MINIDUMP_TYPE dump_type, + const bool is_client_pointers); + + ~MinidumpGenerator(); + + void SetDumpFile(const HANDLE dump_file) { dump_file_ = dump_file; } + void SetFullDumpFile(const HANDLE full_dump_file) { + full_dump_file_ = full_dump_file; + } + + // Generate the name for the dump file that will be written to once + // WriteMinidump() is called. Can only be called once and cannot be called + // if the dump file is set via SetDumpFile(). + bool GenerateDumpFile(std::wstring* dump_path); + + // Generate the name for the full dump file that will be written to once + // WriteMinidump() is called. Cannot be called unless the minidump type + // includes MiniDumpWithFullMemory. Can only be called once and cannot be + // called if the dump file is set via SetFullDumpFile(). + bool GenerateFullDumpFile(std::wstring* full_dump_path); + + void SetAdditionalStreams( + MINIDUMP_USER_STREAM_INFORMATION* additional_streams) { + additional_streams_ = additional_streams; + } + + void SetCallback(MINIDUMP_CALLBACK_INFORMATION* callback_info) { + callback_info_ = callback_info; + } + + // Writes the minidump with the given parameters. Stores the + // dump file path in the dump_path parameter if dump generation + // succeeds. + bool WriteMinidump(); + + private: + // Function pointer type for MiniDumpWriteDump, which is looked up + // dynamically. + typedef BOOL (WINAPI* MiniDumpWriteDumpType)( + HANDLE hProcess, + DWORD ProcessId, + HANDLE hFile, + MINIDUMP_TYPE DumpType, + CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam); + + // Function pointer type for UuidCreate, which is looked up dynamically. + typedef RPC_STATUS (RPC_ENTRY* UuidCreateType)(UUID* Uuid); + + // Loads the appropriate DLL lazily in a thread safe way. + HMODULE GetDbghelpModule(); + + // Loads the appropriate DLL and gets a pointer to the MiniDumpWriteDump + // function lazily and in a thread-safe manner. + MiniDumpWriteDumpType GetWriteDump(); + + // Loads the appropriate DLL lazily in a thread safe way. + HMODULE GetRpcrt4Module(); + + // Loads the appropriate DLL and gets a pointer to the UuidCreate + // function lazily and in a thread-safe manner. + UuidCreateType GetCreateUuid(); + + // Returns the path for the file to write dump to. + bool GenerateDumpFilePath(std::wstring* file_path); + + // Handle to dynamically loaded DbgHelp.dll. + HMODULE dbghelp_module_; + + // Pointer to the MiniDumpWriteDump function. + MiniDumpWriteDumpType write_dump_; + + // Handle to dynamically loaded rpcrt4.dll. + HMODULE rpcrt4_module_; + + // Pointer to the UuidCreate function. + UuidCreateType create_uuid_; + + // Handle for the process to dump. + HANDLE process_handle_; + + // Process ID for the process to dump. + DWORD process_id_; + + // The crashing thread ID. + DWORD thread_id_; + + // The thread ID which is requesting the dump. + DWORD requesting_thread_id_; + + // Pointer to the exception information for the crash. This may point to an + // address in the crashing process so it should not be dereferenced. + EXCEPTION_POINTERS* exception_pointers_; + + // Assertion info for the report. + MDRawAssertionInfo* assert_info_; + + // Type of minidump to generate. + MINIDUMP_TYPE dump_type_; + + // Specifies whether the exception_pointers_ reference memory in the crashing + // process. + bool is_client_pointers_; + + // Folder path to store dump files. + std::wstring dump_path_; + + // The file where the dump will be written. + HANDLE dump_file_; + + // The file where the full dump will be written. + HANDLE full_dump_file_; + + // Tracks whether the dump file handle is managed externally. + bool dump_file_is_internal_; + + // Tracks whether the full dump file handle is managed externally. + bool full_dump_file_is_internal_; + + // Additional streams to be written to the dump. + MINIDUMP_USER_STREAM_INFORMATION* additional_streams_; + + // The user defined callback for the various stages of the dump process. + MINIDUMP_CALLBACK_INFORMATION* callback_info_; + + // Critical section to sychronize action of loading modules dynamically. + CRITICAL_SECTION module_load_sync_; + + // Critical section to synchronize action of dynamically getting function + // addresses from modules. + CRITICAL_SECTION get_proc_address_sync_; +}; + +} // namespace google_breakpad + +#endif // CLIENT_WINDOWS_CRASH_GENERATION_MINIDUMP_GENERATOR_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/objs.mozbuild b/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/objs.mozbuild new file mode 100644 index 000000000..549c14e10 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation/objs.mozbuild @@ -0,0 +1,17 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +lobjs_crash_generation = [ + 'client_info.cc', + 'crash_generation_client.cc', + 'crash_generation_server.cc', + 'minidump_generator.cc', +] + +subdir = 'toolkit/crashreporter/google-breakpad/src/client/windows/crash_generation' +objs_crash_generation = [ + '/%s/%s' % (subdir, s) for s in lobjs_crash_generation +] diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/handler/exception_handler.cc b/toolkit/crashreporter/google-breakpad/src/client/windows/handler/exception_handler.cc new file mode 100644 index 000000000..1f7b19f9a --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/handler/exception_handler.cc @@ -0,0 +1,1073 @@ +// Copyright (c) 2006, 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 <objbase.h> + +#include <algorithm> +#include <cassert> +#include <cstdio> + +#include "common/windows/string_utils-inl.h" + +#include "client/windows/common/ipc_protocol.h" +#include "client/windows/handler/exception_handler.h" +#include "common/windows/guid_string.h" + +namespace google_breakpad { + +// This is passed as the context to the MinidumpWriteDump callback. +typedef struct { + AppMemoryList::const_iterator iter; + AppMemoryList::const_iterator end; +} MinidumpCallbackContext; + +vector<ExceptionHandler*>* ExceptionHandler::handler_stack_ = NULL; +LONG ExceptionHandler::handler_stack_index_ = 0; +CRITICAL_SECTION ExceptionHandler::handler_stack_critical_section_; +volatile LONG ExceptionHandler::instance_count_ = 0; + +ExceptionHandler::ExceptionHandler(const wstring& dump_path, + FilterCallback filter, + MinidumpCallback callback, + void* callback_context, + int handler_types, + MINIDUMP_TYPE dump_type, + const wchar_t* pipe_name, + const CustomClientInfo* custom_info) { + Initialize(dump_path, + filter, + callback, + callback_context, + handler_types, + dump_type, + pipe_name, + NULL, // pipe_handle + NULL, // crash_generation_client + custom_info); +} + +ExceptionHandler::ExceptionHandler(const wstring& dump_path, + FilterCallback filter, + MinidumpCallback callback, + void* callback_context, + int handler_types, + MINIDUMP_TYPE dump_type, + HANDLE pipe_handle, + const CustomClientInfo* custom_info) { + Initialize(dump_path, + filter, + callback, + callback_context, + handler_types, + dump_type, + NULL, // pipe_name + pipe_handle, + NULL, // crash_generation_client + custom_info); +} + +ExceptionHandler::ExceptionHandler( + const wstring& dump_path, + FilterCallback filter, + MinidumpCallback callback, + void* callback_context, + int handler_types, + CrashGenerationClient* crash_generation_client) { + // The dump_type, pipe_name and custom_info that are passed in to Initialize() + // are not used. The ones set in crash_generation_client are used instead. + Initialize(dump_path, + filter, + callback, + callback_context, + handler_types, + MiniDumpNormal, // dump_type - not used + NULL, // pipe_name - not used + NULL, // pipe_handle + crash_generation_client, + NULL); // custom_info - not used +} + +ExceptionHandler::ExceptionHandler(const wstring &dump_path, + FilterCallback filter, + MinidumpCallback callback, + void* callback_context, + int handler_types) { + Initialize(dump_path, + filter, + callback, + callback_context, + handler_types, + MiniDumpNormal, + NULL, // pipe_name + NULL, // pipe_handle + NULL, // crash_generation_client + NULL); // custom_info +} + +void ExceptionHandler::Initialize( + const wstring& dump_path, + FilterCallback filter, + MinidumpCallback callback, + void* callback_context, + int handler_types, + MINIDUMP_TYPE dump_type, + const wchar_t* pipe_name, + HANDLE pipe_handle, + CrashGenerationClient* crash_generation_client, + const CustomClientInfo* custom_info) { + LONG instance_count = InterlockedIncrement(&instance_count_); + filter_ = filter; + callback_ = callback; + callback_context_ = callback_context; + dump_path_c_ = NULL; + next_minidump_id_c_ = NULL; + next_minidump_path_c_ = NULL; + dbghelp_module_ = NULL; + minidump_write_dump_ = NULL; + dump_type_ = dump_type; + rpcrt4_module_ = NULL; + uuid_create_ = NULL; + handler_types_ = handler_types; + previous_filter_ = NULL; +#if _MSC_VER >= 1400 // MSVC 2005/8 + previous_iph_ = NULL; +#endif // _MSC_VER >= 1400 + previous_pch_ = NULL; + handler_thread_ = NULL; + is_shutdown_ = false; + handler_start_semaphore_ = NULL; + handler_finish_semaphore_ = NULL; + requesting_thread_id_ = 0; + exception_info_ = NULL; + assertion_ = NULL; + handler_return_value_ = false; + handle_debug_exceptions_ = false; + consume_invalid_handle_exceptions_ = false; + + // Attempt to use out-of-process if user has specified a pipe or a + // crash generation client. + scoped_ptr<CrashGenerationClient> client; + if (crash_generation_client) { + client.reset(crash_generation_client); + } else if (pipe_name) { + client.reset( + new CrashGenerationClient(pipe_name, dump_type_, custom_info)); + } else if (pipe_handle) { + client.reset( + new CrashGenerationClient(pipe_handle, dump_type_, custom_info)); + } + + if (client.get() != NULL) { + // If successful in registering with the monitoring process, + // there is no need to setup in-process crash generation. + if (client->Register()) { + crash_generation_client_.reset(client.release()); + } + } + + if (!IsOutOfProcess()) { + // Either client did not ask for out-of-process crash generation + // or registration with the server process failed. In either case, + // setup to do in-process crash generation. + + // Set synchronization primitives and the handler thread. Each + // ExceptionHandler object gets its own handler thread because that's the + // only way to reliably guarantee sufficient stack space in an exception, + // and it allows an easy way to get a snapshot of the requesting thread's + // context outside of an exception. + InitializeCriticalSection(&handler_critical_section_); + handler_start_semaphore_ = CreateSemaphore(NULL, 0, 1, NULL); + assert(handler_start_semaphore_ != NULL); + + handler_finish_semaphore_ = CreateSemaphore(NULL, 0, 1, NULL); + assert(handler_finish_semaphore_ != NULL); + + // Don't attempt to create the thread if we could not create the semaphores. + if (handler_finish_semaphore_ != NULL && handler_start_semaphore_ != NULL) { + DWORD thread_id; + const int kExceptionHandlerThreadInitialStackSize = 64 * 1024; + handler_thread_ = CreateThread(NULL, // lpThreadAttributes + kExceptionHandlerThreadInitialStackSize, + ExceptionHandlerThreadMain, + this, // lpParameter + 0, // dwCreationFlags + &thread_id); + assert(handler_thread_ != NULL); + } + + dbghelp_module_ = LoadLibrary(L"dbghelp.dll"); + if (dbghelp_module_) { + minidump_write_dump_ = reinterpret_cast<MiniDumpWriteDump_type>( + GetProcAddress(dbghelp_module_, "MiniDumpWriteDump")); + } + + // Load this library dynamically to not affect existing projects. Most + // projects don't link against this directly, it's usually dynamically + // loaded by dependent code. + rpcrt4_module_ = LoadLibrary(L"rpcrt4.dll"); + if (rpcrt4_module_) { + uuid_create_ = reinterpret_cast<UuidCreate_type>( + GetProcAddress(rpcrt4_module_, "UuidCreate")); + } + + // set_dump_path calls UpdateNextID. This sets up all of the path and id + // strings, and their equivalent c_str pointers. + set_dump_path(dump_path); + } + + // Reserve one element for the instruction memory + AppMemory instruction_memory; + instruction_memory.ptr = NULL; + instruction_memory.length = 0; + app_memory_info_.push_back(instruction_memory); + + // There is a race condition here. If the first instance has not yet + // initialized the critical section, the second (and later) instances may + // try to use uninitialized critical section object. The feature of multiple + // instances in one module is not used much, so leave it as is for now. + // One way to solve this in the current design (that is, keeping the static + // handler stack) is to use spin locks with volatile bools to synchronize + // the handler stack. This works only if the compiler guarantees to generate + // cache coherent code for volatile. + // TODO(munjal): Fix this in a better way by changing the design if possible. + + // Lazy initialization of the handler_stack_critical_section_ + if (instance_count == 1) { + InitializeCriticalSection(&handler_stack_critical_section_); + } + + if (handler_types != HANDLER_NONE) { + EnterCriticalSection(&handler_stack_critical_section_); + + // The first time an ExceptionHandler that installs a handler is + // created, set up the handler stack. + if (!handler_stack_) { + handler_stack_ = new vector<ExceptionHandler*>(); + } + handler_stack_->push_back(this); + + if (handler_types & HANDLER_EXCEPTION) + previous_filter_ = SetUnhandledExceptionFilter(HandleException); + +#if _MSC_VER >= 1400 // MSVC 2005/8 + if (handler_types & HANDLER_INVALID_PARAMETER) + previous_iph_ = _set_invalid_parameter_handler(HandleInvalidParameter); +#endif // _MSC_VER >= 1400 + + if (handler_types & HANDLER_PURECALL) + previous_pch_ = _set_purecall_handler(HandlePureVirtualCall); + + LeaveCriticalSection(&handler_stack_critical_section_); + } +} + +ExceptionHandler::~ExceptionHandler() { + if (dbghelp_module_) { + FreeLibrary(dbghelp_module_); + } + + if (rpcrt4_module_) { + FreeLibrary(rpcrt4_module_); + } + + if (handler_types_ != HANDLER_NONE) { + EnterCriticalSection(&handler_stack_critical_section_); + + if (handler_types_ & HANDLER_EXCEPTION) + SetUnhandledExceptionFilter(previous_filter_); + +#if _MSC_VER >= 1400 // MSVC 2005/8 + if (handler_types_ & HANDLER_INVALID_PARAMETER) + _set_invalid_parameter_handler(previous_iph_); +#endif // _MSC_VER >= 1400 + + if (handler_types_ & HANDLER_PURECALL) + _set_purecall_handler(previous_pch_); + + if (handler_stack_->back() == this) { + handler_stack_->pop_back(); + } else { + // TODO(mmentovai): use advapi32!ReportEvent to log the warning to the + // system's application event log. + fprintf(stderr, "warning: removing Breakpad handler out of order\n"); + vector<ExceptionHandler*>::iterator iterator = handler_stack_->begin(); + while (iterator != handler_stack_->end()) { + if (*iterator == this) { + iterator = handler_stack_->erase(iterator); + } else { + ++iterator; + } + } + } + + if (handler_stack_->empty()) { + // When destroying the last ExceptionHandler that installed a handler, + // clean up the handler stack. + delete handler_stack_; + handler_stack_ = NULL; + } + + LeaveCriticalSection(&handler_stack_critical_section_); + } + + // Some of the objects were only initialized if out of process + // registration was not done. + if (!IsOutOfProcess()) { +#ifdef BREAKPAD_NO_TERMINATE_THREAD + // Clean up the handler thread and synchronization primitives. The handler + // thread is either waiting on the semaphore to handle a crash or it is + // handling a crash. Coming out of the wait is fast but wait more in the + // eventuality a crash is handled. This compilation option results in a + // deadlock if the exception handler is destroyed while executing code + // inside DllMain. + is_shutdown_ = true; + ReleaseSemaphore(handler_start_semaphore_, 1, NULL); + const int kWaitForHandlerThreadMs = 60000; + WaitForSingleObject(handler_thread_, kWaitForHandlerThreadMs); +#else + TerminateThread(handler_thread_, 1); +#endif // BREAKPAD_NO_TERMINATE_THREAD + + CloseHandle(handler_thread_); + handler_thread_ = NULL; + DeleteCriticalSection(&handler_critical_section_); + CloseHandle(handler_start_semaphore_); + CloseHandle(handler_finish_semaphore_); + } + + // There is a race condition in the code below: if this instance is + // deleting the static critical section and a new instance of the class + // is created, then there is a possibility that the critical section be + // initialized while the same critical section is being deleted. Given the + // usage pattern for the code, this race condition is unlikely to hit, but it + // is a race condition nonetheless. + if (InterlockedDecrement(&instance_count_) == 0) { + DeleteCriticalSection(&handler_stack_critical_section_); + } +} + +bool ExceptionHandler::RequestUpload(DWORD crash_id) { + return crash_generation_client_->RequestUpload(crash_id); +} + +// static +DWORD ExceptionHandler::ExceptionHandlerThreadMain(void* lpParameter) { + ExceptionHandler* self = reinterpret_cast<ExceptionHandler *>(lpParameter); + assert(self); + assert(self->handler_start_semaphore_ != NULL); + assert(self->handler_finish_semaphore_ != NULL); + + while (true) { + if (WaitForSingleObject(self->handler_start_semaphore_, INFINITE) == + WAIT_OBJECT_0) { + // Perform the requested action. + if (self->is_shutdown_) { + // The instance of the exception handler is being destroyed. + break; + } else { + self->handler_return_value_ = + self->WriteMinidumpWithException(self->requesting_thread_id_, + self->exception_info_, + self->assertion_); + } + + // Allow the requesting thread to proceed. + ReleaseSemaphore(self->handler_finish_semaphore_, 1, NULL); + } + } + + // This statement is not reached when the thread is unconditionally + // terminated by the ExceptionHandler destructor. + return 0; +} + +// HandleException and HandleInvalidParameter must create an +// AutoExceptionHandler object to maintain static state and to determine which +// ExceptionHandler instance to use. The constructor locates the correct +// instance, and makes it available through get_handler(). The destructor +// restores the state in effect prior to allocating the AutoExceptionHandler. +class AutoExceptionHandler { + public: + AutoExceptionHandler() { + // Increment handler_stack_index_ so that if another Breakpad handler is + // registered using this same HandleException function, and it needs to be + // called while this handler is running (either because this handler + // declines to handle the exception, or an exception occurs during + // handling), HandleException will find the appropriate ExceptionHandler + // object in handler_stack_ to deliver the exception to. + // + // Because handler_stack_ is addressed in reverse (as |size - index|), + // preincrementing handler_stack_index_ avoids needing to subtract 1 from + // the argument to |at|. + // + // The index is maintained instead of popping elements off of the handler + // stack and pushing them at the end of this method. This avoids ruining + // the order of elements in the stack in the event that some other thread + // decides to manipulate the handler stack (such as creating a new + // ExceptionHandler object) while an exception is being handled. + EnterCriticalSection(&ExceptionHandler::handler_stack_critical_section_); + handler_ = ExceptionHandler::handler_stack_->at( + ExceptionHandler::handler_stack_->size() - + ++ExceptionHandler::handler_stack_index_); + + // In case another exception occurs while this handler is doing its thing, + // it should be delivered to the previous filter. + SetUnhandledExceptionFilter(handler_->previous_filter_); +#if _MSC_VER >= 1400 // MSVC 2005/8 + _set_invalid_parameter_handler(handler_->previous_iph_); +#endif // _MSC_VER >= 1400 + _set_purecall_handler(handler_->previous_pch_); + } + + ~AutoExceptionHandler() { + // Put things back the way they were before entering this handler. + SetUnhandledExceptionFilter(ExceptionHandler::HandleException); +#if _MSC_VER >= 1400 // MSVC 2005/8 + _set_invalid_parameter_handler(ExceptionHandler::HandleInvalidParameter); +#endif // _MSC_VER >= 1400 + _set_purecall_handler(ExceptionHandler::HandlePureVirtualCall); + + --ExceptionHandler::handler_stack_index_; + LeaveCriticalSection(&ExceptionHandler::handler_stack_critical_section_); + } + + ExceptionHandler* get_handler() const { return handler_; } + + private: + ExceptionHandler* handler_; +}; + +// static +LONG ExceptionHandler::HandleException(EXCEPTION_POINTERS* exinfo) { + AutoExceptionHandler auto_exception_handler; + ExceptionHandler* current_handler = auto_exception_handler.get_handler(); + + // Ignore EXCEPTION_BREAKPOINT and EXCEPTION_SINGLE_STEP exceptions. This + // logic will short-circuit before calling WriteMinidumpOnHandlerThread, + // allowing something else to handle the breakpoint without incurring the + // overhead transitioning to and from the handler thread. This behavior + // can be overridden by calling ExceptionHandler::set_handle_debug_exceptions. + DWORD code = exinfo->ExceptionRecord->ExceptionCode; + LONG action; + bool is_debug_exception = (code == EXCEPTION_BREAKPOINT) || + (code == EXCEPTION_SINGLE_STEP); + + if (code == EXCEPTION_INVALID_HANDLE && + current_handler->consume_invalid_handle_exceptions_) { + return EXCEPTION_CONTINUE_EXECUTION; + } + + bool success = false; + + if (!is_debug_exception || + current_handler->get_handle_debug_exceptions()) { + // If out-of-proc crash handler client is available, we have to use that + // to generate dump and we cannot fall back on in-proc dump generation + // because we never prepared for an in-proc dump generation + + // In case of out-of-process dump generation, directly call + // WriteMinidumpWithException since there is no separate thread running. + if (current_handler->IsOutOfProcess()) { + success = current_handler->WriteMinidumpWithException( + GetCurrentThreadId(), + exinfo, + NULL); + } else { + success = current_handler->WriteMinidumpOnHandlerThread(exinfo, NULL); + } + } + + // The handler fully handled the exception. Returning + // EXCEPTION_EXECUTE_HANDLER indicates this to the system, and usually + // results in the application being terminated. + // + // Note: If the application was launched from within the Cygwin + // environment, returning EXCEPTION_EXECUTE_HANDLER seems to cause the + // application to be restarted. + if (success) { + action = EXCEPTION_EXECUTE_HANDLER; + } else { + // There was an exception, it was a breakpoint or something else ignored + // above, or it was passed to the handler, which decided not to handle it. + // This could be because the filter callback didn't want it, because + // minidump writing failed for some reason, or because the post-minidump + // callback function indicated failure. Give the previous handler a + // chance to do something with the exception. If there is no previous + // handler, return EXCEPTION_CONTINUE_SEARCH, which will allow a debugger + // or native "crashed" dialog to handle the exception. + if (current_handler->previous_filter_) { + action = current_handler->previous_filter_(exinfo); + } else { + action = EXCEPTION_CONTINUE_SEARCH; + } + } + + return action; +} + +#if _MSC_VER >= 1400 // MSVC 2005/8 +// static +void ExceptionHandler::HandleInvalidParameter(const wchar_t* expression, + const wchar_t* function, + const wchar_t* file, + unsigned int line, + uintptr_t reserved) { + // This is an invalid parameter, not an exception. It's safe to play with + // sprintf here. + AutoExceptionHandler auto_exception_handler; + ExceptionHandler* current_handler = auto_exception_handler.get_handler(); + + MDRawAssertionInfo assertion; + memset(&assertion, 0, sizeof(assertion)); + _snwprintf_s(reinterpret_cast<wchar_t*>(assertion.expression), + sizeof(assertion.expression) / sizeof(assertion.expression[0]), + _TRUNCATE, L"%s", expression); + _snwprintf_s(reinterpret_cast<wchar_t*>(assertion.function), + sizeof(assertion.function) / sizeof(assertion.function[0]), + _TRUNCATE, L"%s", function); + _snwprintf_s(reinterpret_cast<wchar_t*>(assertion.file), + sizeof(assertion.file) / sizeof(assertion.file[0]), + _TRUNCATE, L"%s", file); + assertion.line = line; + assertion.type = MD_ASSERTION_INFO_TYPE_INVALID_PARAMETER; + + // Make up an exception record for the current thread and CPU context + // to make it possible for the crash processor to classify these + // as do regular crashes, and to make it humane for developers to + // analyze them. + EXCEPTION_RECORD exception_record = {}; + CONTEXT exception_context = {}; + EXCEPTION_POINTERS exception_ptrs = { &exception_record, &exception_context }; + + ::RtlCaptureContext(&exception_context); + + exception_record.ExceptionCode = STATUS_INVALID_PARAMETER; + + // We store pointers to the the expression and function strings, + // and the line as exception parameters to make them easy to + // access by the developer on the far side. + exception_record.NumberParameters = 3; + exception_record.ExceptionInformation[0] = + reinterpret_cast<ULONG_PTR>(&assertion.expression); + exception_record.ExceptionInformation[1] = + reinterpret_cast<ULONG_PTR>(&assertion.file); + exception_record.ExceptionInformation[2] = assertion.line; + + bool success = false; + // In case of out-of-process dump generation, directly call + // WriteMinidumpWithException since there is no separate thread running. + if (current_handler->IsOutOfProcess()) { + success = current_handler->WriteMinidumpWithException( + GetCurrentThreadId(), + &exception_ptrs, + &assertion); + } else { + success = current_handler->WriteMinidumpOnHandlerThread(&exception_ptrs, + &assertion); + } + + if (!success) { + if (current_handler->previous_iph_) { + // The handler didn't fully handle the exception. Give it to the + // previous invalid parameter handler. + current_handler->previous_iph_(expression, + function, + file, + line, + reserved); + } else { + // If there's no previous handler, pass the exception back in to the + // invalid parameter handler's core. That's the routine that called this + // function, but now, since this function is no longer registered (and in + // fact, no function at all is registered), this will result in the + // default code path being taken: _CRT_DEBUGGER_HOOK and _invoke_watson. + // Use _invalid_parameter where it exists (in _DEBUG builds) as it passes + // more information through. In non-debug builds, it is not available, + // so fall back to using _invalid_parameter_noinfo. See invarg.c in the + // CRT source. +#ifdef _DEBUG + _invalid_parameter(expression, function, file, line, reserved); +#else // _DEBUG + _invalid_parameter_noinfo(); +#endif // _DEBUG + } + } + + // The handler either took care of the invalid parameter problem itself, + // or passed it on to another handler. "Swallow" it by exiting, paralleling + // the behavior of "swallowing" exceptions. + exit(0); +} +#endif // _MSC_VER >= 1400 + +// static +void ExceptionHandler::HandlePureVirtualCall() { + // This is an pure virtual function call, not an exception. It's safe to + // play with sprintf here. + AutoExceptionHandler auto_exception_handler; + ExceptionHandler* current_handler = auto_exception_handler.get_handler(); + + MDRawAssertionInfo assertion; + memset(&assertion, 0, sizeof(assertion)); + assertion.type = MD_ASSERTION_INFO_TYPE_PURE_VIRTUAL_CALL; + + // Make up an exception record for the current thread and CPU context + // to make it possible for the crash processor to classify these + // as do regular crashes, and to make it humane for developers to + // analyze them. + EXCEPTION_RECORD exception_record = {}; + CONTEXT exception_context = {}; + EXCEPTION_POINTERS exception_ptrs = { &exception_record, &exception_context }; + + ::RtlCaptureContext(&exception_context); + + exception_record.ExceptionCode = STATUS_NONCONTINUABLE_EXCEPTION; + + // We store pointers to the the expression and function strings, + // and the line as exception parameters to make them easy to + // access by the developer on the far side. + exception_record.NumberParameters = 3; + exception_record.ExceptionInformation[0] = + reinterpret_cast<ULONG_PTR>(&assertion.expression); + exception_record.ExceptionInformation[1] = + reinterpret_cast<ULONG_PTR>(&assertion.file); + exception_record.ExceptionInformation[2] = assertion.line; + + bool success = false; + // In case of out-of-process dump generation, directly call + // WriteMinidumpWithException since there is no separate thread running. + + if (current_handler->IsOutOfProcess()) { + success = current_handler->WriteMinidumpWithException( + GetCurrentThreadId(), + &exception_ptrs, + &assertion); + } else { + success = current_handler->WriteMinidumpOnHandlerThread(&exception_ptrs, + &assertion); + } + + if (!success) { + if (current_handler->previous_pch_) { + // The handler didn't fully handle the exception. Give it to the + // previous purecall handler. + current_handler->previous_pch_(); + } else { + // If there's no previous handler, return and let _purecall handle it. + // This will just put up an assertion dialog. + return; + } + } + + // The handler either took care of the invalid parameter problem itself, + // or passed it on to another handler. "Swallow" it by exiting, paralleling + // the behavior of "swallowing" exceptions. + exit(0); +} + +bool ExceptionHandler::WriteMinidumpOnHandlerThread( + EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion) { + EnterCriticalSection(&handler_critical_section_); + + // There isn't much we can do if the handler thread + // was not successfully created. + if (handler_thread_ == NULL) { + LeaveCriticalSection(&handler_critical_section_); + return false; + } + + // The handler thread should only be created when the semaphores are valid. + assert(handler_start_semaphore_ != NULL); + assert(handler_finish_semaphore_ != NULL); + + // Set up data to be passed in to the handler thread. + requesting_thread_id_ = GetCurrentThreadId(); + exception_info_ = exinfo; + assertion_ = assertion; + + // This causes the handler thread to call WriteMinidumpWithException. + ReleaseSemaphore(handler_start_semaphore_, 1, NULL); + + // Wait until WriteMinidumpWithException is done and collect its return value. + WaitForSingleObject(handler_finish_semaphore_, INFINITE); + bool status = handler_return_value_; + + // Clean up. + requesting_thread_id_ = 0; + exception_info_ = NULL; + assertion_ = NULL; + + LeaveCriticalSection(&handler_critical_section_); + + return status; +} + +bool ExceptionHandler::WriteMinidump() { + // Make up an exception record for the current thread and CPU context + // to make it possible for the crash processor to classify these + // as do regular crashes, and to make it humane for developers to + // analyze them. + EXCEPTION_RECORD exception_record = {}; + CONTEXT exception_context = {}; + EXCEPTION_POINTERS exception_ptrs = { &exception_record, &exception_context }; + + ::RtlCaptureContext(&exception_context); + exception_record.ExceptionCode = STATUS_NONCONTINUABLE_EXCEPTION; + + return WriteMinidumpForException(&exception_ptrs); +} + +bool ExceptionHandler::WriteMinidumpForException(EXCEPTION_POINTERS* exinfo) { + // In case of out-of-process dump generation, directly call + // WriteMinidumpWithException since there is no separate thread running. + if (IsOutOfProcess()) { + return WriteMinidumpWithException(GetCurrentThreadId(), + exinfo, + NULL); + } + + bool success = WriteMinidumpOnHandlerThread(exinfo, NULL); + UpdateNextID(); + return success; +} + +// static +bool ExceptionHandler::WriteMinidump(const wstring &dump_path, + MinidumpCallback callback, + void* callback_context, + MINIDUMP_TYPE dump_type) { + ExceptionHandler handler(dump_path, NULL, callback, callback_context, + HANDLER_NONE, dump_type, (HANDLE)NULL, NULL); + return handler.WriteMinidump(); +} + +// static +bool ExceptionHandler::WriteMinidumpForChild(HANDLE child, + DWORD child_blamed_thread, + const wstring& dump_path, + MinidumpCallback callback, + void* callback_context, + MINIDUMP_TYPE dump_type) { + EXCEPTION_RECORD ex; + CONTEXT ctx; + EXCEPTION_POINTERS exinfo = { NULL, NULL }; + // As documented on MSDN, on failure SuspendThread returns (DWORD) -1 + const DWORD kFailedToSuspendThread = static_cast<DWORD>(-1); + DWORD last_suspend_count = kFailedToSuspendThread; + HANDLE child_thread_handle = OpenThread(THREAD_GET_CONTEXT | + THREAD_QUERY_INFORMATION | + THREAD_SUSPEND_RESUME, + FALSE, + child_blamed_thread); + // This thread may have died already, so not opening the handle is a + // non-fatal error. + if (child_thread_handle != NULL) { + last_suspend_count = SuspendThread(child_thread_handle); + if (last_suspend_count != kFailedToSuspendThread) { + ctx.ContextFlags = CONTEXT_ALL; + if (GetThreadContext(child_thread_handle, &ctx)) { + memset(&ex, 0, sizeof(ex)); + ex.ExceptionCode = EXCEPTION_BREAKPOINT; +#if defined(_M_IX86) + ex.ExceptionAddress = reinterpret_cast<PVOID>(ctx.Eip); +#elif defined(_M_X64) + ex.ExceptionAddress = reinterpret_cast<PVOID>(ctx.Rip); +#endif + exinfo.ExceptionRecord = &ex; + exinfo.ContextRecord = &ctx; + } + } + } + + ExceptionHandler handler(dump_path, NULL, callback, callback_context, + HANDLER_NONE, dump_type, (HANDLE)NULL, NULL); + bool success = handler.WriteMinidumpWithExceptionForProcess( + child_blamed_thread, + exinfo.ExceptionRecord ? &exinfo : NULL, + NULL, child, false); + + if (last_suspend_count != kFailedToSuspendThread) { + ResumeThread(child_thread_handle); + } + + CloseHandle(child_thread_handle); + + if (callback) { + success = callback(handler.dump_path_c_, handler.next_minidump_id_c_, + callback_context, NULL, NULL, success); + } + + return success; +} + +bool ExceptionHandler::WriteMinidumpWithException( + DWORD requesting_thread_id, + EXCEPTION_POINTERS* exinfo, + MDRawAssertionInfo* assertion) { + // Give user code a chance to approve or prevent writing a minidump. If the + // filter returns false, don't handle the exception at all. If this method + // was called as a result of an exception, returning false will cause + // HandleException to call any previous handler or return + // EXCEPTION_CONTINUE_SEARCH on the exception thread, allowing it to appear + // as though this handler were not present at all. + if (filter_ && !filter_(callback_context_, exinfo, assertion)) { + return false; + } + + bool success = false; + if (IsOutOfProcess()) { + success = crash_generation_client_->RequestDump(exinfo, assertion); + } else { + success = WriteMinidumpWithExceptionForProcess(requesting_thread_id, + exinfo, + assertion, + GetCurrentProcess(), + true); + } + + if (callback_) { + // TODO(munjal): In case of out-of-process dump generation, both + // dump_path_c_ and next_minidump_id_ will be NULL. For out-of-process + // scenario, the server process ends up creating the dump path and dump + // id so they are not known to the client. + success = callback_(dump_path_c_, next_minidump_id_c_, callback_context_, + exinfo, assertion, success); + } + + return success; +} + +// static +BOOL CALLBACK ExceptionHandler::MinidumpWriteDumpCallback( + PVOID context, + const PMINIDUMP_CALLBACK_INPUT callback_input, + PMINIDUMP_CALLBACK_OUTPUT callback_output) { + switch (callback_input->CallbackType) { + case MemoryCallback: { + MinidumpCallbackContext* callback_context = + reinterpret_cast<MinidumpCallbackContext*>(context); + if (callback_context->iter == callback_context->end) + return FALSE; + + // Include the specified memory region. + callback_output->MemoryBase = callback_context->iter->ptr; + callback_output->MemorySize = callback_context->iter->length; + callback_context->iter++; + return TRUE; + } + + // Include all modules. + case IncludeModuleCallback: + case ModuleCallback: + return TRUE; + + // Include all threads. + case IncludeThreadCallback: + case ThreadCallback: + return TRUE; + + // Stop receiving cancel callbacks. + case CancelCallback: + callback_output->CheckCancel = FALSE; + callback_output->Cancel = FALSE; + return TRUE; + } + // Ignore other callback types. + return FALSE; +} + +bool ExceptionHandler::WriteMinidumpWithExceptionForProcess( + DWORD requesting_thread_id, + EXCEPTION_POINTERS* exinfo, + MDRawAssertionInfo* assertion, + HANDLE process, + bool write_requester_stream) { + bool success = false; + if (minidump_write_dump_) { + HANDLE dump_file = CreateFile(next_minidump_path_c_, + GENERIC_WRITE, + 0, // no sharing + NULL, + CREATE_NEW, // fail if exists + FILE_ATTRIBUTE_NORMAL, + NULL); + if (dump_file != INVALID_HANDLE_VALUE) { + MINIDUMP_EXCEPTION_INFORMATION except_info; + except_info.ThreadId = requesting_thread_id; + except_info.ExceptionPointers = exinfo; + except_info.ClientPointers = FALSE; + + // Leave room in user_stream_array for possible breakpad and + // assertion info streams. + MINIDUMP_USER_STREAM user_stream_array[2]; + MINIDUMP_USER_STREAM_INFORMATION user_streams; + user_streams.UserStreamCount = 0; + user_streams.UserStreamArray = user_stream_array; + + if (write_requester_stream) { + // Add an MDRawBreakpadInfo stream to the minidump, to provide + // additional information about the exception handler to the Breakpad + // processor. The information will help the processor determine which + // threads are relevant. The Breakpad processor does not require this + // information but can function better with Breakpad-generated dumps + // when it is present. The native debugger is not harmed by the + // presence of this information. + MDRawBreakpadInfo breakpad_info; + breakpad_info.validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID | + MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID; + breakpad_info.dump_thread_id = GetCurrentThreadId(); + breakpad_info.requesting_thread_id = requesting_thread_id; + + int index = user_streams.UserStreamCount; + user_stream_array[index].Type = MD_BREAKPAD_INFO_STREAM; + user_stream_array[index].BufferSize = sizeof(breakpad_info); + user_stream_array[index].Buffer = &breakpad_info; + ++user_streams.UserStreamCount; + } + + if (assertion) { + int index = user_streams.UserStreamCount; + user_stream_array[index].Type = MD_ASSERTION_INFO_STREAM; + user_stream_array[index].BufferSize = sizeof(MDRawAssertionInfo); + user_stream_array[index].Buffer = assertion; + ++user_streams.UserStreamCount; + } + + // Older versions of DbgHelp.dll don't correctly put the memory around + // the faulting instruction pointer into the minidump. This + // callback will ensure that it gets included. + if (exinfo) { + // Find a memory region of 256 bytes centered on the + // faulting instruction pointer. + const ULONG64 instruction_pointer = +#if defined(_M_IX86) + exinfo->ContextRecord->Eip; +#elif defined(_M_AMD64) + exinfo->ContextRecord->Rip; +#else +#error Unsupported platform +#endif + + MEMORY_BASIC_INFORMATION info; + if (VirtualQueryEx(process, + reinterpret_cast<LPCVOID>(instruction_pointer), + &info, + sizeof(MEMORY_BASIC_INFORMATION)) != 0 && + info.State == MEM_COMMIT) { + // Attempt to get 128 bytes before and after the instruction + // pointer, but settle for whatever's available up to the + // boundaries of the memory region. + const ULONG64 kIPMemorySize = 256; + ULONG64 base = + (std::max)(reinterpret_cast<ULONG64>(info.BaseAddress), + instruction_pointer - (kIPMemorySize / 2)); + ULONG64 end_of_range = + (std::min)(instruction_pointer + (kIPMemorySize / 2), + reinterpret_cast<ULONG64>(info.BaseAddress) + + info.RegionSize); + ULONG size = static_cast<ULONG>(end_of_range - base); + + AppMemory& elt = app_memory_info_.front(); + elt.ptr = base; + elt.length = size; + } + } + + MinidumpCallbackContext context; + context.iter = app_memory_info_.begin(); + context.end = app_memory_info_.end(); + + // Skip the reserved element if there was no instruction memory + if (context.iter->ptr == 0) { + context.iter++; + } + + MINIDUMP_CALLBACK_INFORMATION callback; + callback.CallbackRoutine = MinidumpWriteDumpCallback; + callback.CallbackParam = reinterpret_cast<void*>(&context); + + // The explicit comparison to TRUE avoids a warning (C4800). + success = (minidump_write_dump_(process, + GetProcessId(process), + dump_file, + dump_type_, + exinfo ? &except_info : NULL, + &user_streams, + &callback) == TRUE); + + CloseHandle(dump_file); + } + } + + return success; +} + +void ExceptionHandler::UpdateNextID() { + assert(uuid_create_); + UUID id = {0}; + if (uuid_create_) { + uuid_create_(&id); + } + next_minidump_id_ = GUIDString::GUIDToWString(&id); + next_minidump_id_c_ = next_minidump_id_.c_str(); + + wchar_t minidump_path[MAX_PATH]; + swprintf(minidump_path, MAX_PATH, L"%s\\%s.dmp", + dump_path_c_, next_minidump_id_c_); + + // remove when VC++7.1 is no longer supported + minidump_path[MAX_PATH - 1] = L'\0'; + + next_minidump_path_ = minidump_path; + next_minidump_path_c_ = next_minidump_path_.c_str(); +} + +void ExceptionHandler::RegisterAppMemory(void* ptr, size_t length) { + AppMemoryList::iterator iter = + std::find(app_memory_info_.begin(), app_memory_info_.end(), ptr); + if (iter != app_memory_info_.end()) { + // Don't allow registering the same pointer twice. + return; + } + + AppMemory app_memory; + app_memory.ptr = reinterpret_cast<ULONG64>(ptr); + app_memory.length = static_cast<ULONG>(length); + app_memory_info_.push_back(app_memory); +} + +void ExceptionHandler::UnregisterAppMemory(void* ptr) { + AppMemoryList::iterator iter = + std::find(app_memory_info_.begin(), app_memory_info_.end(), ptr); + if (iter != app_memory_info_.end()) { + app_memory_info_.erase(iter); + } +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/handler/exception_handler.gyp b/toolkit/crashreporter/google-breakpad/src/client/windows/handler/exception_handler.gyp new file mode 100644 index 000000000..c5733277d --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/handler/exception_handler.gyp @@ -0,0 +1,47 @@ +# Copyright 2010 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. + +{ + 'includes': [ + '../../../build/common.gypi', + ], + 'targets': [ + { + 'target_name': 'exception_handler', + 'type': 'static_library', + 'sources': [ + "exception_handler.cc", + "exception_handler.h", + ], + 'dependencies': [ + '../breakpad_client.gyp:common', + '../crash_generation/crash_generation.gyp:crash_generation_server', + ] + }, + ], +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/handler/exception_handler.h b/toolkit/crashreporter/google-breakpad/src/client/windows/handler/exception_handler.h new file mode 100644 index 000000000..11babe513 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/handler/exception_handler.h @@ -0,0 +1,524 @@ +// Copyright (c) 2006, 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. + +// ExceptionHandler can write a minidump file when an exception occurs, +// or when WriteMinidump() is called explicitly by your program. +// +// To have the exception handler write minidumps when an uncaught exception +// (crash) occurs, you should create an instance early in the execution +// of your program, and keep it around for the entire time you want to +// have crash handling active (typically, until shutdown). +// +// If you want to write minidumps without installing the exception handler, +// you can create an ExceptionHandler with install_handler set to false, +// then call WriteMinidump. You can also use this technique if you want to +// use different minidump callbacks for different call sites. +// +// In either case, a callback function is called when a minidump is written, +// which receives the unqiue id of the minidump. The caller can use this +// id to collect and write additional application state, and to launch an +// external crash-reporting application. +// +// It is important that creation and destruction of ExceptionHandler objects +// be nested cleanly, when using install_handler = true. +// Avoid the following pattern: +// ExceptionHandler *e = new ExceptionHandler(...); +// ExceptionHandler *f = new ExceptionHandler(...); +// delete e; +// This will put the exception filter stack into an inconsistent state. + +#ifndef CLIENT_WINDOWS_HANDLER_EXCEPTION_HANDLER_H__ +#define CLIENT_WINDOWS_HANDLER_EXCEPTION_HANDLER_H__ + +#include <stdlib.h> +#include <windows.h> +#include <dbghelp.h> +#include <rpc.h> + +#pragma warning(push) +// Disable exception handler warnings. +#pragma warning(disable:4530) + +#include <list> +#include <string> +#include <vector> + +#include "client/windows/common/ipc_protocol.h" +#include "client/windows/crash_generation/crash_generation_client.h" +#include "common/scoped_ptr.h" +#include "google_breakpad/common/minidump_format.h" + +namespace google_breakpad { + +using std::vector; +using std::wstring; + +// These entries store a list of memory regions that the client wants included +// in the minidump. +struct AppMemory { + ULONG64 ptr; + ULONG length; + + bool operator==(const struct AppMemory& other) const { + return ptr == other.ptr; + } + + bool operator==(const void* other) const { + return ptr == reinterpret_cast<ULONG64>(other); + } +}; +typedef std::list<AppMemory> AppMemoryList; + +class ExceptionHandler { + public: + // A callback function to run before Breakpad performs any substantial + // processing of an exception. A FilterCallback is called before writing + // a minidump. context is the parameter supplied by the user as + // callback_context when the handler was created. exinfo points to the + // exception record, if any; assertion points to assertion information, + // if any. + // + // If a FilterCallback returns true, Breakpad will continue processing, + // attempting to write a minidump. If a FilterCallback returns false, + // Breakpad will immediately report the exception as unhandled without + // writing a minidump, allowing another handler the opportunity to handle it. + typedef bool (*FilterCallback)(void* context, EXCEPTION_POINTERS* exinfo, + MDRawAssertionInfo* assertion); + + // A callback function to run after the minidump has been written. + // minidump_id is a unique id for the dump, so the minidump + // file is <dump_path>\<minidump_id>.dmp. context is the parameter supplied + // by the user as callback_context when the handler was created. exinfo + // points to the exception record, or NULL if no exception occurred. + // succeeded indicates whether a minidump file was successfully written. + // assertion points to information about an assertion if the handler was + // invoked by an assertion. + // + // If an exception occurred and the callback returns true, Breakpad will treat + // the exception as fully-handled, suppressing any other handlers from being + // notified of the exception. If the callback returns false, Breakpad will + // treat the exception as unhandled, and allow another handler to handle it. + // If there are no other handlers, Breakpad will report the exception to the + // system as unhandled, allowing a debugger or native crash dialog the + // opportunity to handle the exception. Most callback implementations + // should normally return the value of |succeeded|, or when they wish to + // not report an exception of handled, false. Callbacks will rarely want to + // return true directly (unless |succeeded| is true). + // + // For out-of-process dump generation, dump path and minidump ID will always + // be NULL. In case of out-of-process dump generation, the dump path and + // minidump id are controlled by the server process and are not communicated + // back to the crashing process. + typedef bool (*MinidumpCallback)(const wchar_t* dump_path, + const wchar_t* minidump_id, + void* context, + EXCEPTION_POINTERS* exinfo, + MDRawAssertionInfo* assertion, + bool succeeded); + + // HandlerType specifies which types of handlers should be installed, if + // any. Use HANDLER_NONE for an ExceptionHandler that remains idle, + // without catching any failures on its own. This type of handler may + // still be triggered by calling WriteMinidump. Otherwise, use a + // combination of the other HANDLER_ values, or HANDLER_ALL to install + // all handlers. + enum HandlerType { + HANDLER_NONE = 0, + HANDLER_EXCEPTION = 1 << 0, // SetUnhandledExceptionFilter + HANDLER_INVALID_PARAMETER = 1 << 1, // _set_invalid_parameter_handler + HANDLER_PURECALL = 1 << 2, // _set_purecall_handler + HANDLER_ALL = HANDLER_EXCEPTION | + HANDLER_INVALID_PARAMETER | + HANDLER_PURECALL + }; + + // Creates a new ExceptionHandler instance to handle writing minidumps. + // Before writing a minidump, the optional filter callback will be called. + // Its return value determines whether or not Breakpad should write a + // minidump. Minidump files will be written to dump_path, and the optional + // callback is called after writing the dump file, as described above. + // handler_types specifies the types of handlers that should be installed. + ExceptionHandler(const wstring& dump_path, + FilterCallback filter, + MinidumpCallback callback, + void* callback_context, + int handler_types); + + // Creates a new ExceptionHandler instance that can attempt to perform + // out-of-process dump generation if pipe_name is not NULL. If pipe_name is + // NULL, or if out-of-process dump generation registration step fails, + // in-process dump generation will be used. This also allows specifying + // the dump type to generate. + ExceptionHandler(const wstring& dump_path, + FilterCallback filter, + MinidumpCallback callback, + void* callback_context, + int handler_types, + MINIDUMP_TYPE dump_type, + const wchar_t* pipe_name, + const CustomClientInfo* custom_info); + + // As above, creates a new ExceptionHandler instance to perform + // out-of-process dump generation if the given pipe_handle is not NULL. + ExceptionHandler(const wstring& dump_path, + FilterCallback filter, + MinidumpCallback callback, + void* callback_context, + int handler_types, + MINIDUMP_TYPE dump_type, + HANDLE pipe_handle, + const CustomClientInfo* custom_info); + + // ExceptionHandler that ENSURES out-of-process dump generation. Expects a + // crash generation client that is already registered with a crash generation + // server. Takes ownership of the passed-in crash_generation_client. + // + // Usage example: + // crash_generation_client = new CrashGenerationClient(..); + // if (crash_generation_client->Register()) { + // // Registration with the crash generation server succeeded. + // // Out-of-process dump generation is guaranteed. + // g_handler = new ExceptionHandler(.., crash_generation_client, ..); + // return true; + // } + ExceptionHandler(const wstring& dump_path, + FilterCallback filter, + MinidumpCallback callback, + void* callback_context, + int handler_types, + CrashGenerationClient* crash_generation_client); + + ~ExceptionHandler(); + + // Get and set the minidump path. + wstring dump_path() const { return dump_path_; } + void set_dump_path(const wstring &dump_path) { + dump_path_ = dump_path; + dump_path_c_ = dump_path_.c_str(); + UpdateNextID(); // Necessary to put dump_path_ in next_minidump_path_. + } + + // Requests that a previously reported crash be uploaded. + bool RequestUpload(DWORD crash_id); + + // Writes a minidump immediately. This can be used to capture the + // execution state independently of a crash. Returns true on success. + bool WriteMinidump(); + + // Writes a minidump immediately, with the user-supplied exception + // information. + bool WriteMinidumpForException(EXCEPTION_POINTERS* exinfo); + + // Convenience form of WriteMinidump which does not require an + // ExceptionHandler instance. + static bool WriteMinidump(const wstring &dump_path, + MinidumpCallback callback, void* callback_context, + MINIDUMP_TYPE dump_type = MiniDumpNormal); + + // Write a minidump of |child| immediately. This can be used to + // capture the execution state of |child| independently of a crash. + // Pass a meaningful |child_blamed_thread| to make that thread in + // the child process the one from which a crash signature is + // extracted. + static bool WriteMinidumpForChild(HANDLE child, + DWORD child_blamed_thread, + const wstring& dump_path, + MinidumpCallback callback, + void* callback_context, + MINIDUMP_TYPE dump_type = MiniDumpNormal); + + // Get the thread ID of the thread requesting the dump (either the exception + // thread or any other thread that called WriteMinidump directly). This + // may be useful if you want to include additional thread state in your + // dumps. + DWORD get_requesting_thread_id() const { return requesting_thread_id_; } + + // Controls behavior of EXCEPTION_BREAKPOINT and EXCEPTION_SINGLE_STEP. + bool get_handle_debug_exceptions() const { return handle_debug_exceptions_; } + void set_handle_debug_exceptions(bool handle_debug_exceptions) { + handle_debug_exceptions_ = handle_debug_exceptions; + } + + // Controls behavior of EXCEPTION_INVALID_HANDLE. + bool get_consume_invalid_handle_exceptions() const { + return consume_invalid_handle_exceptions_; + } + void set_consume_invalid_handle_exceptions( + bool consume_invalid_handle_exceptions) { + consume_invalid_handle_exceptions_ = consume_invalid_handle_exceptions; + } + + // Returns whether out-of-process dump generation is used or not. + bool IsOutOfProcess() const { return crash_generation_client_.get() != NULL; } + + // Calling RegisterAppMemory(p, len) causes len bytes starting + // at address p to be copied to the minidump when a crash happens. + void RegisterAppMemory(void* ptr, size_t length); + void UnregisterAppMemory(void* ptr); + + private: + friend class AutoExceptionHandler; + + // Initializes the instance with given values. + void Initialize(const wstring& dump_path, + FilterCallback filter, + MinidumpCallback callback, + void* callback_context, + int handler_types, + MINIDUMP_TYPE dump_type, + const wchar_t* pipe_name, + HANDLE pipe_handle, + CrashGenerationClient* crash_generation_client, + const CustomClientInfo* custom_info); + + // Function pointer type for MiniDumpWriteDump, which is looked up + // dynamically. + typedef BOOL (WINAPI *MiniDumpWriteDump_type)( + HANDLE hProcess, + DWORD dwPid, + HANDLE hFile, + MINIDUMP_TYPE DumpType, + CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam); + + // Function pointer type for UuidCreate, which is looked up dynamically. + typedef RPC_STATUS (RPC_ENTRY *UuidCreate_type)(UUID* Uuid); + + // Runs the main loop for the exception handler thread. + static DWORD WINAPI ExceptionHandlerThreadMain(void* lpParameter); + + // Called on the exception thread when an unhandled exception occurs. + // Signals the exception handler thread to handle the exception. + static LONG WINAPI HandleException(EXCEPTION_POINTERS* exinfo); + +#if _MSC_VER >= 1400 // MSVC 2005/8 + // This function will be called by some CRT functions when they detect + // that they were passed an invalid parameter. Note that in _DEBUG builds, + // the CRT may display an assertion dialog before calling this function, + // and the function will not be called unless the assertion dialog is + // dismissed by clicking "Ignore." + static void HandleInvalidParameter(const wchar_t* expression, + const wchar_t* function, + const wchar_t* file, + unsigned int line, + uintptr_t reserved); +#endif // _MSC_VER >= 1400 + + // This function will be called by the CRT when a pure virtual + // function is called. + static void HandlePureVirtualCall(); + + // This is called on the exception thread or on another thread that + // the user wishes to produce a dump from. It calls + // WriteMinidumpWithException on the handler thread, avoiding stack + // overflows and inconsistent dumps due to writing the dump from + // the exception thread. If the dump is requested as a result of an + // exception, exinfo contains exception information, otherwise, it + // is NULL. If the dump is requested as a result of an assertion + // (such as an invalid parameter being passed to a CRT function), + // assertion contains data about the assertion, otherwise, it is NULL. + bool WriteMinidumpOnHandlerThread(EXCEPTION_POINTERS* exinfo, + MDRawAssertionInfo* assertion); + + // This function is called on the handler thread. It calls into + // WriteMinidumpWithExceptionForProcess() with a handle to the + // current process. requesting_thread_id is the ID of the thread + // that requested the dump. If the dump is requested as a result of + // an exception, exinfo contains exception information, otherwise, + // it is NULL. + bool WriteMinidumpWithException(DWORD requesting_thread_id, + EXCEPTION_POINTERS* exinfo, + MDRawAssertionInfo* assertion); + + // This function is used as a callback when calling MinidumpWriteDump, + // in order to add additional memory regions to the dump. + static BOOL CALLBACK MinidumpWriteDumpCallback( + PVOID context, + const PMINIDUMP_CALLBACK_INPUT callback_input, + PMINIDUMP_CALLBACK_OUTPUT callback_output); + + // This function does the actual writing of a minidump. It is + // called on the handler thread. requesting_thread_id is the ID of + // the thread that requested the dump, if that information is + // meaningful. If the dump is requested as a result of an + // exception, exinfo contains exception information, otherwise, it + // is NULL. process is the one that will be dumped. If + // requesting_thread_id is meaningful and should be added to the + // minidump, write_requester_stream is |true|. + bool WriteMinidumpWithExceptionForProcess(DWORD requesting_thread_id, + EXCEPTION_POINTERS* exinfo, + MDRawAssertionInfo* assertion, + HANDLE process, + bool write_requester_stream); + + // Generates a new ID and stores it in next_minidump_id_, and stores the + // path of the next minidump to be written in next_minidump_path_. + void UpdateNextID(); + + FilterCallback filter_; + MinidumpCallback callback_; + void* callback_context_; + + scoped_ptr<CrashGenerationClient> crash_generation_client_; + + // The directory in which a minidump will be written, set by the dump_path + // argument to the constructor, or set_dump_path. + wstring dump_path_; + + // The basename of the next minidump to be written, without the extension. + wstring next_minidump_id_; + + // The full pathname of the next minidump to be written, including the file + // extension. + wstring next_minidump_path_; + + // Pointers to C-string representations of the above. These are set when + // the above wstring versions are set in order to avoid calling c_str during + // an exception, as c_str may attempt to allocate heap memory. These + // pointers are not owned by the ExceptionHandler object, but their lifetimes + // should be equivalent to the lifetimes of the associated wstring, provided + // that the wstrings are not altered. + const wchar_t* dump_path_c_; + const wchar_t* next_minidump_id_c_; + const wchar_t* next_minidump_path_c_; + + HMODULE dbghelp_module_; + MiniDumpWriteDump_type minidump_write_dump_; + MINIDUMP_TYPE dump_type_; + + HMODULE rpcrt4_module_; + UuidCreate_type uuid_create_; + + // Tracks the handler types that were installed according to the + // handler_types constructor argument. + int handler_types_; + + // When installed_handler_ is true, previous_filter_ is the unhandled + // exception filter that was set prior to installing ExceptionHandler as + // the unhandled exception filter and pointing it to |this|. NULL indicates + // that there is no previous unhandled exception filter. + LPTOP_LEVEL_EXCEPTION_FILTER previous_filter_; + +#if _MSC_VER >= 1400 // MSVC 2005/8 + // Beginning in VC 8, the CRT provides an invalid parameter handler that will + // be called when some CRT functions are passed invalid parameters. In + // earlier CRTs, the same conditions would cause unexpected behavior or + // crashes. + _invalid_parameter_handler previous_iph_; +#endif // _MSC_VER >= 1400 + + // The CRT allows you to override the default handler for pure + // virtual function calls. + _purecall_handler previous_pch_; + + // The exception handler thread. + HANDLE handler_thread_; + + // True if the exception handler is being destroyed. + // Starting with MSVC 2005, Visual C has stronger guarantees on volatile vars. + // It has release semantics on write and acquire semantics on reads. + // See the msdn documentation. + volatile bool is_shutdown_; + + // The critical section enforcing the requirement that only one exception be + // handled by a handler at a time. + CRITICAL_SECTION handler_critical_section_; + + // Semaphores used to move exception handling between the exception thread + // and the handler thread. handler_start_semaphore_ is signalled by the + // exception thread to wake up the handler thread when an exception occurs. + // handler_finish_semaphore_ is signalled by the handler thread to wake up + // the exception thread when handling is complete. + HANDLE handler_start_semaphore_; + HANDLE handler_finish_semaphore_; + + // The next 2 fields contain data passed from the requesting thread to + // the handler thread. + + // The thread ID of the thread requesting the dump (either the exception + // thread or any other thread that called WriteMinidump directly). + DWORD requesting_thread_id_; + + // The exception info passed to the exception handler on the exception + // thread, if an exception occurred. NULL for user-requested dumps. + EXCEPTION_POINTERS* exception_info_; + + // If the handler is invoked due to an assertion, this will contain a + // pointer to the assertion information. It is NULL at other times. + MDRawAssertionInfo* assertion_; + + // The return value of the handler, passed from the handler thread back to + // the requesting thread. + bool handler_return_value_; + + // If true, the handler will intercept EXCEPTION_BREAKPOINT and + // EXCEPTION_SINGLE_STEP exceptions. Leave this false (the default) + // to not interfere with debuggers. + bool handle_debug_exceptions_; + + // If true, the handler will consume any EXCEPTION_INVALID_HANDLE exceptions. + // Leave this false (the default) to handle these exceptions as normal. + bool consume_invalid_handle_exceptions_; + + // Callers can request additional memory regions to be included in + // the dump. + AppMemoryList app_memory_info_; + + // A stack of ExceptionHandler objects that have installed unhandled + // exception filters. This vector is used by HandleException to determine + // which ExceptionHandler object to route an exception to. When an + // ExceptionHandler is created with install_handler true, it will append + // itself to this list. + static vector<ExceptionHandler*>* handler_stack_; + + // The index of the ExceptionHandler in handler_stack_ that will handle the + // next exception. Note that 0 means the last entry in handler_stack_, 1 + // means the next-to-last entry, and so on. This is used by HandleException + // to support multiple stacked Breakpad handlers. + static LONG handler_stack_index_; + + // handler_stack_critical_section_ guards operations on handler_stack_ and + // handler_stack_index_. The critical section is initialized by the + // first instance of the class and destroyed by the last instance of it. + static CRITICAL_SECTION handler_stack_critical_section_; + + // The number of instances of this class. + static volatile LONG instance_count_; + + // disallow copy ctor and operator= + explicit ExceptionHandler(const ExceptionHandler &); + void operator=(const ExceptionHandler &); +}; + +} // namespace google_breakpad + +#pragma warning(pop) + +#endif // CLIENT_WINDOWS_HANDLER_EXCEPTION_HANDLER_H__ diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/handler/objs.mozbuild b/toolkit/crashreporter/google-breakpad/src/client/windows/handler/objs.mozbuild new file mode 100644 index 000000000..6cabb09b7 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/handler/objs.mozbuild @@ -0,0 +1,14 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +lobjs_handler = [ + 'exception_handler.cc', +] + +subdir = 'toolkit/crashreporter/google-breakpad/src/client/windows/handler' +objs_handler = [ + '/%s/%s' % (subdir, s) for s in lobjs_handler +] diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/sender/crash_report_sender.cc b/toolkit/crashreporter/google-breakpad/src/client/windows/sender/crash_report_sender.cc new file mode 100644 index 000000000..70c36b0e2 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/sender/crash_report_sender.cc @@ -0,0 +1,142 @@ +// Copyright (c) 2006, 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. + +// Disable exception handler warnings. +#pragma warning( disable : 4530 ) + +#include <errno.h> + +#include "client/windows/sender/crash_report_sender.h" +#include "common/windows/http_upload.h" + +#if _MSC_VER < 1400 // MSVC 2005/8 +// Older MSVC doesn't have fscanf_s, but they are compatible as long as +// we don't use the string conversions (%s/%c/%S/%C). +#define fscanf_s fscanf +#endif + +namespace google_breakpad { + +static const char kCheckpointSignature[] = "GBP1\n"; + +CrashReportSender::CrashReportSender(const wstring &checkpoint_file) + : checkpoint_file_(checkpoint_file), + max_reports_per_day_(-1), + last_sent_date_(-1), + reports_sent_(0) { + FILE *fd; + if (OpenCheckpointFile(L"r", &fd) == 0) { + ReadCheckpoint(fd); + fclose(fd); + } +} + +ReportResult CrashReportSender::SendCrashReport( + const wstring &url, const map<wstring, wstring> ¶meters, + const map<wstring, wstring> &files, wstring *report_code) { + int today = GetCurrentDate(); + if (today == last_sent_date_ && + max_reports_per_day_ != -1 && + reports_sent_ >= max_reports_per_day_) { + return RESULT_THROTTLED; + } + + int http_response = 0; + bool result = HTTPUpload::SendRequest( + url, parameters, files, NULL, report_code, + &http_response); + + if (result) { + ReportSent(today); + return RESULT_SUCCEEDED; + } else if (http_response >= 400 && http_response < 500) { + return RESULT_REJECTED; + } else { + return RESULT_FAILED; + } +} + +void CrashReportSender::ReadCheckpoint(FILE *fd) { + char buf[128]; + if (!fgets(buf, sizeof(buf), fd) || + strcmp(buf, kCheckpointSignature) != 0) { + return; + } + + if (fscanf_s(fd, "%d\n", &last_sent_date_) != 1) { + last_sent_date_ = -1; + return; + } + if (fscanf_s(fd, "%d\n", &reports_sent_) != 1) { + reports_sent_ = 0; + return; + } +} + +void CrashReportSender::ReportSent(int today) { + // Update the report stats + if (today != last_sent_date_) { + last_sent_date_ = today; + reports_sent_ = 0; + } + ++reports_sent_; + + // Update the checkpoint file + FILE *fd; + if (OpenCheckpointFile(L"w", &fd) == 0) { + fputs(kCheckpointSignature, fd); + fprintf(fd, "%d\n", last_sent_date_); + fprintf(fd, "%d\n", reports_sent_); + fclose(fd); + } +} + +int CrashReportSender::GetCurrentDate() const { + SYSTEMTIME system_time; + GetSystemTime(&system_time); + return (system_time.wYear * 10000) + (system_time.wMonth * 100) + + system_time.wDay; +} + +int CrashReportSender::OpenCheckpointFile(const wchar_t *mode, FILE **fd) { + if (checkpoint_file_.empty()) { + return ENOENT; + } +#if _MSC_VER >= 1400 // MSVC 2005/8 + return _wfopen_s(fd, checkpoint_file_.c_str(), mode); +#else + *fd = _wfopen(checkpoint_file_.c_str(), mode); + if (*fd == NULL) { + return errno; + } + return 0; +#endif +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/sender/crash_report_sender.gyp b/toolkit/crashreporter/google-breakpad/src/client/windows/sender/crash_report_sender.gyp new file mode 100644 index 000000000..dc8583a0a --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/sender/crash_report_sender.gyp @@ -0,0 +1,46 @@ +# Copyright 2010 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. + +{ + 'includes': [ + '../../../build/common.gypi', + ], + 'targets': [ + { + 'target_name': 'crash_report_sender', + 'type': 'static_library', + 'sources': [ + 'crash_report_sender.cc', + 'crash_report_sender.h', + ], + 'dependencies': [ + '../breakpad_client.gyp:common' + ], + }, + ], +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/sender/crash_report_sender.h b/toolkit/crashreporter/google-breakpad/src/client/windows/sender/crash_report_sender.h new file mode 100644 index 000000000..7786cc699 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/sender/crash_report_sender.h @@ -0,0 +1,125 @@ +// Copyright (c) 2006, 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_WINDOWS_SENDER_CRASH_REPORT_SENDER_H__ +#define CLIENT_WINDOWS_SENDER_CRASH_REPORT_SENDER_H__ + +// CrashReportSender is a "static" class which provides an API to upload +// crash reports via HTTP(S). A crash report is formatted as a multipart POST +// request, which contains a set of caller-supplied string key/value pairs, +// and a minidump file to upload. +// +// To use this library in your project, you will need to link against +// wininet.lib. + +#pragma warning( push ) +// Disable exception handler warnings. +#pragma warning( disable : 4530 ) + +#include <map> +#include <string> + +namespace google_breakpad { + +using std::wstring; +using std::map; + +typedef enum { + RESULT_FAILED = 0, // Failed to communicate with the server; try later. + RESULT_REJECTED, // Successfully sent the crash report, but the + // server rejected it; don't resend this report. + RESULT_SUCCEEDED, // The server accepted the crash report. + RESULT_THROTTLED // No attempt was made to send the crash report, because + // we exceeded the maximum reports per day. +} ReportResult; + +class CrashReportSender { + public: + // Initializes a CrashReportSender instance. + // If checkpoint_file is non-empty, breakpad will persist crash report + // state to this file. A checkpoint file is required for + // set_max_reports_per_day() to function properly. + explicit CrashReportSender(const wstring &checkpoint_file); + ~CrashReportSender() {} + + // Sets the maximum number of crash reports that will be sent in a 24-hour + // period. This uses the state persisted to the checkpoint file. + // The default value of -1 means that there is no limit on reports sent. + void set_max_reports_per_day(int reports) { + max_reports_per_day_ = reports; + } + + int max_reports_per_day() const { return max_reports_per_day_; } + + // Sends the specified files, along with the map of + // name value pairs, as a multipart POST request to the given URL. + // Parameter names must contain only printable ASCII characters, + // and may not contain a quote (") character. + // Only HTTP(S) URLs are currently supported. The return value indicates + // the result of the operation (see above for possible results). + // If report_code is non-NULL and the report is sent successfully (that is, + // the return value is RESULT_SUCCEEDED), a code uniquely identifying the + // report will be returned in report_code. + // (Otherwise, report_code will be unchanged.) + ReportResult SendCrashReport(const wstring &url, + const map<wstring, wstring> ¶meters, + const map<wstring, wstring> &files, + wstring *report_code); + + private: + // Reads persistent state from a checkpoint file. + void ReadCheckpoint(FILE *fd); + + // Called when a new report has been sent, to update the checkpoint state. + void ReportSent(int today); + + // Returns today's date (UTC) formatted as YYYYMMDD. + int GetCurrentDate() const; + + // Opens the checkpoint file with the specified mode. + // Returns zero on success, or an error code on failure. + int OpenCheckpointFile(const wchar_t *mode, FILE **fd); + + wstring checkpoint_file_; + int max_reports_per_day_; + // The last date on which we sent a report, expressed as YYYYMMDD. + int last_sent_date_; + // Number of reports sent on last_sent_date_ + int reports_sent_; + + // Disallow copy constructor and operator= + explicit CrashReportSender(const CrashReportSender &); + void operator=(const CrashReportSender &); +}; + +} // namespace google_breakpad + +#pragma warning( pop ) + +#endif // CLIENT_WINDOWS_SENDER_CRASH_REPORT_SENDER_H__ diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/sender/objs.mozbuild b/toolkit/crashreporter/google-breakpad/src/client/windows/sender/objs.mozbuild new file mode 100644 index 000000000..7b519328d --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/sender/objs.mozbuild @@ -0,0 +1,14 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +lobjs_sender = [ + 'crash_report_sender.cc', +] + +subdir = 'toolkit/crashreporter/google-breakpad/src/client/windows/sender' +objs_sender = [ + '/%s/%s' % (subdir, s) for s in lobjs_sender +] diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/abstract_class.cc b/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/abstract_class.cc new file mode 100644 index 000000000..32f78f2b9 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/abstract_class.cc @@ -0,0 +1,53 @@ +// Copyright (c) 2008, 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/windows/tests/crash_generation_app/abstract_class.h" + +namespace google_breakpad { + +Base::Base(Derived* derived) + : derived_(derived) { +} + +Base::~Base() { + derived_->DoSomething(); +} + +#pragma warning(push) +#pragma warning(disable:4355) +// Disable warning C4355: 'this' : used in base member initializer list. +Derived::Derived() + : Base(this) { // C4355 +} +#pragma warning(pop) + +void Derived::DoSomething() { +} + +} // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/abstract_class.h b/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/abstract_class.h new file mode 100644 index 000000000..e3f2a4f37 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/abstract_class.h @@ -0,0 +1,57 @@ +// Copyright (c) 2008, 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_WINDOWS_TESTS_CRASH_GENERATION_APP_ABSTRACT_CLASS_H__ +#define CLIENT_WINDOWS_TESTS_CRASH_GENERATION_APP_ABSTRACT_CLASS_H__ + +namespace google_breakpad { + +// Dummy classes to help generate a pure call violation. + +class Derived; + +class Base { + public: + Base(Derived* derived); + virtual ~Base(); + virtual void DoSomething() = 0; + + private: + Derived* derived_; +}; + +class Derived : public Base { + public: + Derived(); + virtual void DoSomething(); +}; + +} // namespace google_breakpad + +#endif // CLIENT_WINDOWS_TESTS_CRASH_GENERATION_APP_CRASH_GENERATION_APP_H__ diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/crash_generation_app.cc b/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/crash_generation_app.cc new file mode 100644 index 000000000..0d837e521 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/crash_generation_app.cc @@ -0,0 +1,522 @@ +// Copyright (c) 2008, 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. + +// crash_generation_app.cpp : Defines the entry point for the application. +// + +#include "client/windows/tests/crash_generation_app/crash_generation_app.h" + +#include <windows.h> +#include <tchar.h> + +#include "client/windows/crash_generation/client_info.h" +#include "client/windows/crash_generation/crash_generation_server.h" +#include "client/windows/handler/exception_handler.h" +#include "client/windows/common/ipc_protocol.h" + +#include "client/windows/tests/crash_generation_app/abstract_class.h" + +namespace google_breakpad { + +const int kMaxLoadString = 100; +const wchar_t kPipeName[] = L"\\\\.\\pipe\\BreakpadCrashServices\\TestServer"; + +const DWORD kEditBoxStyles = WS_CHILD | + WS_VISIBLE | + WS_VSCROLL | + ES_LEFT | + ES_MULTILINE | + ES_AUTOVSCROLL | + ES_READONLY; + +// Maximum length of a line in the edit box. +const size_t kMaximumLineLength = 256; + +// CS to access edit control in a thread safe way. +static CRITICAL_SECTION* cs_edit = NULL; + +// Edit control. +static HWND client_status_edit_box; + +HINSTANCE current_instance; // Current instance. +TCHAR title[kMaxLoadString]; // Title bar text. +TCHAR window_class[kMaxLoadString]; // Main window class name. + +ATOM MyRegisterClass(HINSTANCE instance); +BOOL InitInstance(HINSTANCE, int); +LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); +INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); + +static size_t kCustomInfoCount = 2; +static CustomInfoEntry kCustomInfoEntries[] = { + CustomInfoEntry(L"prod", L"CrashTestApp"), + CustomInfoEntry(L"ver", L"1.0"), +}; + +static ExceptionHandler* handler = NULL; +static CrashGenerationServer* crash_server = NULL; + +// Registers the window class. +// +// This function and its usage are only necessary if you want this code +// to be compatible with Win32 systems prior to the 'RegisterClassEx' +// function that was added to Windows 95. It is important to call this +// function so that the application will get 'well formed' small icons +// associated with it. +ATOM MyRegisterClass(HINSTANCE instance) { + WNDCLASSEX wcex; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = WndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = instance; + wcex.hIcon = LoadIcon(instance, + MAKEINTRESOURCE(IDI_CRASHGENERATIONAPP)); + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); + wcex.lpszMenuName = MAKEINTRESOURCE(IDC_CRASHGENERATIONAPP); + wcex.lpszClassName = window_class; + wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); + + return RegisterClassEx(&wcex); +} + +// Saves instance handle and creates main window +// +// In this function, we save the instance handle in a global variable and +// create and display the main program window. +BOOL InitInstance(HINSTANCE instance, int command_show) { + current_instance = instance; + HWND wnd = CreateWindow(window_class, + title, + WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, + 0, + CW_USEDEFAULT, + 0, + NULL, + NULL, + instance, + NULL); + + if (!wnd) { + return FALSE; + } + + ShowWindow(wnd, command_show); + UpdateWindow(wnd); + + return TRUE; +} + +static void AppendTextToEditBox(TCHAR* text) { + EnterCriticalSection(cs_edit); + SYSTEMTIME current_time; + GetLocalTime(¤t_time); + TCHAR line[kMaximumLineLength]; + int result = swprintf_s(line, + kMaximumLineLength, + L"[%.2d-%.2d-%.4d %.2d:%.2d:%.2d] %s", + current_time.wMonth, + current_time.wDay, + current_time.wYear, + current_time.wHour, + current_time.wMinute, + current_time.wSecond, + text); + + if (result == -1) { + return; + } + + int length = GetWindowTextLength(client_status_edit_box); + SendMessage(client_status_edit_box, + EM_SETSEL, + (WPARAM)length, + (LPARAM)length); + SendMessage(client_status_edit_box, + EM_REPLACESEL, + (WPARAM)FALSE, + (LPARAM)line); + LeaveCriticalSection(cs_edit); +} + +static DWORD WINAPI AppendTextWorker(void* context) { + TCHAR* text = reinterpret_cast<TCHAR*>(context); + + AppendTextToEditBox(text); + delete[] text; + + return 0; +} + +bool ShowDumpResults(const wchar_t* dump_path, + const wchar_t* minidump_id, + void* context, + EXCEPTION_POINTERS* exinfo, + MDRawAssertionInfo* assertion, + bool succeeded) { + TCHAR* text = new TCHAR[kMaximumLineLength]; + text[0] = _T('\0'); + int result = swprintf_s(text, + kMaximumLineLength, + TEXT("Dump generation request %s\r\n"), + succeeded ? TEXT("succeeded") : TEXT("failed")); + if (result == -1) { + delete [] text; + } + + QueueUserWorkItem(AppendTextWorker, text, WT_EXECUTEDEFAULT); + return succeeded; +} + +static void ShowClientConnected(void* context, + const ClientInfo* client_info) { + TCHAR* line = new TCHAR[kMaximumLineLength]; + line[0] = _T('\0'); + int result = swprintf_s(line, + kMaximumLineLength, + L"Client connected:\t\t%d\r\n", + client_info->pid()); + + if (result == -1) { + delete[] line; + return; + } + + QueueUserWorkItem(AppendTextWorker, line, WT_EXECUTEDEFAULT); +} + +static void ShowClientCrashed(void* context, + const ClientInfo* client_info, + const wstring* dump_path) { + TCHAR* line = new TCHAR[kMaximumLineLength]; + line[0] = _T('\0'); + int result = swprintf_s(line, + kMaximumLineLength, + TEXT("Client requested dump:\t%d\r\n"), + client_info->pid()); + + if (result == -1) { + delete[] line; + return; + } + + QueueUserWorkItem(AppendTextWorker, line, WT_EXECUTEDEFAULT); + + CustomClientInfo custom_info = client_info->GetCustomInfo(); + if (custom_info.count <= 0) { + return; + } + + wstring str_line; + for (size_t i = 0; i < custom_info.count; ++i) { + if (i > 0) { + str_line += L", "; + } + str_line += custom_info.entries[i].name; + str_line += L": "; + str_line += custom_info.entries[i].value; + } + + line = new TCHAR[kMaximumLineLength]; + line[0] = _T('\0'); + result = swprintf_s(line, + kMaximumLineLength, + L"%s\n", + str_line.c_str()); + if (result == -1) { + delete[] line; + return; + } + QueueUserWorkItem(AppendTextWorker, line, WT_EXECUTEDEFAULT); +} + +static void ShowClientExited(void* context, + const ClientInfo* client_info) { + TCHAR* line = new TCHAR[kMaximumLineLength]; + line[0] = _T('\0'); + int result = swprintf_s(line, + kMaximumLineLength, + TEXT("Client exited:\t\t%d\r\n"), + client_info->pid()); + + if (result == -1) { + delete[] line; + return; + } + + QueueUserWorkItem(AppendTextWorker, line, WT_EXECUTEDEFAULT); +} + +void CrashServerStart() { + // Do not create another instance of the server. + if (crash_server) { + return; + } + + std::wstring dump_path = L"C:\\Dumps\\"; + + if (_wmkdir(dump_path.c_str()) && (errno != EEXIST)) { + MessageBoxW(NULL, L"Unable to create dump directory", L"Dumper", MB_OK); + return; + } + + crash_server = new CrashGenerationServer(kPipeName, + NULL, + ShowClientConnected, + NULL, + ShowClientCrashed, + NULL, + ShowClientExited, + NULL, + NULL, + NULL, + true, + &dump_path); + + if (!crash_server->Start()) { + MessageBoxW(NULL, L"Unable to start server", L"Dumper", MB_OK); + delete crash_server; + crash_server = NULL; + } +} + +void CrashServerStop() { + delete crash_server; + crash_server = NULL; +} + +void DerefZeroCrash() { + int* x = 0; + *x = 1; +} + +void InvalidParamCrash() { + printf(NULL); +} + +void PureCallCrash() { + Derived derived; +} + +void RequestDump() { + if (!handler->WriteMinidump()) { + MessageBoxW(NULL, L"Dump request failed", L"Dumper", MB_OK); + } + kCustomInfoEntries[1].set_value(L"1.1"); +} + +void CleanUp() { + if (cs_edit) { + DeleteCriticalSection(cs_edit); + delete cs_edit; + } + + if (handler) { + delete handler; + } + + if (crash_server) { + delete crash_server; + } +} + +// Processes messages for the main window. +// +// WM_COMMAND - process the application menu. +// WM_PAINT - Paint the main window. +// WM_DESTROY - post a quit message and return. +LRESULT CALLBACK WndProc(HWND wnd, + UINT message, + WPARAM w_param, + LPARAM l_param) { + int message_id; + int message_event; + PAINTSTRUCT ps; + HDC hdc; + + HINSTANCE instance = (HINSTANCE)GetWindowLongPtr(wnd, GWLP_HINSTANCE); + + switch (message) { + case WM_COMMAND: + // Parse the menu selections. + message_id = LOWORD(w_param); + message_event = HIWORD(w_param); + switch (message_id) { + case IDM_ABOUT: + DialogBox(current_instance, + MAKEINTRESOURCE(IDD_ABOUTBOX), + wnd, + About); + break; + case IDM_EXIT: + DestroyWindow(wnd); + break; + case ID_SERVER_START: + CrashServerStart(); + break; + case ID_SERVER_STOP: + CrashServerStop(); + break; + case ID_CLIENT_DEREFZERO: + DerefZeroCrash(); + break; + case ID_CLIENT_INVALIDPARAM: + InvalidParamCrash(); + break; + case ID_CLIENT_PURECALL: + PureCallCrash(); + break; + case ID_CLIENT_REQUESTEXPLICITDUMP: + RequestDump(); + break; + default: + return DefWindowProc(wnd, message, w_param, l_param); + } + break; + case WM_CREATE: + client_status_edit_box = CreateWindow(TEXT("EDIT"), + NULL, + kEditBoxStyles, + 0, + 0, + 0, + 0, + wnd, + NULL, + instance, + NULL); + break; + case WM_SIZE: + // Make the edit control the size of the window's client area. + MoveWindow(client_status_edit_box, + 0, + 0, + LOWORD(l_param), // width of client area. + HIWORD(l_param), // height of client area. + TRUE); // repaint window. + break; + case WM_SETFOCUS: + SetFocus(client_status_edit_box); + break; + case WM_PAINT: + hdc = BeginPaint(wnd, &ps); + EndPaint(wnd, &ps); + break; + case WM_DESTROY: + CleanUp(); + PostQuitMessage(0); + break; + default: + return DefWindowProc(wnd, message, w_param, l_param); + } + + return 0; +} + +// Message handler for about box. +INT_PTR CALLBACK About(HWND dlg, + UINT message, + WPARAM w_param, + LPARAM l_param) { + UNREFERENCED_PARAMETER(l_param); + switch (message) { + case WM_INITDIALOG: + return (INT_PTR)TRUE; + + case WM_COMMAND: + if (LOWORD(w_param) == IDOK || LOWORD(w_param) == IDCANCEL) { + EndDialog(dlg, LOWORD(w_param)); + return (INT_PTR)TRUE; + } + break; + } + + return (INT_PTR)FALSE; +} + +} // namespace google_breakpad + +int APIENTRY _tWinMain(HINSTANCE instance, + HINSTANCE previous_instance, + LPTSTR command_line, + int command_show) { + using namespace google_breakpad; + + UNREFERENCED_PARAMETER(previous_instance); + UNREFERENCED_PARAMETER(command_line); + + cs_edit = new CRITICAL_SECTION(); + InitializeCriticalSection(cs_edit); + + CustomClientInfo custom_info = {kCustomInfoEntries, kCustomInfoCount}; + + CrashServerStart(); + // This is needed for CRT to not show dialog for invalid param + // failures and instead let the code handle it. + _CrtSetReportMode(_CRT_ASSERT, 0); + handler = new ExceptionHandler(L"C:\\dumps\\", + NULL, + google_breakpad::ShowDumpResults, + NULL, + ExceptionHandler::HANDLER_ALL, + MiniDumpNormal, + kPipeName, + &custom_info); + + // Initialize global strings. + LoadString(instance, IDS_APP_TITLE, title, kMaxLoadString); + LoadString(instance, + IDC_CRASHGENERATIONAPP, + window_class, + kMaxLoadString); + MyRegisterClass(instance); + + // Perform application initialization. + if (!InitInstance(instance, command_show)) { + return FALSE; + } + + HACCEL accel_table = LoadAccelerators( + instance, + MAKEINTRESOURCE(IDC_CRASHGENERATIONAPP)); + + // Main message loop. + MSG msg; + while (GetMessage(&msg, NULL, 0, 0)) { + if (!TranslateAccelerator(msg.hwnd, accel_table, &msg)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + return static_cast<int>(msg.wParam); +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/crash_generation_app.gyp b/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/crash_generation_app.gyp new file mode 100644 index 000000000..3ce307da0 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/crash_generation_app.gyp @@ -0,0 +1,63 @@ +# Copyright 2010 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. + +{ + 'includes': [ + '../../../../build/common.gypi', + ], + 'targets': [ + { + 'target_name': 'crash_generation_app', + 'type': 'executable', + 'sources': [ + 'abstract_class.cc', + 'abstract_class.h', + 'crash_generation_app.cc', + 'crash_generation_app.h', + 'crash_generation_app.ico', + 'crash_generation_app.rc', + 'resource.h', + 'small.ico', + ], + 'libraries': [ + 'user32.lib', + ], + 'dependencies': [ + '../../breakpad_client.gyp:common', + '../../crash_generation/crash_generation.gyp:crash_generation_server', + '../../crash_generation/crash_generation.gyp:crash_generation_client', + '../../handler/exception_handler.gyp:exception_handler', + ], + 'msvs_settings': { + 'VCLinkerTool': { + 'SubSystem': '2', # Windows Subsystem as opposed to a console app + }, + }, + } + ] +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/crash_generation_app.h b/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/crash_generation_app.h new file mode 100644 index 000000000..4d3bb6eb2 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/crash_generation_app.h @@ -0,0 +1,35 @@ +// Copyright (c) 2008, 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_WINDOWS_TESTS_CRASH_GENERATION_APP_CRASH_GENERATION_APP_H__ +#define CLIENT_WINDOWS_TESTS_CRASH_GENERATION_APP_CRASH_GENERATION_APP_H__ + +#include "resource.h" + +#endif // CLIENT_WINDOWS_TESTS_CRASH_GENERATION_APP_CRASH_GENERATION_APP_H__ diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/crash_generation_app.ico b/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/crash_generation_app.ico Binary files differnew file mode 100644 index 000000000..d551aa3aa --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/crash_generation_app.ico diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/crash_generation_app.rc b/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/crash_generation_app.rc new file mode 100644 index 000000000..a362562b9 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/crash_generation_app.rc @@ -0,0 +1,144 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#define APSTUDIO_HIDDEN_SYMBOLS +#include "windows.h" +#undef APSTUDIO_HIDDEN_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_CRASHGENERATIONAPP ICON "crash_generation_app.ico" +IDI_SMALL ICON "small.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Menu +// + +IDC_CRASHGENERATIONAPP MENU +BEGIN + POPUP "&File" + BEGIN + MENUITEM "E&xit", IDM_EXIT + END + POPUP "&Server" + BEGIN + MENUITEM "&Start", ID_SERVER_START + MENUITEM "S&top", ID_SERVER_STOP + END + POPUP "&Client" + BEGIN + MENUITEM "&Deref Zero", ID_CLIENT_DEREFZERO + MENUITEM "&Invalid Param", ID_CLIENT_INVALIDPARAM + MENUITEM "&Pure Call", ID_CLIENT_PURECALL + MENUITEM "&Request Dump", ID_CLIENT_REQUESTEXPLICITDUMP + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Accelerator +// + +IDC_CRASHGENERATIONAPP ACCELERATORS +BEGIN + "?", IDM_ABOUT, ASCII, ALT + "/", IDM_ABOUT, ASCII, ALT +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_ABOUTBOX DIALOG 22, 17, 230, 75 +STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU +CAPTION "About" +FONT 8, "System" +BEGIN + ICON IDI_CRASHGENERATIONAPP,IDC_MYICON,14,9,16,16 + LTEXT "CrashGenerationApp Version 1.0",IDC_STATIC,49,10,119,8,SS_NOPREFIX + LTEXT "Copyright (C) 2008",IDC_STATIC,49,20,119,8 + DEFPUSHBUTTON "OK",IDOK,195,6,30,11,WS_GROUP +END + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#define APSTUDIO_HIDDEN_SYMBOLS\r\n" + "#include ""windows.h""\r\n" + "#undef APSTUDIO_HIDDEN_SYMBOLS\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_APP_TITLE "CrashGenerationApp" + IDC_CRASHGENERATIONAPP "CRASHGENERATIONAPP" +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/resource.h b/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/resource.h new file mode 100644 index 000000000..8c7f6570a --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/resource.h @@ -0,0 +1,73 @@ +// Copyright (c) 2008, 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. + +// PreCompile.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#ifndef CLIENT_WINDOWS_TESTS_CRASH_GENERATION_APP_RESOURCE_H__ +#define CLIENT_WINDOWS_TESTS_CRASH_GENERATION_APP_RESOURCE_H__ + +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by crash_generation_app.rc +// +#define IDC_MYICON 2 +#define IDD_CRASHGENERATIONAPP_DIALOG 102 +#define IDS_APP_TITLE 103 +#define IDD_ABOUTBOX 103 +#define IDM_ABOUT 104 +#define IDM_EXIT 105 +#define IDI_CRASHGENERATIONAPP 107 +#define IDI_SMALL 108 +#define IDC_CRASHGENERATIONAPP 109 +#define IDR_MAINFRAME 128 +#define ID_SERVER_START 32771 +#define ID_SERVER_STOP 32772 +#define ID_CLIENT_INVALIDPARAM 32773 +#define ID_CLIENT_ASSERTFAILURE 32774 +#define ID_CLIENT_DEREFZERO 32775 +#define ID_CLIENT_PURECALL 32777 +#define ID_CLIENT_REQUESTEXPLICITDUMP 32778 +#define IDC_STATIC -1 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NO_MFC 1 +#define _APS_NEXT_RESOURCE_VALUE 129 +#define _APS_NEXT_COMMAND_VALUE 32780 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 110 +#endif +#endif + +#endif // CLIENT_WINDOWS_TESTS_CRASH_GENERATION_APP_RESOURCE_H__ diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/small.ico b/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/small.ico Binary files differnew file mode 100644 index 000000000..d551aa3aa --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/tests/crash_generation_app/small.ico diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/client_tests.gyp b/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/client_tests.gyp new file mode 100644 index 000000000..b13603ef4 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/client_tests.gyp @@ -0,0 +1,80 @@ +# Copyright 2010 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. + +{ + 'includes': [ + '../../../build/common.gypi', + ], + 'targets': [ + { + 'target_name': 'client_tests', + 'type': 'executable', + 'sources': [ + 'exception_handler_test.h', + 'exception_handler_test.cc', + 'exception_handler_death_test.cc', + 'exception_handler_nesting_test.cc', + 'minidump_test.cc', + 'dump_analysis.cc', + 'dump_analysis.h', + 'crash_generation_server_test.cc' + ], + 'dependencies': [ + 'testing.gyp:gtest', + 'testing.gyp:gmock', + '../breakpad_client.gyp:common', + '../crash_generation/crash_generation.gyp:crash_generation_server', + '../crash_generation/crash_generation.gyp:crash_generation_client', + '../handler/exception_handler.gyp:exception_handler', + 'processor_bits', + ] + }, + { + 'target_name': 'processor_bits', + 'type': 'static_library', + 'include_dirs': [ + '<(DEPTH)', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '<(DEPTH)', + ] + }, + 'sources': [ + '<(DEPTH)/common/string_conversion.cc', + '<(DEPTH)/processor/basic_code_modules.cc', + '<(DEPTH)/processor/dump_context.cc', + '<(DEPTH)/processor/dump_object.cc', + '<(DEPTH)/processor/logging.cc', + '<(DEPTH)/processor/minidump.cc', + '<(DEPTH)/processor/pathname_stripper.cc', + '<(DEPTH)/processor/proc_maps_linux.cc', + ] + } + ], +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/crash_generation_server_test.cc b/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/crash_generation_server_test.cc new file mode 100644 index 000000000..b7b2b84f2 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/crash_generation_server_test.cc @@ -0,0 +1,305 @@ +// Copyright 2010, 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 "testing/gtest/include/gtest/gtest.h" +#include "testing/include/gmock/gmock.h" + +#include "client/windows/crash_generation/crash_generation_server.h" +#include "client/windows/common/ipc_protocol.h" + +using testing::_; + +namespace { + +const wchar_t kPipeName[] = + L"\\\\.\\pipe\\CrashGenerationServerTest\\TestCaseServer"; + +const DWORD kPipeDesiredAccess = FILE_READ_DATA | + FILE_WRITE_DATA | + FILE_WRITE_ATTRIBUTES; + +const DWORD kPipeFlagsAndAttributes = SECURITY_IDENTIFICATION | + SECURITY_SQOS_PRESENT; + +const DWORD kPipeMode = PIPE_READMODE_MESSAGE; + +#define arraysize(f) (sizeof(f) / sizeof(*f)) +const google_breakpad::CustomInfoEntry kCustomInfoEntries[] = { + google_breakpad::CustomInfoEntry(L"prod", L"CrashGenerationServerTest"), + google_breakpad::CustomInfoEntry(L"ver", L"1.0"), +}; + +class CrashGenerationServerTest : public ::testing::Test { + public: + CrashGenerationServerTest() + : crash_generation_server_(kPipeName, + NULL, + CallOnClientConnected, &mock_callbacks_, + CallOnClientDumpRequested, &mock_callbacks_, + CallOnClientExited, &mock_callbacks_, + CallOnClientUploadRequested, &mock_callbacks_, + false, + NULL), + thread_id_(0), + exception_pointers_(NULL) { + memset(&assert_info_, 0, sizeof(assert_info_)); + } + + protected: + class MockCrashGenerationServerCallbacks { + public: + MOCK_METHOD1(OnClientConnected, + void(const google_breakpad::ClientInfo* client_info)); + MOCK_METHOD2(OnClientDumpRequested, + void(const google_breakpad::ClientInfo* client_info, + const std::wstring* file_path)); + MOCK_METHOD1(OnClientExited, + void(const google_breakpad::ClientInfo* client_info)); + MOCK_METHOD1(OnClientUploadRequested, + void(const DWORD crash_id)); + }; + + enum ClientFault { + NO_FAULT, + CLOSE_AFTER_CONNECT, + SEND_INVALID_REGISTRATION, + TRUNCATE_REGISTRATION, + CLOSE_AFTER_REGISTRATION, + RESPONSE_BUFFER_TOO_SMALL, + CLOSE_AFTER_RESPONSE, + SEND_INVALID_ACK + }; + + void SetUp() { + ASSERT_TRUE(crash_generation_server_.Start()); + } + + void FaultyClient(ClientFault fault_type) { + HANDLE pipe = CreateFile(kPipeName, + kPipeDesiredAccess, + 0, + NULL, + OPEN_EXISTING, + kPipeFlagsAndAttributes, + NULL); + + if (pipe == INVALID_HANDLE_VALUE) { + ASSERT_EQ(ERROR_PIPE_BUSY, GetLastError()); + + // Cannot continue retrying if wait on pipe fails. + ASSERT_TRUE(WaitNamedPipe(kPipeName, 500)); + + pipe = CreateFile(kPipeName, + kPipeDesiredAccess, + 0, + NULL, + OPEN_EXISTING, + kPipeFlagsAndAttributes, + NULL); + } + + ASSERT_NE(pipe, INVALID_HANDLE_VALUE); + + DWORD mode = kPipeMode; + ASSERT_TRUE(SetNamedPipeHandleState(pipe, &mode, NULL, NULL)); + + DoFaultyClient(fault_type, pipe); + + CloseHandle(pipe); + } + + void DoTestFault(ClientFault fault) { + EXPECT_CALL(mock_callbacks_, OnClientConnected(_)).Times(0); + ASSERT_NO_FATAL_FAILURE(FaultyClient(fault)); + ASSERT_NO_FATAL_FAILURE(FaultyClient(fault)); + ASSERT_NO_FATAL_FAILURE(FaultyClient(fault)); + + EXPECT_CALL(mock_callbacks_, OnClientConnected(_)); + + ASSERT_NO_FATAL_FAILURE(FaultyClient(NO_FAULT)); + + // Slight hack. The OnClientConnected is only invoked after the ack is + // received by the server. At that point, the FaultyClient call has already + // returned. The best way to wait until the server is done handling that is + // to send one more ping, whose processing will be blocked by delivery of + // the OnClientConnected message. + ASSERT_NO_FATAL_FAILURE(FaultyClient(CLOSE_AFTER_CONNECT)); + } + + MockCrashGenerationServerCallbacks mock_callbacks_; + + private: + // Depends on the caller to successfully open the pipe before invocation and + // to close it immediately afterwards. + void DoFaultyClient(ClientFault fault_type, HANDLE pipe) { + if (fault_type == CLOSE_AFTER_CONNECT) { + return; + } + + google_breakpad::CustomClientInfo custom_info = {kCustomInfoEntries, + arraysize(kCustomInfoEntries)}; + + google_breakpad::ProtocolMessage msg( + fault_type == SEND_INVALID_REGISTRATION ? + google_breakpad::MESSAGE_TAG_NONE : + google_breakpad::MESSAGE_TAG_REGISTRATION_REQUEST, + GetCurrentProcessId(), + MiniDumpNormal, + &thread_id_, + &exception_pointers_, + &assert_info_, + custom_info, + NULL, + NULL, + NULL); + + DWORD bytes_count = 0; + + ASSERT_TRUE(WriteFile(pipe, + &msg, + fault_type == TRUNCATE_REGISTRATION ? + sizeof(msg) / 2 : sizeof(msg), + &bytes_count, + NULL)); + + if (fault_type == CLOSE_AFTER_REGISTRATION) { + return; + } + + google_breakpad::ProtocolMessage reply; + + if (!ReadFile(pipe, + &reply, + fault_type == RESPONSE_BUFFER_TOO_SMALL ? + sizeof(google_breakpad::ProtocolMessage) / 2 : + sizeof(google_breakpad::ProtocolMessage), + &bytes_count, + NULL)) { + switch (fault_type) { + case TRUNCATE_REGISTRATION: + case RESPONSE_BUFFER_TOO_SMALL: + case SEND_INVALID_REGISTRATION: + return; + + default: + FAIL() << "Unexpectedly failed to register."; + } + } + + if (fault_type == CLOSE_AFTER_RESPONSE) { + return; + } + + google_breakpad::ProtocolMessage ack_msg; + ack_msg.tag = google_breakpad::MESSAGE_TAG_REGISTRATION_ACK; + + ASSERT_TRUE(WriteFile(pipe, + &ack_msg, + SEND_INVALID_ACK ? + sizeof(ack_msg) : sizeof(ack_msg) / 2, + &bytes_count, + NULL)); + + return; + } + + static void CallOnClientConnected( + void* context, const google_breakpad::ClientInfo* client_info) { + static_cast<MockCrashGenerationServerCallbacks*>(context)-> + OnClientConnected(client_info); + } + + static void CallOnClientDumpRequested( + void* context, + const google_breakpad::ClientInfo* client_info, + const std::wstring* file_path) { + static_cast<MockCrashGenerationServerCallbacks*>(context)-> + OnClientDumpRequested(client_info, file_path); + } + + static void CallOnClientExited( + void* context, const google_breakpad::ClientInfo* client_info) { + static_cast<MockCrashGenerationServerCallbacks*>(context)-> + OnClientExited(client_info); + } + + static void CallOnClientUploadRequested(void* context, const DWORD crash_id) { + static_cast<MockCrashGenerationServerCallbacks*>(context)-> + OnClientUploadRequested(crash_id); + } + + DWORD thread_id_; + EXCEPTION_POINTERS* exception_pointers_; + MDRawAssertionInfo assert_info_; + + google_breakpad::CrashGenerationServer crash_generation_server_; +}; + +TEST_F(CrashGenerationServerTest, PingServerTest) { + DoTestFault(CLOSE_AFTER_CONNECT); +} + +TEST_F(CrashGenerationServerTest, InvalidRegistration) { + DoTestFault(SEND_INVALID_REGISTRATION); +} + +TEST_F(CrashGenerationServerTest, TruncateRegistration) { + DoTestFault(TRUNCATE_REGISTRATION); +} + +TEST_F(CrashGenerationServerTest, CloseAfterRegistration) { + DoTestFault(CLOSE_AFTER_REGISTRATION); +} + +TEST_F(CrashGenerationServerTest, ResponseBufferTooSmall) { + DoTestFault(RESPONSE_BUFFER_TOO_SMALL); +} + +TEST_F(CrashGenerationServerTest, CloseAfterResponse) { + DoTestFault(CLOSE_AFTER_RESPONSE); +} + +// It turns out that, as long as you send one byte, the ACK is accepted and +// registration succeeds. +TEST_F(CrashGenerationServerTest, SendInvalidAck) { + EXPECT_CALL(mock_callbacks_, OnClientConnected(_)); + ASSERT_NO_FATAL_FAILURE(FaultyClient(SEND_INVALID_ACK)); + + // See DoTestFault for an explanation of this line + ASSERT_NO_FATAL_FAILURE(FaultyClient(CLOSE_AFTER_CONNECT)); + + EXPECT_CALL(mock_callbacks_, OnClientConnected(_)); + ASSERT_NO_FATAL_FAILURE(FaultyClient(NO_FAULT)); + + // See DoTestFault for an explanation of this line + ASSERT_NO_FATAL_FAILURE(FaultyClient(CLOSE_AFTER_CONNECT)); +} + +} // anonymous namespace diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/dump_analysis.cc b/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/dump_analysis.cc new file mode 100644 index 000000000..6bf854719 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/dump_analysis.cc @@ -0,0 +1,184 @@ +// Copyright (c) 2010, 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 <windows.h> +#include <objbase.h> +#include <dbghelp.h> + +#include "client/windows/unittests/dump_analysis.h" // NOLINT +#include "testing/gtest/include/gtest/gtest.h" + +DumpAnalysis::~DumpAnalysis() { + if (dump_file_view_ != NULL) { + EXPECT_TRUE(::UnmapViewOfFile(dump_file_view_)); + ::CloseHandle(dump_file_mapping_); + dump_file_mapping_ = NULL; + } + + if (dump_file_handle_ != NULL) { + ::CloseHandle(dump_file_handle_); + dump_file_handle_ = NULL; + } +} + +void DumpAnalysis::EnsureDumpMapped() { + if (dump_file_view_ == NULL) { + dump_file_handle_ = ::CreateFile(dump_file_.c_str(), + GENERIC_READ, + 0, + NULL, + OPEN_EXISTING, + 0, + NULL); + ASSERT_TRUE(dump_file_handle_ != NULL); + ASSERT_TRUE(dump_file_mapping_ == NULL); + + dump_file_mapping_ = ::CreateFileMapping(dump_file_handle_, + NULL, + PAGE_READONLY, + 0, + 0, + NULL); + ASSERT_TRUE(dump_file_mapping_ != NULL); + + dump_file_view_ = ::MapViewOfFile(dump_file_mapping_, + FILE_MAP_READ, + 0, + 0, + 0); + ASSERT_TRUE(dump_file_view_ != NULL); + } +} + +bool DumpAnalysis::HasTebs() const { + MINIDUMP_THREAD_LIST* thread_list = NULL; + size_t thread_list_size = GetStream(ThreadListStream, &thread_list); + + if (thread_list_size > 0 && thread_list != NULL) { + for (ULONG i = 0; i < thread_list->NumberOfThreads; ++i) { + if (!HasMemory(thread_list->Threads[i].Teb)) + return false; + } + + return true; + } + + // No thread list, no TEB info. + return false; +} + +bool DumpAnalysis::HasPeb() const { + MINIDUMP_THREAD_LIST* thread_list = NULL; + size_t thread_list_size = GetStream(ThreadListStream, &thread_list); + + if (thread_list_size > 0 && thread_list != NULL && + thread_list->NumberOfThreads > 0) { + FakeTEB* teb = NULL; + if (!HasMemory(thread_list->Threads[0].Teb, &teb)) + return false; + + return HasMemory(teb->peb); + } + + return false; +} + +bool DumpAnalysis::HasStream(ULONG stream_number) const { + void* stream = NULL; + size_t stream_size = GetStreamImpl(stream_number, &stream); + return stream_size > 0 && stream != NULL; +} + +size_t DumpAnalysis::GetStreamImpl(ULONG stream_number, void** stream) const { + MINIDUMP_DIRECTORY* directory = NULL; + ULONG memory_list_size = 0; + BOOL ret = ::MiniDumpReadDumpStream(dump_file_view_, + stream_number, + &directory, + stream, + &memory_list_size); + + return ret ? memory_list_size : 0; +} + +bool DumpAnalysis::HasMemoryImpl(const void *addr_in, size_t structuresize, + void **structure) const { + uintptr_t address = reinterpret_cast<uintptr_t>(addr_in); + MINIDUMP_MEMORY_LIST* memory_list = NULL; + size_t memory_list_size = GetStream(MemoryListStream, &memory_list); + if (memory_list_size > 0 && memory_list != NULL) { + for (ULONG i = 0; i < memory_list->NumberOfMemoryRanges; ++i) { + MINIDUMP_MEMORY_DESCRIPTOR& descr = memory_list->MemoryRanges[i]; + const uintptr_t range_start = + static_cast<uintptr_t>(descr.StartOfMemoryRange); + uintptr_t range_end = range_start + descr.Memory.DataSize; + + if (address >= range_start && + address + structuresize < range_end) { + // The start address falls in the range, and the end address is + // in bounds, return a pointer to the structure if requested. + if (structure != NULL) + *structure = RVA_TO_ADDR(dump_file_view_, descr.Memory.Rva); + + return true; + } + } + } + + // We didn't find the range in a MINIDUMP_MEMORY_LIST, so maybe this + // is a full dump using MINIDUMP_MEMORY64_LIST with all the memory at the + // end of the dump file. + MINIDUMP_MEMORY64_LIST* memory64_list = NULL; + memory_list_size = GetStream(Memory64ListStream, &memory64_list); + if (memory_list_size > 0 && memory64_list != NULL) { + // Keep track of where the current descriptor maps to. + RVA64 curr_rva = memory64_list->BaseRva; + for (ULONG i = 0; i < memory64_list->NumberOfMemoryRanges; ++i) { + MINIDUMP_MEMORY_DESCRIPTOR64& descr = memory64_list->MemoryRanges[i]; + uintptr_t range_start = + static_cast<uintptr_t>(descr.StartOfMemoryRange); + uintptr_t range_end = range_start + static_cast<size_t>(descr.DataSize); + + if (address >= range_start && + address + structuresize < range_end) { + // The start address falls in the range, and the end address is + // in bounds, return a pointer to the structure if requested. + if (structure != NULL) + *structure = RVA_TO_ADDR(dump_file_view_, curr_rva); + + return true; + } + + // Advance the current RVA. + curr_rva += descr.DataSize; + } + } + + return false; +} diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/dump_analysis.h b/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/dump_analysis.h new file mode 100644 index 000000000..6cef48d81 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/dump_analysis.h @@ -0,0 +1,102 @@ +// Copyright (c) 2010, 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_WINDOWS_UNITTESTS_DUMP_ANALYSIS_H_ +#define CLIENT_WINDOWS_UNITTESTS_DUMP_ANALYSIS_H_ + +#include "client/windows/crash_generation/minidump_generator.h" + +// Convenience to get to the PEB pointer in a TEB. +struct FakeTEB { + char dummy[0x30]; + void* peb; +}; + +class DumpAnalysis { + public: + explicit DumpAnalysis(const std::wstring& file_path) + : dump_file_(file_path), dump_file_view_(NULL), dump_file_mapping_(NULL), + dump_file_handle_(NULL) { + EnsureDumpMapped(); + } + ~DumpAnalysis(); + + bool HasStream(ULONG stream_number) const; + + // This is template to keep type safety in the front, but we end up casting + // to void** inside the implementation to pass the pointer to Win32. So + // casting here is considered safe. + template <class StreamType> + size_t GetStream(ULONG stream_number, StreamType** stream) const { + return GetStreamImpl(stream_number, reinterpret_cast<void**>(stream)); + } + + bool HasTebs() const; + bool HasPeb() const; + bool HasMemory(ULONG64 address) const { + return HasMemory<BYTE>(address, NULL); + } + + bool HasMemory(const void* address) const { + return HasMemory<BYTE>(address, NULL); + } + + template <class StructureType> + bool HasMemory(ULONG64 address, StructureType** structure = NULL) const { + // We can't cope with 64 bit addresses for now. + if (address > 0xFFFFFFFFUL) + return false; + + return HasMemory(reinterpret_cast<void*>(address), structure); + } + + template <class StructureType> + bool HasMemory(const void* addr_in, StructureType** structure = NULL) const { + return HasMemoryImpl(addr_in, sizeof(StructureType), + reinterpret_cast<void**>(structure)); + } + + protected: + void EnsureDumpMapped(); + + HANDLE dump_file_mapping_; + HANDLE dump_file_handle_; + void* dump_file_view_; + std::wstring dump_file_; + + private: + // This is the implementation of GetStream<>. + size_t GetStreamImpl(ULONG stream_number, void** stream) const; + + // This is the implementation of HasMemory<>. + bool HasMemoryImpl(const void* addr_in, size_t pointersize, + void** structure) const; +}; + +#endif // CLIENT_WINDOWS_UNITTESTS_DUMP_ANALYSIS_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/exception_handler_death_test.cc b/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/exception_handler_death_test.cc new file mode 100644 index 000000000..079ca3d6f --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/exception_handler_death_test.cc @@ -0,0 +1,582 @@ +// Copyright 2009, 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 <windows.h> +#include <dbghelp.h> +#include <strsafe.h> +#include <objbase.h> +#include <shellapi.h> + +#include <string> + +#include "breakpad_googletest_includes.h" +#include "client/windows/crash_generation/crash_generation_server.h" +#include "client/windows/handler/exception_handler.h" +#include "client/windows/unittests/exception_handler_test.h" +#include "common/windows/string_utils-inl.h" +#include "google_breakpad/processor/minidump.h" + +namespace { + +using std::wstring; +using namespace google_breakpad; + +const wchar_t kPipeName[] = L"\\\\.\\pipe\\BreakpadCrashTest\\TestCaseServer"; +const char kSuccessIndicator[] = "success"; +const char kFailureIndicator[] = "failure"; + +// Utility function to test for a path's existence. +BOOL DoesPathExist(const TCHAR *path_name); + +enum OutOfProcGuarantee { + OUT_OF_PROC_GUARANTEED, + OUT_OF_PROC_BEST_EFFORT, +}; + +class ExceptionHandlerDeathTest : public ::testing::Test { + protected: + // Member variable for each test that they can use + // for temporary storage. + TCHAR temp_path_[MAX_PATH]; + // Actually constructs a temp path name. + virtual void SetUp(); + // A helper method that tests can use to crash. + void DoCrashAccessViolation(const OutOfProcGuarantee out_of_proc_guarantee); + void DoCrashPureVirtualCall(); +}; + +void ExceptionHandlerDeathTest::SetUp() { + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + TCHAR temp_path[MAX_PATH] = { '\0' }; + TCHAR test_name_wide[MAX_PATH] = { '\0' }; + // We want the temporary directory to be what the OS returns + // to us, + the test case name. + GetTempPath(MAX_PATH, temp_path); + // The test case name is exposed as a c-style string, + // convert it to a wchar_t string. + int dwRet = MultiByteToWideChar(CP_ACP, 0, test_info->name(), + strlen(test_info->name()), + test_name_wide, + MAX_PATH); + if (!dwRet) { + assert(false); + } + StringCchPrintfW(temp_path_, MAX_PATH, L"%s%s", temp_path, test_name_wide); + CreateDirectory(temp_path_, NULL); +} + +BOOL DoesPathExist(const TCHAR *path_name) { + DWORD flags = GetFileAttributes(path_name); + if (flags == INVALID_FILE_ATTRIBUTES) { + return FALSE; + } + return TRUE; +} + +bool MinidumpWrittenCallback(const wchar_t* dump_path, + const wchar_t* minidump_id, + void* context, + EXCEPTION_POINTERS* exinfo, + MDRawAssertionInfo* assertion, + bool succeeded) { + if (succeeded && DoesPathExist(dump_path)) { + fprintf(stderr, kSuccessIndicator); + } else { + fprintf(stderr, kFailureIndicator); + } + // If we don't flush, the output doesn't get sent before + // this process dies. + fflush(stderr); + return succeeded; +} + +TEST_F(ExceptionHandlerDeathTest, InProcTest) { + // For the in-proc test, we just need to instantiate an exception + // handler in in-proc mode, and crash. Since the entire test is + // reexecuted in the child process, we don't have to worry about + // the semantics of the exception handler being inherited/not + // inherited across CreateProcess(). + ASSERT_TRUE(DoesPathExist(temp_path_)); + scoped_ptr<google_breakpad::ExceptionHandler> exc( + new google_breakpad::ExceptionHandler( + temp_path_, + NULL, + &MinidumpWrittenCallback, + NULL, + google_breakpad::ExceptionHandler::HANDLER_ALL)); + + // Disable GTest SEH handler + testing::DisableExceptionHandlerInScope disable_exception_handler; + + int *i = NULL; + ASSERT_DEATH((*i)++, kSuccessIndicator); +} + +static bool gDumpCallbackCalled = false; + +void clientDumpCallback(void *dump_context, + const google_breakpad::ClientInfo *client_info, + const std::wstring *dump_path) { + gDumpCallbackCalled = true; +} + +void ExceptionHandlerDeathTest::DoCrashAccessViolation( + const OutOfProcGuarantee out_of_proc_guarantee) { + scoped_ptr<google_breakpad::ExceptionHandler> exc; + + if (out_of_proc_guarantee == OUT_OF_PROC_GUARANTEED) { + google_breakpad::CrashGenerationClient *client = + new google_breakpad::CrashGenerationClient(kPipeName, + MiniDumpNormal, + NULL); // custom_info + ASSERT_TRUE(client->Register()); + exc.reset(new google_breakpad::ExceptionHandler( + temp_path_, + NULL, // filter + NULL, // callback + NULL, // callback_context + google_breakpad::ExceptionHandler::HANDLER_ALL, + client)); + } else { + ASSERT_TRUE(out_of_proc_guarantee == OUT_OF_PROC_BEST_EFFORT); + exc.reset(new google_breakpad::ExceptionHandler( + temp_path_, + NULL, // filter + NULL, // callback + NULL, // callback_context + google_breakpad::ExceptionHandler::HANDLER_ALL, + MiniDumpNormal, + kPipeName, + NULL)); // custom_info + } + + // Disable GTest SEH handler + testing::DisableExceptionHandlerInScope disable_exception_handler; + + // Although this is executing in the child process of the death test, + // if it's not true we'll still get an error rather than the crash + // being expected. + ASSERT_TRUE(exc->IsOutOfProcess()); + int *i = NULL; + printf("%d\n", (*i)++); +} + +TEST_F(ExceptionHandlerDeathTest, OutOfProcTest) { + // We can take advantage of a detail of google test here to save some + // complexity in testing: when you do a death test, it actually forks. + // So we can make the main test harness the crash generation server, + // and call ASSERT_DEATH on a NULL dereference, it to expecting test + // the out of process scenario, since it's happening in a different + // process! This is different from the above because, above, we pass + // a NULL pipe name, and we also don't start a crash generation server. + + ASSERT_TRUE(DoesPathExist(temp_path_)); + std::wstring dump_path(temp_path_); + google_breakpad::CrashGenerationServer server( + kPipeName, NULL, NULL, NULL, &clientDumpCallback, NULL, NULL, NULL, NULL, + NULL, true, &dump_path); + + // This HAS to be EXPECT_, because when this test case is executed in the + // child process, the server registration will fail due to the named pipe + // being the same. + EXPECT_TRUE(server.Start()); + gDumpCallbackCalled = false; + ASSERT_DEATH(this->DoCrashAccessViolation(OUT_OF_PROC_BEST_EFFORT), ""); + EXPECT_TRUE(gDumpCallbackCalled); +} + +TEST_F(ExceptionHandlerDeathTest, OutOfProcGuaranteedTest) { + // This is similar to the previous test (OutOfProcTest). The only difference + // is that in this test, the crash generation client is created and registered + // with the crash generation server outside of the ExceptionHandler + // constructor which allows breakpad users to opt out of the default + // in-process dump generation when the registration with the crash generation + // server fails. + + ASSERT_TRUE(DoesPathExist(temp_path_)); + std::wstring dump_path(temp_path_); + google_breakpad::CrashGenerationServer server( + kPipeName, NULL, NULL, NULL, &clientDumpCallback, NULL, NULL, NULL, NULL, + NULL, true, &dump_path); + + // This HAS to be EXPECT_, because when this test case is executed in the + // child process, the server registration will fail due to the named pipe + // being the same. + EXPECT_TRUE(server.Start()); + gDumpCallbackCalled = false; + ASSERT_DEATH(this->DoCrashAccessViolation(OUT_OF_PROC_GUARANTEED), ""); + EXPECT_TRUE(gDumpCallbackCalled); +} + +TEST_F(ExceptionHandlerDeathTest, InvalidParameterTest) { + using google_breakpad::ExceptionHandler; + + ASSERT_TRUE(DoesPathExist(temp_path_)); + ExceptionHandler handler(temp_path_, NULL, NULL, NULL, + ExceptionHandler::HANDLER_INVALID_PARAMETER); + + // Disable the message box for assertions + _CrtSetReportMode(_CRT_ASSERT, 0); + + // Call with a bad argument. The invalid parameter will be swallowed + // and a dump will be generated, the process will exit(0). + ASSERT_EXIT(printf(NULL), ::testing::ExitedWithCode(0), ""); +} + + +struct PureVirtualCallBase { + PureVirtualCallBase() { + // We have to reinterpret so the linker doesn't get confused because the + // method isn't defined. + reinterpret_cast<PureVirtualCallBase*>(this)->PureFunction(); + } + virtual ~PureVirtualCallBase() {} + virtual void PureFunction() const = 0; +}; +struct PureVirtualCall : public PureVirtualCallBase { + PureVirtualCall() { PureFunction(); } + virtual void PureFunction() const {} +}; + +void ExceptionHandlerDeathTest::DoCrashPureVirtualCall() { + PureVirtualCall instance; +} + +TEST_F(ExceptionHandlerDeathTest, PureVirtualCallTest) { + using google_breakpad::ExceptionHandler; + + ASSERT_TRUE(DoesPathExist(temp_path_)); + ExceptionHandler handler(temp_path_, NULL, NULL, NULL, + ExceptionHandler::HANDLER_PURECALL); + + // Disable the message box for assertions + _CrtSetReportMode(_CRT_ASSERT, 0); + + // Calls a pure virtual function. + EXPECT_EXIT(DoCrashPureVirtualCall(), ::testing::ExitedWithCode(0), ""); +} + +wstring find_minidump_in_directory(const wstring &directory) { + wstring search_path = directory + L"\\*"; + WIN32_FIND_DATA find_data; + HANDLE find_handle = FindFirstFileW(search_path.c_str(), &find_data); + if (find_handle == INVALID_HANDLE_VALUE) + return wstring(); + + wstring filename; + do { + const wchar_t extension[] = L".dmp"; + const int extension_length = sizeof(extension) / sizeof(extension[0]) - 1; + const int filename_length = wcslen(find_data.cFileName); + if (filename_length > extension_length && + wcsncmp(extension, + find_data.cFileName + filename_length - extension_length, + extension_length) == 0) { + filename = directory + L"\\" + find_data.cFileName; + break; + } + } while (FindNextFile(find_handle, &find_data)); + FindClose(find_handle); + return filename; +} + +#ifndef ADDRESS_SANITIZER + +TEST_F(ExceptionHandlerDeathTest, InstructionPointerMemory) { + ASSERT_TRUE(DoesPathExist(temp_path_)); + scoped_ptr<google_breakpad::ExceptionHandler> exc( + new google_breakpad::ExceptionHandler( + temp_path_, + NULL, + NULL, + NULL, + google_breakpad::ExceptionHandler::HANDLER_ALL)); + + // Disable GTest SEH handler + testing::DisableExceptionHandlerInScope disable_exception_handler; + + // Get some executable memory. + const uint32_t kMemorySize = 256; // bytes + const int kOffset = kMemorySize / 2; + // This crashes with SIGILL on x86/x86-64/arm. + const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff }; + char* memory = reinterpret_cast<char*>(VirtualAlloc(NULL, + kMemorySize, + MEM_COMMIT | MEM_RESERVE, + PAGE_EXECUTE_READWRITE)); + ASSERT_TRUE(memory); + + // Write some instructions that will crash. Put them + // in the middle of the block of memory, because the + // minidump should contain 128 bytes on either side of the + // instruction pointer. + memcpy(memory + kOffset, instructions, sizeof(instructions)); + + // Now execute the instructions, which should crash. + typedef void (*void_function)(void); + void_function memory_function = + reinterpret_cast<void_function>(memory + kOffset); + ASSERT_DEATH(memory_function(), ""); + + // free the memory. + VirtualFree(memory, 0, MEM_RELEASE); + + // Verify that the resulting minidump contains the memory around the IP + wstring minidump_filename_wide = find_minidump_in_directory(temp_path_); + ASSERT_FALSE(minidump_filename_wide.empty()); + string minidump_filename; + ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(minidump_filename_wide, + &minidump_filename)); + + // Read the minidump. Locate the exception record and the + // memory list, and then ensure that there is a memory region + // in the memory list that covers the instruction pointer from + // the exception record. + { + Minidump minidump(minidump_filename); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(exception); + ASSERT_TRUE(memory_list); + ASSERT_LT((unsigned)0, memory_list->region_count()); + + MinidumpContext* context = exception->GetContext(); + ASSERT_TRUE(context); + + uint64_t instruction_pointer; + ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer)); + + MinidumpMemoryRegion* region = + memory_list->GetMemoryRegionForAddress(instruction_pointer); + ASSERT_TRUE(region); + + EXPECT_EQ(kMemorySize, region->GetSize()); + const uint8_t* bytes = region->GetMemory(); + ASSERT_TRUE(bytes); + + uint8_t prefix_bytes[kOffset]; + uint8_t suffix_bytes[kMemorySize - kOffset - sizeof(instructions)]; + memset(prefix_bytes, 0, sizeof(prefix_bytes)); + memset(suffix_bytes, 0, sizeof(suffix_bytes)); + EXPECT_EQ(0, memcmp(bytes, prefix_bytes, sizeof(prefix_bytes))); + EXPECT_EQ(0, memcmp(bytes + kOffset, instructions, sizeof(instructions))); + EXPECT_EQ(0, memcmp(bytes + kOffset + sizeof(instructions), + suffix_bytes, sizeof(suffix_bytes))); + } + + DeleteFileW(minidump_filename_wide.c_str()); +} + +TEST_F(ExceptionHandlerDeathTest, InstructionPointerMemoryMinBound) { + ASSERT_TRUE(DoesPathExist(temp_path_)); + scoped_ptr<google_breakpad::ExceptionHandler> exc( + new google_breakpad::ExceptionHandler( + temp_path_, + NULL, + NULL, + NULL, + google_breakpad::ExceptionHandler::HANDLER_ALL)); + + // Disable GTest SEH handler + testing::DisableExceptionHandlerInScope disable_exception_handler; + + SYSTEM_INFO sSysInfo; // Useful information about the system + GetSystemInfo(&sSysInfo); // Initialize the structure. + + const uint32_t kMemorySize = 256; // bytes + const DWORD kPageSize = sSysInfo.dwPageSize; + const int kOffset = 0; + // This crashes with SIGILL on x86/x86-64/arm. + const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff }; + // Get some executable memory. Specifically, reserve two pages, + // but only commit the second. + char* all_memory = reinterpret_cast<char*>(VirtualAlloc(NULL, + kPageSize * 2, + MEM_RESERVE, + PAGE_NOACCESS)); + ASSERT_TRUE(all_memory); + char* memory = all_memory + kPageSize; + ASSERT_TRUE(VirtualAlloc(memory, kPageSize, + MEM_COMMIT, PAGE_EXECUTE_READWRITE)); + + // Write some instructions that will crash. Put them + // in the middle of the block of memory, because the + // minidump should contain 128 bytes on either side of the + // instruction pointer. + memcpy(memory + kOffset, instructions, sizeof(instructions)); + + // Now execute the instructions, which should crash. + typedef void (*void_function)(void); + void_function memory_function = + reinterpret_cast<void_function>(memory + kOffset); + ASSERT_DEATH(memory_function(), ""); + + // free the memory. + VirtualFree(memory, 0, MEM_RELEASE); + + // Verify that the resulting minidump contains the memory around the IP + wstring minidump_filename_wide = find_minidump_in_directory(temp_path_); + ASSERT_FALSE(minidump_filename_wide.empty()); + string minidump_filename; + ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(minidump_filename_wide, + &minidump_filename)); + + // Read the minidump. Locate the exception record and the + // memory list, and then ensure that there is a memory region + // in the memory list that covers the instruction pointer from + // the exception record. + { + Minidump minidump(minidump_filename); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(exception); + ASSERT_TRUE(memory_list); + ASSERT_LT((unsigned)0, memory_list->region_count()); + + MinidumpContext* context = exception->GetContext(); + ASSERT_TRUE(context); + + uint64_t instruction_pointer; + ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer)); + + MinidumpMemoryRegion* region = + memory_list->GetMemoryRegionForAddress(instruction_pointer); + ASSERT_TRUE(region); + + EXPECT_EQ(kMemorySize / 2, region->GetSize()); + const uint8_t* bytes = region->GetMemory(); + ASSERT_TRUE(bytes); + + uint8_t suffix_bytes[kMemorySize / 2 - sizeof(instructions)]; + memset(suffix_bytes, 0, sizeof(suffix_bytes)); + EXPECT_TRUE(memcmp(bytes + kOffset, + instructions, sizeof(instructions)) == 0); + EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(instructions), + suffix_bytes, sizeof(suffix_bytes)) == 0); + } + + DeleteFileW(minidump_filename_wide.c_str()); +} + +TEST_F(ExceptionHandlerDeathTest, InstructionPointerMemoryMaxBound) { + ASSERT_TRUE(DoesPathExist(temp_path_)); + scoped_ptr<google_breakpad::ExceptionHandler> exc( + new google_breakpad::ExceptionHandler( + temp_path_, + NULL, + NULL, + NULL, + google_breakpad::ExceptionHandler::HANDLER_ALL)); + + // Disable GTest SEH handler + testing::DisableExceptionHandlerInScope disable_exception_handler; + + SYSTEM_INFO sSysInfo; // Useful information about the system + GetSystemInfo(&sSysInfo); // Initialize the structure. + + const DWORD kPageSize = sSysInfo.dwPageSize; + // This crashes with SIGILL on x86/x86-64/arm. + const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff }; + const int kOffset = kPageSize - sizeof(instructions); + // Get some executable memory. Specifically, reserve two pages, + // but only commit the first. + char* memory = reinterpret_cast<char*>(VirtualAlloc(NULL, + kPageSize * 2, + MEM_RESERVE, + PAGE_NOACCESS)); + ASSERT_TRUE(memory); + ASSERT_TRUE(VirtualAlloc(memory, kPageSize, + MEM_COMMIT, PAGE_EXECUTE_READWRITE)); + + // Write some instructions that will crash. + memcpy(memory + kOffset, instructions, sizeof(instructions)); + + // Now execute the instructions, which should crash. + typedef void (*void_function)(void); + void_function memory_function = + reinterpret_cast<void_function>(memory + kOffset); + ASSERT_DEATH(memory_function(), ""); + + // free the memory. + VirtualFree(memory, 0, MEM_RELEASE); + + // Verify that the resulting minidump contains the memory around the IP + wstring minidump_filename_wide = find_minidump_in_directory(temp_path_); + ASSERT_FALSE(minidump_filename_wide.empty()); + string minidump_filename; + ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(minidump_filename_wide, + &minidump_filename)); + + // Read the minidump. Locate the exception record and the + // memory list, and then ensure that there is a memory region + // in the memory list that covers the instruction pointer from + // the exception record. + { + Minidump minidump(minidump_filename); + ASSERT_TRUE(minidump.Read()); + + MinidumpException* exception = minidump.GetException(); + MinidumpMemoryList* memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(exception); + ASSERT_TRUE(memory_list); + ASSERT_LT((unsigned)0, memory_list->region_count()); + + MinidumpContext* context = exception->GetContext(); + ASSERT_TRUE(context); + + uint64_t instruction_pointer; + ASSERT_TRUE(context->GetInstructionPointer(&instruction_pointer)); + + MinidumpMemoryRegion* region = + memory_list->GetMemoryRegionForAddress(instruction_pointer); + ASSERT_TRUE(region); + + const size_t kPrefixSize = 128; // bytes + EXPECT_EQ(kPrefixSize + sizeof(instructions), region->GetSize()); + const uint8_t* bytes = region->GetMemory(); + ASSERT_TRUE(bytes); + + uint8_t prefix_bytes[kPrefixSize]; + memset(prefix_bytes, 0, sizeof(prefix_bytes)); + EXPECT_EQ(0, memcmp(bytes, prefix_bytes, sizeof(prefix_bytes))); + EXPECT_EQ(0, memcmp(bytes + kPrefixSize, + instructions, sizeof(instructions))); + } + + DeleteFileW(minidump_filename_wide.c_str()); +} + +#endif // !ADDRESS_SANITIZER + +} // namespace diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/exception_handler_nesting_test.cc b/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/exception_handler_nesting_test.cc new file mode 100644 index 000000000..3ae1d7cd0 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/exception_handler_nesting_test.cc @@ -0,0 +1,327 @@ +// Copyright 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 <windows.h> + +#include <string> + +#include "breakpad_googletest_includes.h" +#include "client/windows/handler/exception_handler.h" +#include "client/windows/unittests/exception_handler_test.h" + +namespace { + +const char kFoo[] = "foo"; +const char kBar[] = "bar"; + +const char kStartOfLine[] = "^"; +const char kEndOfLine[] = "$"; + +const char kFilterReturnsTrue[] = "filter_returns_true"; +const char kFilterReturnsFalse[] = "filter_returns_false"; + +const char kCallbackReturnsTrue[] = "callback_returns_true"; +const char kCallbackReturnsFalse[] = "callback_returns_false"; + +bool DoesPathExist(const wchar_t *path_name) { + DWORD flags = GetFileAttributes(path_name); + if (flags == INVALID_FILE_ATTRIBUTES) { + return false; + } + return true; +} + +// A callback function to run before Breakpad performs any substantial +// processing of an exception. A FilterCallback is called before writing +// a minidump. context is the parameter supplied by the user as +// callback_context when the handler was created. exinfo points to the +// exception record, if any; assertion points to assertion information, +// if any. +// +// If a FilterCallback returns true, Breakpad will continue processing, +// attempting to write a minidump. If a FilterCallback returns false, +// Breakpad will immediately report the exception as unhandled without +// writing a minidump, allowing another handler the opportunity to handle it. +template <bool filter_return_value> +bool CrashHandlerFilter(void* context, + EXCEPTION_POINTERS* exinfo, + MDRawAssertionInfo* assertion) { + if (filter_return_value) { + fprintf(stderr, kFilterReturnsTrue); + } else { + fprintf(stderr, kFilterReturnsFalse); + } + fflush(stderr); + + return filter_return_value; +} + +// A callback function to run after the minidump has been written. +// minidump_id is a unique id for the dump, so the minidump +// file is <dump_path>\<minidump_id>.dmp. context is the parameter supplied +// by the user as callback_context when the handler was created. exinfo +// points to the exception record, or NULL if no exception occurred. +// succeeded indicates whether a minidump file was successfully written. +// assertion points to information about an assertion if the handler was +// invoked by an assertion. +// +// If an exception occurred and the callback returns true, Breakpad will treat +// the exception as fully-handled, suppressing any other handlers from being +// notified of the exception. If the callback returns false, Breakpad will +// treat the exception as unhandled, and allow another handler to handle it. +// If there are no other handlers, Breakpad will report the exception to the +// system as unhandled, allowing a debugger or native crash dialog the +// opportunity to handle the exception. Most callback implementations +// should normally return the value of |succeeded|, or when they wish to +// not report an exception of handled, false. Callbacks will rarely want to +// return true directly (unless |succeeded| is true). +// +// For out-of-process dump generation, dump path and minidump ID will always +// be NULL. In case of out-of-process dump generation, the dump path and +// minidump id are controlled by the server process and are not communicated +// back to the crashing process. +template <bool callback_return_value> +bool MinidumpWrittenCallback(const wchar_t* dump_path, + const wchar_t* minidump_id, + void* context, + EXCEPTION_POINTERS* exinfo, + MDRawAssertionInfo* assertion, + bool succeeded) { + bool rv = false; + if (callback_return_value && + succeeded && + DoesPathExist(dump_path)) { + rv = true; + fprintf(stderr, kCallbackReturnsTrue); + } else { + fprintf(stderr, kCallbackReturnsFalse); + } + fflush(stderr); + + return rv; +} + + +void DoCrash(const char *message) { + if (message) { + fprintf(stderr, "%s", message); + fflush(stderr); + } + int *i = NULL; + (*i)++; + + ASSERT_TRUE(false); +} + +void InstallExceptionHandlerAndCrash(bool install_filter, + bool filter_return_value, + bool install_callback, + bool callback_return_value) { + wchar_t temp_path[MAX_PATH] = { '\0' }; + GetTempPath(MAX_PATH, temp_path); + + ASSERT_TRUE(DoesPathExist(temp_path)); + google_breakpad::ExceptionHandler exc( + temp_path, + install_filter ? + (filter_return_value ? + &CrashHandlerFilter<true> : + &CrashHandlerFilter<false>) : + NULL, + install_callback ? + (callback_return_value ? + &MinidumpWrittenCallback<true> : + &MinidumpWrittenCallback<false>) : + NULL, + NULL, // callback_context + google_breakpad::ExceptionHandler::HANDLER_EXCEPTION); + + // Disable GTest SEH handler + testing::DisableExceptionHandlerInScope disable_exception_handler; + + DoCrash(NULL); +} + +TEST(AssertDeathSanity, Simple) { + ASSERT_DEATH(DoCrash(NULL), ""); +} + +TEST(AssertDeathSanity, Regex) { + ASSERT_DEATH(DoCrash(kFoo), + std::string(kStartOfLine) + + std::string(kFoo) + + std::string(kEndOfLine)); + + ASSERT_DEATH(DoCrash(kBar), + std::string(kStartOfLine) + + std::string(kBar) + + std::string(kEndOfLine)); +} + +TEST(ExceptionHandlerCallbacks, FilterTrue_No_Callback) { + ASSERT_DEATH( + InstallExceptionHandlerAndCrash(true, // install_filter + true, // filter_return_value + false, // install_callback + false), // callback_return_value + std::string(kStartOfLine) + + std::string(kFilterReturnsTrue) + + std::string(kEndOfLine)); +} + +TEST(ExceptionHandlerCallbacks, FilterTrue_Callback) { + ASSERT_DEATH( + InstallExceptionHandlerAndCrash(true, // install_filter + true, // filter_return_value + true, // install_callback + false), // callback_return_value + std::string(kStartOfLine) + + std::string(kFilterReturnsTrue) + + std::string(kCallbackReturnsFalse) + + std::string(kEndOfLine)); +} + +TEST(ExceptionHandlerCallbacks, FilterFalse_No_Callback) { + ASSERT_DEATH( + InstallExceptionHandlerAndCrash(true, // install_filter + false, // filter_return_value + false, // install_callback + false), // callback_return_value + std::string(kStartOfLine) + + std::string(kFilterReturnsFalse) + + std::string(kEndOfLine)); +} + +// Callback shouldn't be executed when filter returns false +TEST(ExceptionHandlerCallbacks, FilterFalse_Callback) { + ASSERT_DEATH( + InstallExceptionHandlerAndCrash(true, // install_filter + false, // filter_return_value + true, // install_callback + false), // callback_return_value + std::string(kStartOfLine) + + std::string(kFilterReturnsFalse) + + std::string(kEndOfLine)); +} + +TEST(ExceptionHandlerCallbacks, No_Filter_No_Callback) { + ASSERT_DEATH( + InstallExceptionHandlerAndCrash(false, // install_filter + true, // filter_return_value + false, // install_callback + false), // callback_return_value + std::string(kStartOfLine) + + std::string(kEndOfLine)); +} + +TEST(ExceptionHandlerCallbacks, No_Filter_Callback) { + ASSERT_DEATH( + InstallExceptionHandlerAndCrash(false, // install_filter + true, // filter_return_value + true, // install_callback + false), // callback_return_value + std::string(kStartOfLine) + + std::string(kCallbackReturnsFalse) + + std::string(kEndOfLine)); +} + + +TEST(ExceptionHandlerNesting, Skip_From_Inner_Filter) { + wchar_t temp_path[MAX_PATH] = { '\0' }; + GetTempPath(MAX_PATH, temp_path); + + ASSERT_TRUE(DoesPathExist(temp_path)); + google_breakpad::ExceptionHandler exc( + temp_path, + &CrashHandlerFilter<true>, + &MinidumpWrittenCallback<false>, + NULL, // callback_context + google_breakpad::ExceptionHandler::HANDLER_EXCEPTION); + + ASSERT_DEATH( + InstallExceptionHandlerAndCrash(true, // install_filter + false, // filter_return_value + true, // install_callback + true), // callback_return_value + std::string(kStartOfLine) + + std::string(kFilterReturnsFalse) + // inner filter + std::string(kFilterReturnsTrue) + // outer filter + std::string(kCallbackReturnsFalse) + // outer callback + std::string(kEndOfLine)); +} + +TEST(ExceptionHandlerNesting, Skip_From_Inner_Callback) { + wchar_t temp_path[MAX_PATH] = { '\0' }; + GetTempPath(MAX_PATH, temp_path); + + ASSERT_TRUE(DoesPathExist(temp_path)); + google_breakpad::ExceptionHandler exc( + temp_path, + &CrashHandlerFilter<true>, + &MinidumpWrittenCallback<false>, + NULL, // callback_context + google_breakpad::ExceptionHandler::HANDLER_EXCEPTION); + + ASSERT_DEATH( + InstallExceptionHandlerAndCrash(true, // install_filter + true, // filter_return_value + true, // install_callback + false), // callback_return_value + std::string(kStartOfLine) + + std::string(kFilterReturnsTrue) + // inner filter + std::string(kCallbackReturnsFalse) + // inner callback + std::string(kFilterReturnsTrue) + // outer filter + std::string(kCallbackReturnsFalse) + // outer callback + std::string(kEndOfLine)); +} + +TEST(ExceptionHandlerNesting, Handled_By_Inner_Handler) { + wchar_t temp_path[MAX_PATH] = { '\0' }; + GetTempPath(MAX_PATH, temp_path); + + ASSERT_TRUE(DoesPathExist(temp_path)); + google_breakpad::ExceptionHandler exc( + temp_path, + &CrashHandlerFilter<true>, + &MinidumpWrittenCallback<true>, + NULL, // callback_context + google_breakpad::ExceptionHandler::HANDLER_EXCEPTION); + + ASSERT_DEATH( + InstallExceptionHandlerAndCrash(true, // install_filter + true, // filter_return_value + true, // install_callback + true), // callback_return_value + std::string(kStartOfLine) + + std::string(kFilterReturnsTrue) + // inner filter + std::string(kCallbackReturnsTrue) + // inner callback + std::string(kEndOfLine)); +} + +} // namespace diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/exception_handler_test.cc b/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/exception_handler_test.cc new file mode 100644 index 000000000..55275323e --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/exception_handler_test.cc @@ -0,0 +1,501 @@ +// Copyright 2009, 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/windows/unittests/exception_handler_test.h" + +#include <windows.h> +#include <dbghelp.h> +#include <strsafe.h> +#include <objbase.h> +#include <shellapi.h> + +#include <string> + +#include "breakpad_googletest_includes.h" +#include "client/windows/crash_generation/crash_generation_server.h" +#include "client/windows/handler/exception_handler.h" +#include "client/windows/unittests/dump_analysis.h" // NOLINT +#include "common/windows/string_utils-inl.h" +#include "google_breakpad/processor/minidump.h" + +namespace testing { + +DisableExceptionHandlerInScope::DisableExceptionHandlerInScope() { + catch_exceptions_ = GTEST_FLAG(catch_exceptions); + GTEST_FLAG(catch_exceptions) = false; +} + +DisableExceptionHandlerInScope::~DisableExceptionHandlerInScope() { + GTEST_FLAG(catch_exceptions) = catch_exceptions_; +} + +} // namespace testing + +namespace { + +using std::wstring; +using namespace google_breakpad; + +const wchar_t kPipeName[] = L"\\\\.\\pipe\\BreakpadCrashTest\\TestCaseServer"; +const char kSuccessIndicator[] = "success"; +const char kFailureIndicator[] = "failure"; + +const MINIDUMP_TYPE kFullDumpType = static_cast<MINIDUMP_TYPE>( + MiniDumpWithFullMemory | // Full memory from process. + MiniDumpWithProcessThreadData | // Get PEB and TEB. + MiniDumpWithHandleData); // Get all handle information. + +class ExceptionHandlerTest : public ::testing::Test { + protected: + // Member variable for each test that they can use + // for temporary storage. + TCHAR temp_path_[MAX_PATH]; + + // Actually constructs a temp path name. + virtual void SetUp(); + + // Deletes temporary files. + virtual void TearDown(); + + void DoCrashInvalidParameter(); + void DoCrashPureVirtualCall(); + + // Utility function to test for a path's existence. + static BOOL DoesPathExist(const TCHAR *path_name); + + // Client callback. + static void ClientDumpCallback( + void *dump_context, + const google_breakpad::ClientInfo *client_info, + const std::wstring *dump_path); + + static bool DumpCallback(const wchar_t* dump_path, + const wchar_t* minidump_id, + void* context, + EXCEPTION_POINTERS* exinfo, + MDRawAssertionInfo* assertion, + bool succeeded); + + static std::wstring dump_file; + static std::wstring full_dump_file; +}; + +std::wstring ExceptionHandlerTest::dump_file; +std::wstring ExceptionHandlerTest::full_dump_file; + +void ExceptionHandlerTest::SetUp() { + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + TCHAR temp_path[MAX_PATH] = { '\0' }; + TCHAR test_name_wide[MAX_PATH] = { '\0' }; + // We want the temporary directory to be what the OS returns + // to us, + the test case name. + GetTempPath(MAX_PATH, temp_path); + // THe test case name is exposed to use as a c-style string, + // But we might be working in UNICODE here on Windows. + int dwRet = MultiByteToWideChar(CP_ACP, 0, test_info->name(), + strlen(test_info->name()), + test_name_wide, + MAX_PATH); + if (!dwRet) { + assert(false); + } + StringCchPrintfW(temp_path_, MAX_PATH, L"%s%s", temp_path, test_name_wide); + CreateDirectory(temp_path_, NULL); +} + +void ExceptionHandlerTest::TearDown() { + if (!dump_file.empty()) { + ::DeleteFile(dump_file.c_str()); + dump_file = L""; + } + if (!full_dump_file.empty()) { + ::DeleteFile(full_dump_file.c_str()); + full_dump_file = L""; + } +} + +BOOL ExceptionHandlerTest::DoesPathExist(const TCHAR *path_name) { + DWORD flags = GetFileAttributes(path_name); + if (flags == INVALID_FILE_ATTRIBUTES) { + return FALSE; + } + return TRUE; +} + +// static +void ExceptionHandlerTest::ClientDumpCallback( + void *dump_context, + const google_breakpad::ClientInfo *client_info, + const wstring *dump_path) { + dump_file = *dump_path; + // Create the full dump file name from the dump path. + full_dump_file = dump_file.substr(0, dump_file.length() - 4) + L"-full.dmp"; +} + +// static +bool ExceptionHandlerTest::DumpCallback(const wchar_t* dump_path, + const wchar_t* minidump_id, + void* context, + EXCEPTION_POINTERS* exinfo, + MDRawAssertionInfo* assertion, + bool succeeded) { + dump_file = dump_path; + dump_file += L"\\"; + dump_file += minidump_id; + dump_file += L".dmp"; + return succeeded; +} + +void ExceptionHandlerTest::DoCrashInvalidParameter() { + google_breakpad::ExceptionHandler *exc = + new google_breakpad::ExceptionHandler( + temp_path_, NULL, NULL, NULL, + google_breakpad::ExceptionHandler::HANDLER_INVALID_PARAMETER, + kFullDumpType, kPipeName, NULL); + + // Disable the message box for assertions + _CrtSetReportMode(_CRT_ASSERT, 0); + + // Although this is executing in the child process of the death test, + // if it's not true we'll still get an error rather than the crash + // being expected. + ASSERT_TRUE(exc->IsOutOfProcess()); + printf(NULL); +} + + +struct PureVirtualCallBase { + PureVirtualCallBase() { + // We have to reinterpret so the linker doesn't get confused because the + // method isn't defined. + reinterpret_cast<PureVirtualCallBase*>(this)->PureFunction(); + } + virtual ~PureVirtualCallBase() {} + virtual void PureFunction() const = 0; +}; +struct PureVirtualCall : public PureVirtualCallBase { + PureVirtualCall() { PureFunction(); } + virtual void PureFunction() const {} +}; + +void ExceptionHandlerTest::DoCrashPureVirtualCall() { + google_breakpad::ExceptionHandler *exc = + new google_breakpad::ExceptionHandler( + temp_path_, NULL, NULL, NULL, + google_breakpad::ExceptionHandler::HANDLER_PURECALL, + kFullDumpType, kPipeName, NULL); + + // Disable the message box for assertions + _CrtSetReportMode(_CRT_ASSERT, 0); + + // Although this is executing in the child process of the death test, + // if it's not true we'll still get an error rather than the crash + // being expected. + ASSERT_TRUE(exc->IsOutOfProcess()); + + // Create a new frame to ensure PureVirtualCall is not optimized to some + // other line in this function. + { + PureVirtualCall instance; + } +} + +// This test validates that the minidump is written correctly. +TEST_F(ExceptionHandlerTest, InvalidParameterMiniDumpTest) { + ASSERT_TRUE(DoesPathExist(temp_path_)); + + // Call with a bad argument + ASSERT_TRUE(DoesPathExist(temp_path_)); + wstring dump_path(temp_path_); + google_breakpad::CrashGenerationServer server( + kPipeName, NULL, NULL, NULL, ClientDumpCallback, NULL, NULL, NULL, NULL, + NULL, true, &dump_path); + + ASSERT_TRUE(dump_file.empty() && full_dump_file.empty()); + + // This HAS to be EXPECT_, because when this test case is executed in the + // child process, the server registration will fail due to the named pipe + // being the same. + EXPECT_TRUE(server.Start()); + EXPECT_EXIT(DoCrashInvalidParameter(), ::testing::ExitedWithCode(0), ""); + ASSERT_TRUE(!dump_file.empty() && !full_dump_file.empty()); + ASSERT_TRUE(DoesPathExist(dump_file.c_str())); + + // Verify the dump for infos. + DumpAnalysis mini(dump_file); + DumpAnalysis full(full_dump_file); + + // The dump should have all of these streams. + EXPECT_TRUE(mini.HasStream(ThreadListStream)); + EXPECT_TRUE(full.HasStream(ThreadListStream)); + EXPECT_TRUE(mini.HasStream(ModuleListStream)); + EXPECT_TRUE(full.HasStream(ModuleListStream)); + EXPECT_TRUE(mini.HasStream(ExceptionStream)); + EXPECT_TRUE(full.HasStream(ExceptionStream)); + EXPECT_TRUE(mini.HasStream(SystemInfoStream)); + EXPECT_TRUE(full.HasStream(SystemInfoStream)); + EXPECT_TRUE(mini.HasStream(MiscInfoStream)); + EXPECT_TRUE(full.HasStream(MiscInfoStream)); + EXPECT_TRUE(mini.HasStream(HandleDataStream)); + EXPECT_TRUE(full.HasStream(HandleDataStream)); + + // We expect PEB and TEBs in this dump. + EXPECT_TRUE(mini.HasTebs() || full.HasTebs()); + EXPECT_TRUE(mini.HasPeb() || full.HasPeb()); + + // Minidump should have a memory listing, but no 64-bit memory. + EXPECT_TRUE(mini.HasStream(MemoryListStream)); + EXPECT_FALSE(mini.HasStream(Memory64ListStream)); + + EXPECT_FALSE(full.HasStream(MemoryListStream)); + EXPECT_TRUE(full.HasStream(Memory64ListStream)); + + // This is the only place we don't use OR because we want both not + // to have the streams. + EXPECT_FALSE(mini.HasStream(ThreadExListStream)); + EXPECT_FALSE(full.HasStream(ThreadExListStream)); + EXPECT_FALSE(mini.HasStream(CommentStreamA)); + EXPECT_FALSE(full.HasStream(CommentStreamA)); + EXPECT_FALSE(mini.HasStream(CommentStreamW)); + EXPECT_FALSE(full.HasStream(CommentStreamW)); + EXPECT_FALSE(mini.HasStream(FunctionTableStream)); + EXPECT_FALSE(full.HasStream(FunctionTableStream)); + EXPECT_FALSE(mini.HasStream(MemoryInfoListStream)); + EXPECT_FALSE(full.HasStream(MemoryInfoListStream)); + EXPECT_FALSE(mini.HasStream(ThreadInfoListStream)); + EXPECT_FALSE(full.HasStream(ThreadInfoListStream)); + EXPECT_FALSE(mini.HasStream(HandleOperationListStream)); + EXPECT_FALSE(full.HasStream(HandleOperationListStream)); + EXPECT_FALSE(mini.HasStream(TokenStream)); + EXPECT_FALSE(full.HasStream(TokenStream)); +} + + +// This test validates that the minidump is written correctly. +TEST_F(ExceptionHandlerTest, PureVirtualCallMiniDumpTest) { + ASSERT_TRUE(DoesPathExist(temp_path_)); + + // Call with a bad argument + ASSERT_TRUE(DoesPathExist(temp_path_)); + wstring dump_path(temp_path_); + google_breakpad::CrashGenerationServer server( + kPipeName, NULL, NULL, NULL, ClientDumpCallback, NULL, NULL, NULL, NULL, + NULL, true, &dump_path); + + ASSERT_TRUE(dump_file.empty() && full_dump_file.empty()); + + // This HAS to be EXPECT_, because when this test case is executed in the + // child process, the server registration will fail due to the named pipe + // being the same. + EXPECT_TRUE(server.Start()); + EXPECT_EXIT(DoCrashPureVirtualCall(), ::testing::ExitedWithCode(0), ""); + ASSERT_TRUE(!dump_file.empty() && !full_dump_file.empty()); + ASSERT_TRUE(DoesPathExist(dump_file.c_str())); + + // Verify the dump for infos. + DumpAnalysis mini(dump_file); + DumpAnalysis full(full_dump_file); + + // The dump should have all of these streams. + EXPECT_TRUE(mini.HasStream(ThreadListStream)); + EXPECT_TRUE(full.HasStream(ThreadListStream)); + EXPECT_TRUE(mini.HasStream(ModuleListStream)); + EXPECT_TRUE(full.HasStream(ModuleListStream)); + EXPECT_TRUE(mini.HasStream(ExceptionStream)); + EXPECT_TRUE(full.HasStream(ExceptionStream)); + EXPECT_TRUE(mini.HasStream(SystemInfoStream)); + EXPECT_TRUE(full.HasStream(SystemInfoStream)); + EXPECT_TRUE(mini.HasStream(MiscInfoStream)); + EXPECT_TRUE(full.HasStream(MiscInfoStream)); + EXPECT_TRUE(mini.HasStream(HandleDataStream)); + EXPECT_TRUE(full.HasStream(HandleDataStream)); + + // We expect PEB and TEBs in this dump. + EXPECT_TRUE(mini.HasTebs() || full.HasTebs()); + EXPECT_TRUE(mini.HasPeb() || full.HasPeb()); + + // Minidump should have a memory listing, but no 64-bit memory. + EXPECT_TRUE(mini.HasStream(MemoryListStream)); + EXPECT_FALSE(mini.HasStream(Memory64ListStream)); + + EXPECT_FALSE(full.HasStream(MemoryListStream)); + EXPECT_TRUE(full.HasStream(Memory64ListStream)); + + // This is the only place we don't use OR because we want both not + // to have the streams. + EXPECT_FALSE(mini.HasStream(ThreadExListStream)); + EXPECT_FALSE(full.HasStream(ThreadExListStream)); + EXPECT_FALSE(mini.HasStream(CommentStreamA)); + EXPECT_FALSE(full.HasStream(CommentStreamA)); + EXPECT_FALSE(mini.HasStream(CommentStreamW)); + EXPECT_FALSE(full.HasStream(CommentStreamW)); + EXPECT_FALSE(mini.HasStream(FunctionTableStream)); + EXPECT_FALSE(full.HasStream(FunctionTableStream)); + EXPECT_FALSE(mini.HasStream(MemoryInfoListStream)); + EXPECT_FALSE(full.HasStream(MemoryInfoListStream)); + EXPECT_FALSE(mini.HasStream(ThreadInfoListStream)); + EXPECT_FALSE(full.HasStream(ThreadInfoListStream)); + EXPECT_FALSE(mini.HasStream(HandleOperationListStream)); + EXPECT_FALSE(full.HasStream(HandleOperationListStream)); + EXPECT_FALSE(mini.HasStream(TokenStream)); + EXPECT_FALSE(full.HasStream(TokenStream)); +} + +// Test that writing a minidump produces a valid minidump containing +// some expected structures. +TEST_F(ExceptionHandlerTest, WriteMinidumpTest) { + ExceptionHandler handler(temp_path_, + NULL, + DumpCallback, + NULL, + ExceptionHandler::HANDLER_ALL); + + // Disable GTest SEH handler + testing::DisableExceptionHandlerInScope disable_exception_handler; + + ASSERT_TRUE(handler.WriteMinidump()); + ASSERT_FALSE(dump_file.empty()); + + string minidump_filename; + ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(dump_file, + &minidump_filename)); + + // Read the minidump and verify some info. + Minidump minidump(minidump_filename); + ASSERT_TRUE(minidump.Read()); + // TODO(ted): more comprehensive tests... +} + +// Test that an additional memory region can be included in the minidump. +TEST_F(ExceptionHandlerTest, AdditionalMemory) { + SYSTEM_INFO si; + GetSystemInfo(&si); + const uint32_t kMemorySize = si.dwPageSize; + + // Get some heap memory. + uint8_t* memory = new uint8_t[kMemorySize]; + const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory); + ASSERT_TRUE(memory); + + // Stick some data into the memory so the contents can be verified. + for (uint32_t i = 0; i < kMemorySize; ++i) { + memory[i] = i % 255; + } + + ExceptionHandler handler(temp_path_, + NULL, + DumpCallback, + NULL, + ExceptionHandler::HANDLER_ALL); + + // Disable GTest SEH handler + testing::DisableExceptionHandlerInScope disable_exception_handler; + + // Add the memory region to the list of memory to be included. + handler.RegisterAppMemory(memory, kMemorySize); + ASSERT_TRUE(handler.WriteMinidump()); + ASSERT_FALSE(dump_file.empty()); + + string minidump_filename; + ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(dump_file, + &minidump_filename)); + + // Read the minidump. Ensure that the memory region is present + Minidump minidump(minidump_filename); + ASSERT_TRUE(minidump.Read()); + + MinidumpMemoryList* dump_memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(dump_memory_list); + const MinidumpMemoryRegion* region = + dump_memory_list->GetMemoryRegionForAddress(kMemoryAddress); + ASSERT_TRUE(region); + + EXPECT_EQ(kMemoryAddress, region->GetBase()); + EXPECT_EQ(kMemorySize, region->GetSize()); + + // Verify memory contents. + EXPECT_EQ(0, memcmp(region->GetMemory(), memory, kMemorySize)); + + delete[] memory; +} + +// Test that a memory region that was previously registered +// can be unregistered. +TEST_F(ExceptionHandlerTest, AdditionalMemoryRemove) { + SYSTEM_INFO si; + GetSystemInfo(&si); + const uint32_t kMemorySize = si.dwPageSize; + + // Get some heap memory. + uint8_t* memory = new uint8_t[kMemorySize]; + const uintptr_t kMemoryAddress = reinterpret_cast<uintptr_t>(memory); + ASSERT_TRUE(memory); + + // Stick some data into the memory so the contents can be verified. + for (uint32_t i = 0; i < kMemorySize; ++i) { + memory[i] = i % 255; + } + + ExceptionHandler handler(temp_path_, + NULL, + DumpCallback, + NULL, + ExceptionHandler::HANDLER_ALL); + + // Disable GTest SEH handler + testing::DisableExceptionHandlerInScope disable_exception_handler; + + // Add the memory region to the list of memory to be included. + handler.RegisterAppMemory(memory, kMemorySize); + + // ...and then remove it + handler.UnregisterAppMemory(memory); + + ASSERT_TRUE(handler.WriteMinidump()); + ASSERT_FALSE(dump_file.empty()); + + string minidump_filename; + ASSERT_TRUE(WindowsStringUtils::safe_wcstombs(dump_file, + &minidump_filename)); + + // Read the minidump. Ensure that the memory region is not present. + Minidump minidump(minidump_filename); + ASSERT_TRUE(minidump.Read()); + + MinidumpMemoryList* dump_memory_list = minidump.GetMemoryList(); + ASSERT_TRUE(dump_memory_list); + const MinidumpMemoryRegion* region = + dump_memory_list->GetMemoryRegionForAddress(kMemoryAddress); + EXPECT_FALSE(region); + + delete[] memory; +} + +} // namespace diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/exception_handler_test.h b/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/exception_handler_test.h new file mode 100644 index 000000000..ef973e539 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/exception_handler_test.h @@ -0,0 +1,61 @@ +// Copyright 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_WINDOWS_UNITTESTS_EXCEPTION_HANDLER_TEST_H_ +#define CLIENT_WINDOWS_UNITTESTS_EXCEPTION_HANDLER_TEST_H_ + +namespace testing { + +// By default, GTest (on Windows) installs a SEH filter (and a handler) before +// starting to run all the tests in order to avoid test interruptions is some +// of the tests are crashing. Unfortunately, this functionality prevents the +// execution to reach the UnhandledExceptionFilter installed by Google-Breakpad +// ExceptionHandler so in order to test the Google-Breakpad exception handling +// code the exception handling done by GTest must be disabled. +// Usage: +// +// google_breakpad::ExceptionHandler exc(...); +// +// // Disable GTest SEH handler +// testing::DisableExceptionHandlerInScope disable_exception_handler; +// ... +// ASSERT_DEATH( ... some crash ...); +// +class DisableExceptionHandlerInScope { + public: + DisableExceptionHandlerInScope(); + ~DisableExceptionHandlerInScope(); + + private: + bool catch_exceptions_; +}; + +} // namespace testing + +#endif // CLIENT_WINDOWS_UNITTESTS_EXCEPTION_HANDLER_TEST_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/minidump_test.cc b/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/minidump_test.cc new file mode 100644 index 000000000..8d2d726c4 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/minidump_test.cc @@ -0,0 +1,333 @@ +// Copyright (c) 2010, 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 <windows.h> +#include <objbase.h> +#include <dbghelp.h> + +#include "client/windows/crash_generation/minidump_generator.h" +#include "client/windows/unittests/dump_analysis.h" // NOLINT + +#include "gtest/gtest.h" + +namespace { + +// Minidump with stacks, PEB, TEB, and unloaded module list. +const MINIDUMP_TYPE kSmallDumpType = static_cast<MINIDUMP_TYPE>( + MiniDumpWithProcessThreadData | // Get PEB and TEB. + MiniDumpWithUnloadedModules); // Get unloaded modules when available. + +// Minidump with all of the above, plus memory referenced from stack. +const MINIDUMP_TYPE kLargerDumpType = static_cast<MINIDUMP_TYPE>( + MiniDumpWithProcessThreadData | // Get PEB and TEB. + MiniDumpWithUnloadedModules | // Get unloaded modules when available. + MiniDumpWithIndirectlyReferencedMemory); // Get memory referenced by stack. + +// Large dump with all process memory. +const MINIDUMP_TYPE kFullDumpType = static_cast<MINIDUMP_TYPE>( + MiniDumpWithFullMemory | // Full memory from process. + MiniDumpWithProcessThreadData | // Get PEB and TEB. + MiniDumpWithHandleData | // Get all handle information. + MiniDumpWithUnloadedModules); // Get unloaded modules when available. + +class MinidumpTest: public testing::Test { + public: + MinidumpTest() { + wchar_t temp_dir_path[ MAX_PATH ] = {0}; + ::GetTempPath(MAX_PATH, temp_dir_path); + dump_path_ = temp_dir_path; + } + + virtual void SetUp() { + // Make sure URLMon isn't loaded into our process. + ASSERT_EQ(NULL, ::GetModuleHandle(L"urlmon.dll")); + + // Then load and unload it to ensure we have something to + // stock the unloaded module list with. + HMODULE urlmon = ::LoadLibrary(L"urlmon.dll"); + ASSERT_TRUE(urlmon != NULL); + ASSERT_TRUE(::FreeLibrary(urlmon)); + } + + virtual void TearDown() { + if (!dump_file_.empty()) { + ::DeleteFile(dump_file_.c_str()); + dump_file_ = L""; + } + if (!full_dump_file_.empty()) { + ::DeleteFile(full_dump_file_.c_str()); + full_dump_file_ = L""; + } + } + + bool WriteDump(ULONG flags) { + using google_breakpad::MinidumpGenerator; + + // Fake exception is access violation on write to this. + EXCEPTION_RECORD ex_record = { + STATUS_ACCESS_VIOLATION, // ExceptionCode + 0, // ExceptionFlags + NULL, // ExceptionRecord; + reinterpret_cast<void*>(0xCAFEBABE), // ExceptionAddress; + 2, // NumberParameters; + { EXCEPTION_WRITE_FAULT, reinterpret_cast<ULONG_PTR>(this) } + }; + CONTEXT ctx_record = {}; + EXCEPTION_POINTERS ex_ptrs = { + &ex_record, + &ctx_record, + }; + + MinidumpGenerator generator(dump_path_, + ::GetCurrentProcess(), + ::GetCurrentProcessId(), + ::GetCurrentThreadId(), + ::GetCurrentThreadId(), + &ex_ptrs, + NULL, + static_cast<MINIDUMP_TYPE>(flags), + TRUE); + generator.GenerateDumpFile(&dump_file_); + generator.GenerateFullDumpFile(&full_dump_file_); + // And write a dump + bool result = generator.WriteMinidump(); + return result == TRUE; + } + + protected: + std::wstring dump_file_; + std::wstring full_dump_file_; + + std::wstring dump_path_; +}; + +// We need to be able to get file information from Windows +bool HasFileInfo(const std::wstring& file_path) { + DWORD dummy; + const wchar_t* path = file_path.c_str(); + DWORD length = ::GetFileVersionInfoSize(path, &dummy); + if (length == 0) + return NULL; + + void* data = calloc(length, 1); + if (!data) + return false; + + if (!::GetFileVersionInfo(path, dummy, length, data)) { + free(data); + return false; + } + + void* translate = NULL; + UINT page_count; + BOOL query_result = VerQueryValue( + data, + L"\\VarFileInfo\\Translation", + static_cast<void**>(&translate), + &page_count); + + free(data); + if (query_result && translate) { + return true; + } else { + return false; + } +} + +TEST_F(MinidumpTest, Version) { + // Loads DbgHelp.dll in process + ImagehlpApiVersion(); + + HMODULE dbg_help = ::GetModuleHandle(L"dbghelp.dll"); + ASSERT_TRUE(dbg_help != NULL); + + wchar_t dbg_help_file[1024] = {}; + ASSERT_TRUE(::GetModuleFileName(dbg_help, + dbg_help_file, + sizeof(dbg_help_file) / + sizeof(*dbg_help_file))); + ASSERT_TRUE(HasFileInfo(std::wstring(dbg_help_file)) != NULL); + +// LOG(INFO) << "DbgHelp.dll version: " << file_info->file_version(); +} + +TEST_F(MinidumpTest, Normal) { + EXPECT_TRUE(WriteDump(MiniDumpNormal)); + DumpAnalysis mini(dump_file_); + + // We expect threads, modules and some memory. + EXPECT_TRUE(mini.HasStream(ThreadListStream)); + EXPECT_TRUE(mini.HasStream(ModuleListStream)); + EXPECT_TRUE(mini.HasStream(MemoryListStream)); + EXPECT_TRUE(mini.HasStream(ExceptionStream)); + EXPECT_TRUE(mini.HasStream(SystemInfoStream)); + EXPECT_TRUE(mini.HasStream(MiscInfoStream)); + + EXPECT_FALSE(mini.HasStream(ThreadExListStream)); + EXPECT_FALSE(mini.HasStream(Memory64ListStream)); + EXPECT_FALSE(mini.HasStream(CommentStreamA)); + EXPECT_FALSE(mini.HasStream(CommentStreamW)); + EXPECT_FALSE(mini.HasStream(HandleDataStream)); + EXPECT_FALSE(mini.HasStream(FunctionTableStream)); + EXPECT_FALSE(mini.HasStream(UnloadedModuleListStream)); + EXPECT_FALSE(mini.HasStream(MemoryInfoListStream)); + EXPECT_FALSE(mini.HasStream(ThreadInfoListStream)); + EXPECT_FALSE(mini.HasStream(HandleOperationListStream)); + EXPECT_FALSE(mini.HasStream(TokenStream)); + + // We expect no PEB nor TEBs in this dump. + EXPECT_FALSE(mini.HasTebs()); + EXPECT_FALSE(mini.HasPeb()); + + // We expect no off-stack memory in this dump. + EXPECT_FALSE(mini.HasMemory(this)); +} + +TEST_F(MinidumpTest, SmallDump) { + ASSERT_TRUE(WriteDump(kSmallDumpType)); + DumpAnalysis mini(dump_file_); + + EXPECT_TRUE(mini.HasStream(ThreadListStream)); + EXPECT_TRUE(mini.HasStream(ModuleListStream)); + EXPECT_TRUE(mini.HasStream(MemoryListStream)); + EXPECT_TRUE(mini.HasStream(ExceptionStream)); + EXPECT_TRUE(mini.HasStream(SystemInfoStream)); + EXPECT_TRUE(mini.HasStream(UnloadedModuleListStream)); + EXPECT_TRUE(mini.HasStream(MiscInfoStream)); + + // We expect PEB and TEBs in this dump. + EXPECT_TRUE(mini.HasTebs()); + EXPECT_TRUE(mini.HasPeb()); + + EXPECT_FALSE(mini.HasStream(ThreadExListStream)); + EXPECT_FALSE(mini.HasStream(Memory64ListStream)); + EXPECT_FALSE(mini.HasStream(CommentStreamA)); + EXPECT_FALSE(mini.HasStream(CommentStreamW)); + EXPECT_FALSE(mini.HasStream(HandleDataStream)); + EXPECT_FALSE(mini.HasStream(FunctionTableStream)); + EXPECT_FALSE(mini.HasStream(MemoryInfoListStream)); + EXPECT_FALSE(mini.HasStream(ThreadInfoListStream)); + EXPECT_FALSE(mini.HasStream(HandleOperationListStream)); + EXPECT_FALSE(mini.HasStream(TokenStream)); + + // We expect no off-stack memory in this dump. + EXPECT_FALSE(mini.HasMemory(this)); +} + +TEST_F(MinidumpTest, LargerDump) { + ASSERT_TRUE(WriteDump(kLargerDumpType)); + DumpAnalysis mini(dump_file_); + + // The dump should have all of these streams. + EXPECT_TRUE(mini.HasStream(ThreadListStream)); + EXPECT_TRUE(mini.HasStream(ModuleListStream)); + EXPECT_TRUE(mini.HasStream(MemoryListStream)); + EXPECT_TRUE(mini.HasStream(ExceptionStream)); + EXPECT_TRUE(mini.HasStream(SystemInfoStream)); + EXPECT_TRUE(mini.HasStream(UnloadedModuleListStream)); + EXPECT_TRUE(mini.HasStream(MiscInfoStream)); + + // We expect memory referenced by stack in this dump. + EXPECT_TRUE(mini.HasMemory(this)); + + // We expect PEB and TEBs in this dump. + EXPECT_TRUE(mini.HasTebs()); + EXPECT_TRUE(mini.HasPeb()); + + EXPECT_FALSE(mini.HasStream(ThreadExListStream)); + EXPECT_FALSE(mini.HasStream(Memory64ListStream)); + EXPECT_FALSE(mini.HasStream(CommentStreamA)); + EXPECT_FALSE(mini.HasStream(CommentStreamW)); + EXPECT_FALSE(mini.HasStream(HandleDataStream)); + EXPECT_FALSE(mini.HasStream(FunctionTableStream)); + EXPECT_FALSE(mini.HasStream(MemoryInfoListStream)); + EXPECT_FALSE(mini.HasStream(ThreadInfoListStream)); + EXPECT_FALSE(mini.HasStream(HandleOperationListStream)); + EXPECT_FALSE(mini.HasStream(TokenStream)); +} + +TEST_F(MinidumpTest, FullDump) { + ASSERT_TRUE(WriteDump(kFullDumpType)); + ASSERT_TRUE(dump_file_ != L""); + ASSERT_TRUE(full_dump_file_ != L""); + DumpAnalysis mini(dump_file_); + DumpAnalysis full(full_dump_file_); + + // Either dumps can contain part of the information. + + // The dump should have all of these streams. + EXPECT_TRUE(mini.HasStream(ThreadListStream)); + EXPECT_TRUE(full.HasStream(ThreadListStream)); + EXPECT_TRUE(mini.HasStream(ModuleListStream)); + EXPECT_TRUE(full.HasStream(ModuleListStream)); + EXPECT_TRUE(mini.HasStream(ExceptionStream)); + EXPECT_TRUE(full.HasStream(ExceptionStream)); + EXPECT_TRUE(mini.HasStream(SystemInfoStream)); + EXPECT_TRUE(full.HasStream(SystemInfoStream)); + EXPECT_TRUE(mini.HasStream(UnloadedModuleListStream)); + EXPECT_TRUE(full.HasStream(UnloadedModuleListStream)); + EXPECT_TRUE(mini.HasStream(MiscInfoStream)); + EXPECT_TRUE(full.HasStream(MiscInfoStream)); + EXPECT_TRUE(mini.HasStream(HandleDataStream)); + EXPECT_TRUE(full.HasStream(HandleDataStream)); + + // We expect memory referenced by stack in this dump. + EXPECT_FALSE(mini.HasMemory(this)); + EXPECT_TRUE(full.HasMemory(this)); + + // We expect PEB and TEBs in this dump. + EXPECT_TRUE(mini.HasTebs() || full.HasTebs()); + EXPECT_TRUE(mini.HasPeb() || full.HasPeb()); + + EXPECT_TRUE(mini.HasStream(MemoryListStream)); + EXPECT_TRUE(full.HasStream(Memory64ListStream)); + EXPECT_FALSE(mini.HasStream(Memory64ListStream)); + EXPECT_FALSE(full.HasStream(MemoryListStream)); + + // This is the only place we don't use OR because we want both not + // to have the streams. + EXPECT_FALSE(mini.HasStream(ThreadExListStream)); + EXPECT_FALSE(full.HasStream(ThreadExListStream)); + EXPECT_FALSE(mini.HasStream(CommentStreamA)); + EXPECT_FALSE(full.HasStream(CommentStreamA)); + EXPECT_FALSE(mini.HasStream(CommentStreamW)); + EXPECT_FALSE(full.HasStream(CommentStreamW)); + EXPECT_FALSE(mini.HasStream(FunctionTableStream)); + EXPECT_FALSE(full.HasStream(FunctionTableStream)); + EXPECT_FALSE(mini.HasStream(MemoryInfoListStream)); + EXPECT_FALSE(full.HasStream(MemoryInfoListStream)); + EXPECT_FALSE(mini.HasStream(ThreadInfoListStream)); + EXPECT_FALSE(full.HasStream(ThreadInfoListStream)); + EXPECT_FALSE(mini.HasStream(HandleOperationListStream)); + EXPECT_FALSE(full.HasStream(HandleOperationListStream)); + EXPECT_FALSE(mini.HasStream(TokenStream)); + EXPECT_FALSE(full.HasStream(TokenStream)); +} + +} // namespace diff --git a/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/testing.gyp b/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/testing.gyp new file mode 100644 index 000000000..141d0ed0c --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/client/windows/unittests/testing.gyp @@ -0,0 +1,83 @@ +# Copyright 2010 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. + +{ + 'includes': [ + '../../../build/common.gypi', + ], + 'target_defaults': { + }, + 'targets': [ + { + 'target_name': 'gtest', + 'type': 'static_library', + 'include_dirs': [ + '<(DEPTH)/testing/include', + '<(DEPTH)/testing/gtest', + '<(DEPTH)/testing/gtest/include', + ], + 'sources': [ + '<(DEPTH)/testing/gtest/src/gtest-all.cc', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '<(DEPTH)/testing/include', + '<(DEPTH)/testing/gtest/include', + ], + # Visual C++ implements variadic templates strangely, and + # VC++2012 broke Google Test by lowering this value. See + # http://stackoverflow.com/questions/12558327/google-test-in-visual-studio-2012 + 'defines': ['_VARIADIC_MAX=10'], + }, + 'defines': ['_VARIADIC_MAX=10'], + }, + { + 'target_name': 'gmock', + 'type': 'static_library', + 'include_dirs': [ + '<(DEPTH)/testing/include', + '<(DEPTH)/testing/', + '<(DEPTH)/testing/gtest', + '<(DEPTH)/testing/gtest/include', + ], + 'sources': [ + '<(DEPTH)/testing/src/gmock-all.cc', + '<(DEPTH)/testing/src/gmock_main.cc', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '<(DEPTH)/testing/include', + '<(DEPTH)/testing/gtest/include', + ], + 'defines': ['_VARIADIC_MAX=10'], + }, + 'defines': ['_VARIADIC_MAX=10'], + }, + + ], +} |