diff options
author | nealsid <nealsid@4c0a9323-5329-0410-9bdc-e9ce6186880e> | 2009-04-01 03:18:49 +0000 |
---|---|---|
committer | nealsid <nealsid@4c0a9323-5329-0410-9bdc-e9ce6186880e> | 2009-04-01 03:18:49 +0000 |
commit | 3ebdb1bd7ae38bf0fb205dfaa2f5fde3d67ea141 (patch) | |
tree | 0217ab93423465d8931af3a6fce65caccb5cc5ba /src/client/mac/Framework | |
parent | issue 305 - breakpad Linux handler doesn't build with compiler built from lat... (diff) | |
download | breakpad-3ebdb1bd7ae38bf0fb205dfaa2f5fde3d67ea141.tar.xz |
Open sourcing the Breakpad framework from Google.
A=many, many people
R=nealsid, jeremy moskovich(from Chromium project)
git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@322 4c0a9323-5329-0410-9bdc-e9ce6186880e
Diffstat (limited to 'src/client/mac/Framework')
-rw-r--r-- | src/client/mac/Framework/Breakpad.h | 181 | ||||
-rw-r--r-- | src/client/mac/Framework/Breakpad.mm | 915 | ||||
-rw-r--r-- | src/client/mac/Framework/Breakpad_Prefix.pch | 8 | ||||
-rw-r--r-- | src/client/mac/Framework/English.lproj/InfoPlist.strings | bin | 0 -> 92 bytes | |||
-rw-r--r-- | src/client/mac/Framework/Info.plist | 26 | ||||
-rw-r--r-- | src/client/mac/Framework/OnDemandServer.h | 146 | ||||
-rw-r--r-- | src/client/mac/Framework/OnDemandServer.mm | 145 |
7 files changed, 1421 insertions, 0 deletions
diff --git a/src/client/mac/Framework/Breakpad.h b/src/client/mac/Framework/Breakpad.h new file mode 100644 index 00000000..05005a99 --- /dev/null +++ b/src/client/mac/Framework/Breakpad.h @@ -0,0 +1,181 @@ +// 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. + +typedef void *BreakpadRef; + +#ifdef __cplusplus +extern "C" { +#endif + +#include <CoreFoundation/CoreFoundation.h> +#include <Foundation/Foundation.h> + + // Keys for configuration file +#define kReporterMinidumpDirectoryKey "MinidumpDir" +#define kReporterMinidumpIDKey "MinidumpID" + +// 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_VERSION "BreakpadVersion" +#define BREAKPAD_URL "BreakpadURL" +#define BREAKPAD_REPORT_INTERVAL "BreakpadReportInterval" +#define BREAKPAD_SKIP_CONFIRM "BreakpadSkipConfirm" +#define BREAKPAD_SEND_AND_EXIT "BreakpadSendAndExit" +#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" + +// 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); + +// 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 +// 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 to +// BREAKPAD_PRODUCT if not specified. +// BREAKPAD_VERSION Product version (e.g., 1.2.3), used +// as metadata for crash report +// BREAKPAD_VENDOR Vendor named, used in UI (e.g. the Xxxx +// foo bar company product widget has crashed) +// BREAKPAD_URL URL destination for reporting +// 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. +// 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. +// BREAKPAD_INSPECTOR_LOCATION The full path to the Inspector executable. +// BREAKPAD_REPORTER_EXE_LOCATION The full path to the Reporter/sender +// executable. +// 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 as +// well as a name and email address. +// BREAKPAD_COMMENTS The text the user provided as comments. +// +// The BREAKPAD_PRODUCT and BREAKPAD_VERSION 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. +// +// 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. +// + +// 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) +void BreakpadSetFilterCallback(BreakpadRef ref, + BreakpadFilterCallback callback); + +// User defined key and value string storage +// All set keys will be uploaded with the minidump if a crash occurs +// Keys and Values are limited to 255 bytes (256 - 1 for terminator). +// NB this is BYTES not GLYPHS. +// 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 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. +void BreakpadSetKeyValue(BreakpadRef ref, NSString *key, NSString *value); +NSString *BreakpadKeyValue(BreakpadRef ref, NSString *key); +void BreakpadRemoveKeyValue(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/src/client/mac/Framework/Breakpad.mm b/src/client/mac/Framework/Breakpad.mm new file mode 100644 index 00000000..52301c72 --- /dev/null +++ b/src/client/mac/Framework/Breakpad.mm @@ -0,0 +1,915 @@ +// 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 VERBOSE 0 + +#if VERBOSE + static bool gDebugLog = true; +#else + static bool gDebugLog = false; +#endif + +#define DEBUGLOG if (gDebugLog) fprintf +#define IGNORE_DEBUGGER "BREAKPAD_IGNORE_DEBUGGER" + +#import "client/mac/Framework/Breakpad.h" +#import "client/mac/crash_generation/Inspector.h" +#import "client/mac/Framework/OnDemandServer.h" +#import "client/mac/handler/protected_memory_allocator.h" +#import "common/mac/MachIPC.h" +#import "common/mac/SimpleStringDictionary.h" + +#import <sys/stat.h> +#import <sys/sysctl.h> + +#import <Foundation/Foundation.h> + +#import "exception_handler.h" +#import "string_utilities.h" + +using google_breakpad::KeyValueEntry; +using google_breakpad::SimpleStringDictionary; +using google_breakpad::SimpleStringDictionaryIterator; + +//============================================================================= +// 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: + // allocator may be NULL, in which case no Protect() or Unprotect() calls + // will be made, but a lock will still be taken + ProtectedMemoryLocker(pthread_mutex_t *mutex, + ProtectedMemoryAllocator *allocator) + : mutex_(mutex), allocator_(allocator) { + // Lock the mutex + assert(pthread_mutex_lock(mutex_) == 0); + + // Unprotect the memory + if (allocator_ ) { + allocator_->Unprotect(); + } + } + + ~ProtectedMemoryLocker() { + // First protect the memory + if (allocator_) { + allocator_->Protect(); + } + + // Then unlock the mutex + assert(pthread_mutex_unlock(mutex_) == 0); + }; + +private: + // Keep anybody from ever creating one of these things not on the stack. + ProtectedMemoryLocker() { } + ProtectedMemoryLocker(const ProtectedMemoryLocker&); + ProtectedMemoryLocker & operator=(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) { + filter_callback_ = callback; + } + + private: + Breakpad() + : handler_(NULL), + config_params_(NULL), + send_and_exit_(true), + filter_callback_(NULL) { + inspector_path_[0] = 0; + } + + bool Initialize(NSDictionary *parameters); + + bool ExtractParameters(NSDictionary *parameters); + + // Dispatches to HandleException() + static bool ExceptionHandlerDirectCallback(void *context, + int exception_type, + int exception_code, + mach_port_t crashing_thread); + + bool HandleException(int exception_type, + int exception_code, + mach_port_t crashing_thread); + + // 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_; + + unsigned int logFileCounter; +}; + +#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, + 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, + crashing_thread); +} + +//============================================================================= +#pragma mark - + +#include <mach-o/dyld.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 + void *address = nil; + NSModule module = nil; + _dyld_lookup_and_bind_fully("_GetResourcePath", + &address, + &module); + + if (module && address) { + const char* moduleName = NSNameOfModule(module); + if (moduleName) { + // The "Resources" directory should be in the same directory as the + // executable code, since that's how the Breakpad framework is built. + resourcePath = [NSString stringWithUTF8String:moduleName]; + resourcePath = [resourcePath stringByDeletingLastPathComponent]; + resourcePath = [resourcePath stringByAppendingPathComponent:@"Resources/"]; + } else { + DEBUGLOG(stderr, "Missing moduleName\n"); + } + } else { + DEBUGLOG(stderr, "Could not find GetResourcePath\n"); + // 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()) { + DEBUGLOG(stderr, "Debugger is active: Not installing handler\n"); + return true; + } + + // Gather any user specified parameters + if (!ExtractParameters(parameters)) { + return false; + } + + // 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 *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 *skipConfirm = [parameters objectForKey:@BREAKPAD_SKIP_CONFIRM]; + NSString *sendAndExit = [parameters objectForKey:@BREAKPAD_SEND_AND_EXIT]; + NSArray *logFilePaths = [parameters objectForKey:@BREAKPAD_LOGFILES]; + NSString *logFileTailSize = [parameters objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE]; + NSString *reportEmail = [parameters objectForKey:@BREAKPAD_EMAIL]; + NSString *requestUserText = + [parameters objectForKey:@BREAKPAD_REQUEST_COMMENTS]; + NSString *vendor = + [parameters objectForKey:@BREAKPAD_VENDOR]; + + // If these two are not already set(skipConfirm and sendAndExit can + // come from user defaults and take priority) + if (!skipConfirm) { + skipConfirm = [stdDefaults stringForKey:@BREAKPAD_SKIP_CONFIRM]; + } + if (!sendAndExit) { + sendAndExit = [stdDefaults stringForKey:@BREAKPAD_SEND_AND_EXIT]; + } + + if (!product) + product = [parameters objectForKey:@"CFBundleName"]; + + if (!display) + display = product; + + if (!version) + version = [parameters objectForKey:@"CFBundleVersion"]; + + if (!interval) + interval = @"3600"; + + 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) { + DEBUGLOG(stderr, "Could not get resource path\n"); + return false; + } + } + + // Find Inspector. + if (!inspectorPathString) { + inspectorPathString = + [resourcePath stringByAppendingPathComponent:@"Inspector"]; + } + + // Verify that there is an Inspector tool + if (![[NSFileManager defaultManager] fileExistsAtPath:inspectorPathString]) { + DEBUGLOG(stderr, "Cannot find Inspector tool\n"); + 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]) { + DEBUGLOG(stderr, "Cannot find Reporter tool\n"); + return false; + } + + // The product and version are required values + if (![product length] || ![version length]) { + DEBUGLOG(stderr, "Missing required product and/or version keys\n"); + return false; + } + + config_params_ = + new (gKeyValueAllocator->Allocate(sizeof(SimpleStringDictionary)) ) + SimpleStringDictionary(); + + SimpleStringDictionary &dictionary = *config_params_; + + 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_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_VENDOR, + [vendor UTF8String]); + + 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 (reportEmail) { + dictionary.SetKeyValue(BREAKPAD_EMAIL, + [reportEmail UTF8String]); + } +#if 0 // for testing + BreakpadSetKeyValue(this, @"UserKey1", @"User Value 1"); + BreakpadSetKeyValue(this, @"UserKey2", @"User Value 2"); + BreakpadSetKeyValue(this, @"UserKey3", @"User Value 3"); + BreakpadSetKeyValue(this, @"UserKey4", @"User Value 4"); +#endif + + 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() { + HandleException(0, 0, 0); +} + +//============================================================================= +bool Breakpad::HandleException(int exception_type, + int exception_code, + mach_port_t crashing_thread) { + DEBUGLOG(stderr, "Breakpad: an exception occurred\n"); + + if (filter_callback_) { + bool should_handle = filter_callback_(exception_type, + exception_code, + crashing_thread); + 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.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 KeyValueEntry *entry = NULL; + SimpleStringDictionaryIterator 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, 2000); + } + } + +#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; +} + +//============================================================================= +//============================================================================= + +#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) { + throw mutexResult; // caught down below + } + + // 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(); + } else { + [pool release]; +#ifdef __EXCEPTIONS + throw(-1); +#else + return NULL; +#endif + } + + // 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; + } catch(...) { // don't let exception leave this C API + 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 exception 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 exception leave this C API + fprintf(stderr, "BreakpadSetKeyValue() : 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 exception 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 exception 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 exception leave this C API + fprintf(stderr, "BreakpadGenerateAndSendReport() : error\n"); + } +} + +//============================================================================= +void BreakpadSetFilterCallback(BreakpadRef ref, + BreakpadFilterCallback callback) { + + 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); + } + } catch(...) { // don't let exception 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/src/client/mac/Framework/Breakpad_Prefix.pch b/src/client/mac/Framework/Breakpad_Prefix.pch new file mode 100644 index 00000000..72986663 --- /dev/null +++ b/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/src/client/mac/Framework/English.lproj/InfoPlist.strings b/src/client/mac/Framework/English.lproj/InfoPlist.strings Binary files differnew file mode 100644 index 00000000..5e45963c --- /dev/null +++ b/src/client/mac/Framework/English.lproj/InfoPlist.strings diff --git a/src/client/mac/Framework/Info.plist b/src/client/mac/Framework/Info.plist new file mode 100644 index 00000000..0ec80c38 --- /dev/null +++ b/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.yourcompany.${PRODUCT_NAME:identifier}</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/src/client/mac/Framework/OnDemandServer.h b/src/client/mac/Framework/OnDemandServer.h new file mode 100644 index 00000000..9d8bc961 --- /dev/null +++ b/src/client/mac/Framework/OnDemandServer.h @@ -0,0 +1,146 @@ +// 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 <iostream> +#import <mach/mach.h> +#import <servers/bootstrap.h> +#import <stdio.h> +#import <stdlib.h> +#import <sys/stat.h> +#import <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/src/client/mac/Framework/OnDemandServer.mm b/src/client/mac/Framework/OnDemandServer.mm new file mode 100644 index 00000000..32ac1faa --- /dev/null +++ b/src/client/mac/Framework/OnDemandServer.mm @@ -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. + +#import "OnDemandServer.h" + +#if DEBUG + #define PRINT_MACH_RESULT(result_, message_) \ + printf(message_"%s (%d)\n", mach_error_string(result_), result_ ); +#else + #define PRINT_MACH_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; + + kern_return_t kr = + bootstrap_create_server(bootstrap_port, + const_cast<char*>(server_command), + geteuid(), // server uid + true, + &server_port_); + + if (kr != KERN_SUCCESS) { + PRINT_MACH_RESULT(kr, "bootstrap_create_server() : "); + return kr; + } + + strlcpy(service_name_, service_name, sizeof(service_name_)); + + // 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_); + + if (kr != KERN_SUCCESS) { + //PRINT_MACH_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 != KERN_SUCCESS) { + PRINT_MACH_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_ = NULL; + + // 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 = bootstrap_register(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; + } +} |