mirror of
https://git.suyu.dev/suyu/breakpad.git
synced 2025-12-28 18:25:20 +01:00
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
This commit is contained in:
parent
0abe34ce5d
commit
3ebdb1bd7a
49 changed files with 12695 additions and 22 deletions
181
src/client/mac/Framework/Breakpad.h
Normal file
181
src/client/mac/Framework/Breakpad.h
Normal file
|
|
@ -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
|
||||
915
src/client/mac/Framework/Breakpad.mm
Normal file
915
src/client/mac/Framework/Breakpad.mm
Normal file
|
|
@ -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);
|
||||
|
||||
}
|
||||
8
src/client/mac/Framework/Breakpad_Prefix.pch
Normal file
8
src/client/mac/Framework/Breakpad_Prefix.pch
Normal file
|
|
@ -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
|
||||
BIN
src/client/mac/Framework/English.lproj/InfoPlist.strings
Normal file
BIN
src/client/mac/Framework/English.lproj/InfoPlist.strings
Normal file
Binary file not shown.
26
src/client/mac/Framework/Info.plist
Normal file
26
src/client/mac/Framework/Info.plist
Normal file
|
|
@ -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>
|
||||
146
src/client/mac/Framework/OnDemandServer.h
Normal file
146
src/client/mac/Framework/OnDemandServer.h
Normal file
|
|
@ -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_;
|
||||
};
|
||||
145
src/client/mac/Framework/OnDemandServer.mm
Normal file
145
src/client/mac/Framework/OnDemandServer.mm
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue