diff --git a/src/client/mac/Framework/Breakpad.h b/src/client/mac/Framework/Breakpad.h index 06ce5ae8..4b5357d2 100644 --- a/src/client/mac/Framework/Breakpad.h +++ b/src/client/mac/Framework/Breakpad.h @@ -61,26 +61,33 @@ extern "C" { // 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_DISPLAY "BreakpadProductDisplay" #define BREAKPAD_PRODUCT "BreakpadProduct" -#define BREAKPAD_VENDOR "BreakpadVendor" +#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_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_LOGFILE_KEY_PREFIX "BreakpadAppLogFile" #define BREAKPAD_EMAIL "BreakpadEmail" #define BREAKPAD_REQUEST_COMMENTS "BreakpadRequestComments" #define BREAKPAD_COMMENTS "BreakpadComments" +#define BREAKPAD_SERVER_TYPE "BreakpadServerType" +// TODO(nealsid) find a better way to support server-specific +// parameters without having to rebuild Breakpad +#define BREAKPAD_BUILD_ID "BreakpadBuildID" +// 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" // 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. @@ -91,9 +98,9 @@ typedef bool (*BreakpadFilterCallback)(int exception_type, int exception_code, mach_port_t crashing_thread); -// 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. +// 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: @@ -153,15 +160,27 @@ typedef bool (*BreakpadFilterCallback)(int exception_type, // well as a name and email address. // Default: NO // -// The BREAKPAD_PRODUCT, BREAKPAD_VERSION, and BREAKPAD_URL are +// 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_BUILD_ID A string parameter indicating build id. +// Optional. +//============================================================================= +// 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. If the BREAKPAD_PRODUCT or BREAKPAD_VERSION are -// ultimately undefined, BreakpadCreate() will fail. You have been -// warned. +// 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 +// 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 @@ -173,6 +192,22 @@ typedef bool (*BreakpadFilterCallback)(int exception_type, // 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. +// +// BREAKPAD_PROCESS_START_TIME The time the process started. +// +// BREAKPAD_PROCESS_CRASH_TIME The time the process crashed. +// +// BREAKPAD_PROCESS_UP_TIME The total time 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. // Returns a new BreakpadRef object on success, NULL otherwise. BreakpadRef BreakpadCreate(NSDictionary *parameters); diff --git a/src/client/mac/Framework/Breakpad.mm b/src/client/mac/Framework/Breakpad.mm index 65678807..30b8eeab 100644 --- a/src/client/mac/Framework/Breakpad.mm +++ b/src/client/mac/Framework/Breakpad.mm @@ -369,7 +369,6 @@ bool Breakpad::Initialize(NSDictionary *parameters) { new (gBreakpadAllocator->Allocate(sizeof(google_breakpad::ExceptionHandler))) google_breakpad::ExceptionHandler( Breakpad::ExceptionHandlerDirectCallback, this, true); - return true; } @@ -409,6 +408,8 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) { [parameters objectForKey:@BREAKPAD_VENDOR]; NSString *dumpSubdirectory = [parameters objectForKey:@BREAKPAD_DUMP_DIRECTORY]; + NSString *buildId = + [parameters objectForKey:@BREAKPAD_BUILD_ID]; // If these two are not already set(skipConfirm and sendAndExit can // come from user defaults and take priority) @@ -547,11 +548,22 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) { dictionary.SetKeyValue(BREAKPAD_DUMP_DIRECTORY, [dumpSubdirectory UTF8String]); + dictionary.SetKeyValue(BREAKPAD_BUILD_ID, + [buildId UTF8String]); + struct timeval tv; + gettimeofday(&tv, NULL); + char timeStartedString[32]; + sprintf(timeStartedString, "%d", 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]); + dictionary.SetKeyValue(logFileKey, + [[logFilePaths objectAtIndex:i] + fileSystemRepresentation]); } } diff --git a/src/client/mac/crash_generation/Inspector.h b/src/client/mac/crash_generation/Inspector.h index 098c56e8..9ee5c48c 100644 --- a/src/client/mac/crash_generation/Inspector.h +++ b/src/client/mac/crash_generation/Inspector.h @@ -171,6 +171,8 @@ class Inspector { kern_return_t SendAcknowledgement(); void LaunchReporter(const char *inConfigFilePath); + void SetCrashTimeParameters(); + mach_port_t service_rcv_port_; int exception_type_; diff --git a/src/client/mac/crash_generation/Inspector.mm b/src/client/mac/crash_generation/Inspector.mm index b147a860..9ba38538 100644 --- a/src/client/mac/crash_generation/Inspector.mm +++ b/src/client/mac/crash_generation/Inspector.mm @@ -321,6 +321,30 @@ kern_return_t Inspector::ReadMessages() { } //============================================================================= +// Sets keys in the parameters dictionary that are specific to process uptime. +// The two we set are process up time, and process crash time. +void Inspector::SetCrashTimeParameters() { + // Set process uptime parameter + struct timeval tv; + gettimeofday(&tv, NULL); + + char processUptimeString[32], processCrashtimeString[32]; + const char *processStartTimeString = + config_params_.GetValueForKey(BREAKPAD_PROCESS_START_TIME); + + // 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; + sprintf(processUptimeString, "%d", processUptime); + config_params_.SetKeyValue(BREAKPAD_PROCESS_UP_TIME, processUptimeString); + } + + sprintf(processCrashtimeString, "%d", tv.tv_sec); + config_params_.SetKeyValue(BREAKPAD_PROCESS_CRASH_TIME, + processCrashtimeString); +} + bool Inspector::InspectTask() { // keep the task quiet while we're looking at it task_suspend(remote_task_); @@ -330,6 +354,7 @@ bool Inspector::InspectTask() { const char *minidumpDirectory = config_params_.GetValueForKey(BREAKPAD_DUMP_DIRECTORY); + SetCrashTimeParameters(); // If the client app has not specified a minidump directory, // use a default of Library// if (0 == strlen(minidumpDirectory)) { diff --git a/src/client/mac/handler/exception_handler_test.cc b/src/client/mac/handler/exception_handler_test.cc index d84f1997..e9d200f9 100644 --- a/src/client/mac/handler/exception_handler_test.cc +++ b/src/client/mac/handler/exception_handler_test.cc @@ -28,15 +28,7 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /* -g++ -framework CoreFoundation -I../../.. \ - ../../minidump_file_writer.cc \ - ../../../common/convert_UTF.c \ - ../../../common/string_conversion.cc \ - ../../../common/mac/string_utilities.cc \ - exception_handler.cc \ - minidump_generator.cc \ - exception_handler_test.cc \ - -o exception_handler_test +g++ -framework CoreFoundation -I../../.. ../../minidump_file_writer.cc ../../../common/convert_UTF.c ../../../common/string_conversion.cc ../../../common/mac/string_utilities.cc exception_handler.cc minidump_generator.cc exception_handler_test.cc -o exception_handler_test -mmacosx-version-min=10.4 ../../../common/mac/file_id.cc dynamic_images.cc ../../../common/mac/macho_id.cc ../../../common/mac/macho_walker.cc -lcrypto ../../../common/mac/macho_utilities.cc */ #include diff --git a/src/client/mac/sender/crash_report_sender.h b/src/client/mac/sender/crash_report_sender.h index 76e6d3ea..955bd86d 100644 --- a/src/client/mac/sender/crash_report_sender.h +++ b/src/client/mac/sender/crash_report_sender.h @@ -38,22 +38,37 @@ #define kClientIdPreferenceKey @"clientid" +extern NSString *const kGoogleServerType; +extern NSString *const kSocorroServerType; +extern NSString *const kDefaultServerType; @interface Reporter : NSObject { @public IBOutlet NSWindow *alertWindow; // The alert window // Values bound in the XIB - NSString *headerMessage_; // Message notifying of the crash - NSString *reportMessage_; // Message explaining the crash report - NSString *commentsValue_; // Comments from the user - NSString *emailMessage_; // Message requesting user email - NSString *emailValue_; // Email from the user + NSString *headerMessage_; // Message notifying of the + // crash + NSString *reportMessage_; // Message explaining the + // crash report + NSString *commentsValue_; // Comments from the user + NSString *emailMessage_; // Message requesting user + // email + NSString *emailValue_; // Email from the user @private - int configFile_; // File descriptor for config file - 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 + int configFile_; // File descriptor for config file + 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 URL + // parameter names + NSMutableDictionary *socorroDictionary_; // The dictionary for + // Socorro + NSMutableDictionary *googleDictionary_; // The dictionary for + // Google } // Stops the modal panel with an NSAlertDefaultReturn value. This is the action @@ -62,14 +77,17 @@ // Stops the modal panel with an NSAlertAlternateReturn value. This is the // action invoked by the "Cancel" button. - (IBAction)cancel:(id)sender; -// Opens the Google Privacy Policy in the default web browser. +// 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; +- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView + doCommandBySelector:(SEL)commandSelector; + +// Helper method to set HTTP parameters based on server type +- (BOOL)setPostParametersFromDictionary:(NSMutableDictionary *)crashParameters; // Accessors to make bindings work - (NSString *)headerMessage; @@ -86,4 +104,8 @@ - (NSString *)emailValue; - (void)setEmailValue:(NSString *)value; + +// Initialization helper to create dictionaries mapping Breakpad +// parameters to URL parameters +- (void)createServerParameterDictionaries; @end diff --git a/src/client/mac/sender/crash_report_sender.m b/src/client/mac/sender/crash_report_sender.m index 99f1eb10..61564e54 100644 --- a/src/client/mac/sender/crash_report_sender.m +++ b/src/client/mac/sender/crash_report_sender.m @@ -44,6 +44,10 @@ const int kMinidumpFileLengthLimit = 800000; #define kApplePrefsSyncExcludeAllKey @"com.apple.PreferenceSync.ExcludeAllSyncKeys" +NSString *const kGoogleServerType = @"google"; +NSString *const kSocorroServerType = @"socorro"; +NSString *const kDefaultServerType = @"google"; + @interface Reporter(PrivateMethods) + (uid_t)consoleUID; @@ -59,12 +63,18 @@ const int kMinidumpFileLengthLimit = 800000; - (BOOL)askUserPermissionToSend:(BOOL)shouldSubmitReport; - (BOOL)shouldSubmitReport; -// Run an alert window with the given timeout. Returns NSAlertButtonDefault if -// the timeout is exceeded. A timeout of 0 queues the message immediately in the -// modal run loop. +// Run an alert window with the given timeout. Returns +// NSAlertButtonDefault if the timeout is exceeded. A timeout of 0 +// queues the message immediately in the modal run loop. - (int)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout; +// 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. +- (NSDictionary *)dictionaryForServerType:(NSString *)serverType; @end @implementation Reporter @@ -102,6 +112,8 @@ const int kMinidumpFileLengthLimit = 800000; [ud setBool:YES forKey:kApplePrefsSyncExcludeAllKey]; } + [self createServerParameterDictionaries]; + return self; } @@ -484,8 +496,9 @@ const int kMinidumpFileLengthLimit = 800000; // Text Field Delegate Methods //============================================================================= -- (BOOL)control:(NSControl*)control textView:(NSTextView*)textView - doCommandBySelector:(SEL)commandSelector { +- (BOOL) control:(NSControl*)control + textView:(NSTextView*)textView +doCommandBySelector:(SEL)commandSelector { BOOL result = NO; // If the user has entered text, don't end editing on "return" if (commandSelector == @selector(insertNewline:) @@ -580,51 +593,84 @@ const int kMinidumpFileLengthLimit = 800000; return YES; } +- (void)createServerParameterDictionaries { + serverDictionary_ = [[NSMutableDictionary alloc] init]; + socorroDictionary_ = [[NSMutableDictionary alloc] init]; + googleDictionary_ = [[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]; + // TODO: just for testing, google's server doesn't support it + [googleDictionary_ setObject:@"buildid" forKey:@BREAKPAD_BUILD_ID]; + + [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:@"ProductName" + forKey:@BREAKPAD_PRODUCT]; + [socorroDictionary_ setObject:@"BuildID" + forKey:@BREAKPAD_BUILD_ID]; +} + +- (NSDictionary *)dictionaryForServerType:(NSString *)serverType { + if (serverType == nil) { + return [serverDictionary_ objectForKey:kDefaultServerType]; + } + return [serverDictionary_ objectForKey:serverType]; +} + +// Helper method to set HTTP parameters based on server type +- (BOOL)setPostParametersFromDictionary:(NSMutableDictionary *)crashParameters { + NSString *serverType = [parameters_ objectForKey:@BREAKPAD_SERVER_TYPE]; + NSDictionary *urlParameterNames = [self dictionaryForServerType:serverType]; + + 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]; + } + } + return YES; +} + //============================================================================= - (void)report { NSURL *url = [NSURL URLWithString:[parameters_ objectForKey:@BREAKPAD_URL]]; HTTPMultipartUpload *upload = [[HTTPMultipartUpload alloc] initWithURL:url]; NSMutableDictionary *uploadParameters = [NSMutableDictionary dictionary]; - // Set the known parameters. This should be kept up to date with the - // parameters defined in the Breakpad.h list of parameters. The intent - // is so that if there's a parameter that's not in this list, we consider - // it to be a "user-defined" parameter and we'll upload it to the server. - NSSet *knownParameters = - [NSSet setWithObjects:@kReporterMinidumpDirectoryKey, - @kReporterMinidumpIDKey, @BREAKPAD_PRODUCT_DISPLAY, - @BREAKPAD_PRODUCT, @BREAKPAD_VERSION, @BREAKPAD_URL, - @BREAKPAD_REPORT_INTERVAL, @BREAKPAD_SKIP_CONFIRM, - @BREAKPAD_SEND_AND_EXIT, @BREAKPAD_REPORTER_EXE_LOCATION, - @BREAKPAD_INSPECTOR_LOCATION, @BREAKPAD_LOGFILES, - @BREAKPAD_LOGFILE_UPLOAD_SIZE, @BREAKPAD_EMAIL, - @BREAKPAD_REQUEST_COMMENTS, @BREAKPAD_COMMENTS, - @BREAKPAD_VENDOR, nil]; - - // Add parameters - [uploadParameters setObject:[parameters_ objectForKey:@BREAKPAD_PRODUCT] - forKey:@"prod"]; - [uploadParameters setObject:[parameters_ objectForKey:@BREAKPAD_VERSION] - forKey:@"ver"]; - - if ([parameters_ objectForKey:@BREAKPAD_EMAIL]) { - [uploadParameters setObject:[parameters_ objectForKey:@BREAKPAD_EMAIL] forKey:@"email"]; + if (![self setPostParametersFromDictionary:uploadParameters]) { + return; } - NSString* comments = [parameters_ objectForKey:@BREAKPAD_COMMENTS]; - if (comments != nil) { - [uploadParameters setObject:comments forKey:@"comments"]; - } - // Add any user parameters - NSArray *parameterKeys = [parameters_ allKeys]; - int keyCount = [parameterKeys count]; - for (int i = 0; i < keyCount; ++i) { - NSString *key = [parameterKeys objectAtIndex:i]; - if (![knownParameters containsObject:key] && - ![key hasPrefix:@BREAKPAD_LOGFILE_KEY_PREFIX]) - [uploadParameters setObject:[parameters_ objectForKey:key] forKey:key]; - } [upload setParameters:uploadParameters]; + // Add minidump file if (minidumpContents_) { [upload addFileContents:minidumpContents_ name:@"upload_file_minidump"]; @@ -691,8 +737,12 @@ const int kMinidumpFileLengthLimit = 800000; [parameters_ release]; [minidumpContents_ release]; [logFileData_ release]; + [googleDictionary_ release]; + [socorroDictionary_ release]; + [serverDictionary_ release]; [super dealloc]; } + @end //============================================================================= diff --git a/src/client/mac/testapp/Info.plist b/src/client/mac/testapp/Info.plist index 84755c5d..586fb5eb 100644 --- a/src/client/mac/testapp/Info.plist +++ b/src/client/mac/testapp/Info.plist @@ -25,7 +25,7 @@ NSPrincipalClass NSApplication BreakpadProductDisplay - Google Breakpad Tester + Breakpad Tester BreakpadProduct Breakpad_Tester BreakpadVersion @@ -40,8 +40,6 @@ YES BreakpadVendor Foo Bar Corp, Incorporated, LTD, LLC - BreakpadMinidumpLocation - Breakpad Test Application/Crash Dumps LSUIElement 1