997 lines
35 KiB
Plaintext
997 lines
35 KiB
Plaintext
// 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 "common/mac/MachIPC.h"
|
|
#import "common/mac/SimpleStringDictionary.h"
|
|
|
|
#import "client/mac/crash_generation/Inspector.h"
|
|
#import "client/mac/handler/exception_handler.h"
|
|
#import "client/mac/Framework/Breakpad.h"
|
|
#import "client/mac/Framework/OnDemandServer.h"
|
|
#import "client/mac/handler/protected_memory_allocator.h"
|
|
|
|
#import <sys/stat.h>
|
|
#import <sys/sysctl.h>
|
|
|
|
#import <Foundation/Foundation.h>
|
|
|
|
|
|
using google_breakpad::KeyValueEntry;
|
|
using google_breakpad::MachPortSender;
|
|
using google_breakpad::MachReceiveMessage;
|
|
using google_breakpad::MachSendMessage;
|
|
using google_breakpad::ReceivePort;
|
|
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, void *context) {
|
|
filter_callback_ = callback;
|
|
filter_callback_context_ = context;
|
|
}
|
|
|
|
private:
|
|
Breakpad()
|
|
: handler_(NULL),
|
|
config_params_(NULL),
|
|
send_and_exit_(true),
|
|
filter_callback_(NULL),
|
|
filter_callback_context_(NULL) {
|
|
inspector_path_[0] = 0;
|
|
}
|
|
|
|
bool Initialize(NSDictionary *parameters);
|
|
|
|
bool ExtractParameters(NSDictionary *parameters);
|
|
|
|
// Dispatches to HandleException()
|
|
static bool ExceptionHandlerDirectCallback(void *context,
|
|
int exception_type,
|
|
int exception_code,
|
|
int exception_subcode,
|
|
mach_port_t crashing_thread);
|
|
|
|
bool HandleException(int exception_type,
|
|
int exception_code,
|
|
int exception_subcode,
|
|
mach_port_t crashing_thread);
|
|
|
|
// Since ExceptionHandler (w/o namespace) is defined as typedef in OSX's
|
|
// MachineExceptions.h, we have to explicitly name the handler.
|
|
google_breakpad::ExceptionHandler *handler_; // The actual handler (STRONG)
|
|
|
|
char inspector_path_[PATH_MAX]; // Path to inspector tool
|
|
|
|
SimpleStringDictionary *config_params_; // Create parameters (STRONG)
|
|
|
|
OnDemandServer inspector_;
|
|
|
|
bool send_and_exit_; // Exit after sending, if true
|
|
|
|
BreakpadFilterCallback filter_callback_;
|
|
void *filter_callback_context_;
|
|
};
|
|
|
|
#pragma mark -
|
|
#pragma mark Helper functions
|
|
|
|
//=============================================================================
|
|
// Helper functions
|
|
|
|
//=============================================================================
|
|
static BOOL IsDebuggerActive() {
|
|
BOOL result = NO;
|
|
NSUserDefaults *stdDefaults = [NSUserDefaults standardUserDefaults];
|
|
|
|
// We check both defaults and the environment variable here
|
|
|
|
BOOL ignoreDebugger = [stdDefaults boolForKey:@IGNORE_DEBUGGER];
|
|
|
|
if (!ignoreDebugger) {
|
|
char *ignoreDebuggerStr = getenv(IGNORE_DEBUGGER);
|
|
ignoreDebugger = (ignoreDebuggerStr ? strtol(ignoreDebuggerStr, NULL, 10) : 0) != 0;
|
|
}
|
|
|
|
if (!ignoreDebugger) {
|
|
pid_t pid = getpid();
|
|
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
|
|
int mibSize = sizeof(mib) / sizeof(int);
|
|
size_t actualSize;
|
|
|
|
if (sysctl(mib, mibSize, NULL, &actualSize, NULL, 0) == 0) {
|
|
struct kinfo_proc *info = (struct kinfo_proc *)malloc(actualSize);
|
|
|
|
if (info) {
|
|
// This comes from looking at the Darwin xnu Kernel
|
|
if (sysctl(mib, mibSize, info, &actualSize, NULL, 0) == 0)
|
|
result = (info->kp_proc.p_flag & P_TRACED) ? YES : NO;
|
|
|
|
free(info);
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//=============================================================================
|
|
bool Breakpad::ExceptionHandlerDirectCallback(void *context,
|
|
int exception_type,
|
|
int exception_code,
|
|
int exception_subcode,
|
|
mach_port_t crashing_thread) {
|
|
Breakpad *breakpad = (Breakpad *)context;
|
|
|
|
// If our context is damaged or something, just return false to indicate that
|
|
// the handler should continue without us.
|
|
if (!breakpad)
|
|
return false;
|
|
|
|
return breakpad->HandleException( exception_type,
|
|
exception_code,
|
|
exception_subcode,
|
|
crashing_thread);
|
|
}
|
|
|
|
//=============================================================================
|
|
#pragma mark -
|
|
|
|
#include <dlfcn.h>
|
|
|
|
//=============================================================================
|
|
// Returns the pathname to the Resources directory for this version of
|
|
// Breakpad which we are now running.
|
|
//
|
|
// Don't make the function static, since _dyld_lookup_and_bind_fully needs a
|
|
// simple non-static C name
|
|
//
|
|
extern "C" {
|
|
NSString * GetResourcePath();
|
|
NSString * GetResourcePath() {
|
|
NSString *resourcePath = nil;
|
|
|
|
// If there are multiple breakpads installed then calling bundleWithIdentifier
|
|
// will not work properly, so only use that as a backup plan.
|
|
// We want to find the bundle containing the code where this function lives
|
|
// and work from there
|
|
//
|
|
|
|
// Get the pathname to the code which contains this function
|
|
Dl_info info;
|
|
if (dladdr((const void*)GetResourcePath, &info) != 0) {
|
|
NSFileManager *filemgr = [NSFileManager defaultManager];
|
|
NSString *filePath =
|
|
[filemgr stringWithFileSystemRepresentation:info.dli_fname
|
|
length:strlen(info.dli_fname)];
|
|
NSString *bundlePath = [filePath stringByDeletingLastPathComponent];
|
|
// The "Resources" directory should be in the same directory as the
|
|
// executable code, since that's how the Breakpad framework is built.
|
|
resourcePath = [bundlePath stringByAppendingPathComponent:@"Resources/"];
|
|
} else {
|
|
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 *skipConfirm = [stdDefaults stringForKey:@BREAKPAD_SKIP_CONFIRM];
|
|
NSString *sendAndExit = [stdDefaults stringForKey:@BREAKPAD_SEND_AND_EXIT];
|
|
|
|
NSString *serverType = [parameters objectForKey:@BREAKPAD_SERVER_TYPE];
|
|
NSString *display = [parameters objectForKey:@BREAKPAD_PRODUCT_DISPLAY];
|
|
NSString *product = [parameters objectForKey:@BREAKPAD_PRODUCT];
|
|
NSString *version = [parameters objectForKey:@BREAKPAD_VERSION];
|
|
NSString *urlStr = [parameters objectForKey:@BREAKPAD_URL];
|
|
NSString *interval = [parameters objectForKey:@BREAKPAD_REPORT_INTERVAL];
|
|
NSString *inspectorPathString =
|
|
[parameters objectForKey:@BREAKPAD_INSPECTOR_LOCATION];
|
|
NSString *reporterPathString =
|
|
[parameters objectForKey:@BREAKPAD_REPORTER_EXE_LOCATION];
|
|
NSString *timeout = [parameters objectForKey:@BREAKPAD_CONFIRM_TIMEOUT];
|
|
NSArray *logFilePaths = [parameters objectForKey:@BREAKPAD_LOGFILES];
|
|
NSString *logFileTailSize =
|
|
[parameters objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE];
|
|
NSString *requestUserText =
|
|
[parameters objectForKey:@BREAKPAD_REQUEST_COMMENTS];
|
|
NSString *requestEmail = [parameters objectForKey:@BREAKPAD_REQUEST_EMAIL];
|
|
NSString *vendor =
|
|
[parameters objectForKey:@BREAKPAD_VENDOR];
|
|
NSString *dumpSubdirectory =
|
|
[parameters objectForKey:@BREAKPAD_DUMP_DIRECTORY];
|
|
|
|
NSDictionary *serverParameters =
|
|
[parameters objectForKey:@BREAKPAD_SERVER_PARAMETER_DICT];
|
|
|
|
// These may have been set above as user prefs, which take priority.
|
|
if (!skipConfirm) {
|
|
skipConfirm = [parameters objectForKey:@BREAKPAD_SKIP_CONFIRM];
|
|
}
|
|
if (!sendAndExit) {
|
|
sendAndExit = [parameters objectForKey:@BREAKPAD_SEND_AND_EXIT];
|
|
}
|
|
|
|
if (!product)
|
|
product = [parameters objectForKey:@"CFBundleName"];
|
|
|
|
if (!display) {
|
|
display = [parameters objectForKey:@"CFBundleDisplayName"];
|
|
if (!display) {
|
|
display = product;
|
|
}
|
|
}
|
|
|
|
if (!version)
|
|
version = [parameters objectForKey:@"CFBundleVersion"];
|
|
|
|
if (!interval)
|
|
interval = @"3600";
|
|
|
|
if (!timeout)
|
|
timeout = @"300";
|
|
|
|
if (!logFileTailSize)
|
|
logFileTailSize = @"200000";
|
|
|
|
if (!vendor) {
|
|
vendor = @"Vendor not specified";
|
|
}
|
|
|
|
// Normalize the values.
|
|
if (skipConfirm) {
|
|
skipConfirm = [skipConfirm uppercaseString];
|
|
|
|
if ([skipConfirm isEqualToString:@"YES"] ||
|
|
[skipConfirm isEqualToString:@"TRUE"] ||
|
|
[skipConfirm isEqualToString:@"1"])
|
|
skipConfirm = @"YES";
|
|
else
|
|
skipConfirm = @"NO";
|
|
} else {
|
|
skipConfirm = @"NO";
|
|
}
|
|
|
|
send_and_exit_ = true;
|
|
if (sendAndExit) {
|
|
sendAndExit = [sendAndExit uppercaseString];
|
|
|
|
if ([sendAndExit isEqualToString:@"NO"] ||
|
|
[sendAndExit isEqualToString:@"FALSE"] ||
|
|
[sendAndExit isEqualToString:@"0"])
|
|
send_and_exit_ = false;
|
|
}
|
|
|
|
if (requestUserText) {
|
|
requestUserText = [requestUserText uppercaseString];
|
|
|
|
if ([requestUserText isEqualToString:@"YES"] ||
|
|
[requestUserText isEqualToString:@"TRUE"] ||
|
|
[requestUserText isEqualToString:@"1"])
|
|
requestUserText = @"YES";
|
|
else
|
|
requestUserText = @"NO";
|
|
} else {
|
|
requestUserText = @"NO";
|
|
}
|
|
|
|
// Find the helper applications if not specified in user config.
|
|
NSString *resourcePath = nil;
|
|
if (!inspectorPathString || !reporterPathString) {
|
|
resourcePath = GetResourcePath();
|
|
if (!resourcePath) {
|
|
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;
|
|
}
|
|
|
|
if (!dumpSubdirectory) {
|
|
dumpSubdirectory = @"";
|
|
}
|
|
|
|
// The product, version, and URL are required values.
|
|
if (![product length]) {
|
|
DEBUGLOG(stderr, "Missing required product key.\n");
|
|
return false;
|
|
}
|
|
|
|
if (![version length]) {
|
|
DEBUGLOG(stderr, "Missing required version key.\n");
|
|
return false;
|
|
}
|
|
|
|
if (![urlStr length]) {
|
|
DEBUGLOG(stderr, "Missing required URL key.\n");
|
|
return false;
|
|
}
|
|
|
|
config_params_ =
|
|
new (gKeyValueAllocator->Allocate(sizeof(SimpleStringDictionary)) )
|
|
SimpleStringDictionary();
|
|
|
|
SimpleStringDictionary &dictionary = *config_params_;
|
|
|
|
dictionary.SetKeyValue(BREAKPAD_SERVER_TYPE, [serverType UTF8String]);
|
|
dictionary.SetKeyValue(BREAKPAD_PRODUCT_DISPLAY, [display UTF8String]);
|
|
dictionary.SetKeyValue(BREAKPAD_PRODUCT, [product UTF8String]);
|
|
dictionary.SetKeyValue(BREAKPAD_VERSION, [version UTF8String]);
|
|
dictionary.SetKeyValue(BREAKPAD_URL, [urlStr UTF8String]);
|
|
dictionary.SetKeyValue(BREAKPAD_REPORT_INTERVAL, [interval UTF8String]);
|
|
dictionary.SetKeyValue(BREAKPAD_SKIP_CONFIRM, [skipConfirm UTF8String]);
|
|
dictionary.SetKeyValue(BREAKPAD_CONFIRM_TIMEOUT, [timeout UTF8String]);
|
|
dictionary.SetKeyValue(BREAKPAD_INSPECTOR_LOCATION,
|
|
[inspectorPathString fileSystemRepresentation]);
|
|
dictionary.SetKeyValue(BREAKPAD_REPORTER_EXE_LOCATION,
|
|
[reporterPathString fileSystemRepresentation]);
|
|
dictionary.SetKeyValue(BREAKPAD_LOGFILE_UPLOAD_SIZE,
|
|
[logFileTailSize UTF8String]);
|
|
dictionary.SetKeyValue(BREAKPAD_REQUEST_COMMENTS,
|
|
[requestUserText UTF8String]);
|
|
dictionary.SetKeyValue(BREAKPAD_REQUEST_EMAIL, [requestEmail UTF8String]);
|
|
dictionary.SetKeyValue(BREAKPAD_VENDOR, [vendor UTF8String]);
|
|
dictionary.SetKeyValue(BREAKPAD_DUMP_DIRECTORY,
|
|
[dumpSubdirectory UTF8String]);
|
|
|
|
struct timeval tv;
|
|
gettimeofday(&tv, NULL);
|
|
char timeStartedString[32];
|
|
sprintf(timeStartedString, "%zd", tv.tv_sec);
|
|
dictionary.SetKeyValue(BREAKPAD_PROCESS_START_TIME,
|
|
timeStartedString);
|
|
|
|
if (logFilePaths) {
|
|
char logFileKey[255];
|
|
for(unsigned int i = 0; i < [logFilePaths count]; i++) {
|
|
sprintf(logFileKey,"%s%d", BREAKPAD_LOGFILE_KEY_PREFIX, i);
|
|
dictionary.SetKeyValue(logFileKey,
|
|
[[logFilePaths objectAtIndex:i]
|
|
fileSystemRepresentation]);
|
|
}
|
|
}
|
|
|
|
if (serverParameters) {
|
|
// For each key-value pair, call BreakpadAddUploadParameter()
|
|
NSEnumerator *keyEnumerator = [serverParameters keyEnumerator];
|
|
NSString *aParameter;
|
|
while ((aParameter = [keyEnumerator nextObject])) {
|
|
BreakpadAddUploadParameter(this, aParameter,
|
|
[serverParameters objectForKey:aParameter]);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//=============================================================================
|
|
void Breakpad::SetKeyValue(NSString *key, NSString *value) {
|
|
// We allow nil values. This is the same as removing the keyvalue.
|
|
if (!config_params_ || !key)
|
|
return;
|
|
|
|
config_params_->SetKeyValue([key UTF8String], [value UTF8String]);
|
|
}
|
|
|
|
//=============================================================================
|
|
NSString *Breakpad::KeyValue(NSString *key) {
|
|
if (!config_params_ || !key)
|
|
return nil;
|
|
|
|
const char *value = config_params_->GetValueForKey([key UTF8String]);
|
|
return value ? [NSString stringWithUTF8String:value] : nil;
|
|
}
|
|
|
|
//=============================================================================
|
|
void Breakpad::RemoveKeyValue(NSString *key) {
|
|
if (!config_params_ || !key) return;
|
|
|
|
config_params_->RemoveKey([key UTF8String]);
|
|
}
|
|
|
|
//=============================================================================
|
|
void Breakpad::GenerateAndSendReport() {
|
|
config_params_->SetKeyValue(BREAKPAD_ON_DEMAND, "YES");
|
|
HandleException(0, 0, 0, mach_thread_self());
|
|
config_params_->SetKeyValue(BREAKPAD_ON_DEMAND, "NO");
|
|
}
|
|
|
|
//=============================================================================
|
|
bool Breakpad::HandleException(int exception_type,
|
|
int exception_code,
|
|
int exception_subcode,
|
|
mach_port_t crashing_thread) {
|
|
DEBUGLOG(stderr, "Breakpad: an exception occurred\n");
|
|
|
|
if (filter_callback_) {
|
|
bool should_handle = filter_callback_(exception_type,
|
|
exception_code,
|
|
crashing_thread,
|
|
filter_callback_context_);
|
|
if (!should_handle) return false;
|
|
}
|
|
|
|
// We need to reset the memory protections to be read/write,
|
|
// since LaunchOnDemand() requires changing state.
|
|
gBreakpadAllocator->Unprotect();
|
|
// Configure the server to launch when we message the service port.
|
|
// The reason we do this here, rather than at startup, is that we
|
|
// can leak a bootstrap service entry if this method is called and
|
|
// there never ends up being a crash.
|
|
inspector_.LaunchOnDemand();
|
|
gBreakpadAllocator->Protect();
|
|
|
|
// The Inspector should send a message to this port to verify it
|
|
// received our information and has finished the inspection.
|
|
ReceivePort acknowledge_port;
|
|
|
|
// Send initial information to the Inspector.
|
|
MachSendMessage message(kMsgType_InspectorInitialInfo);
|
|
message.AddDescriptor(mach_task_self()); // our task
|
|
message.AddDescriptor(crashing_thread); // crashing thread
|
|
message.AddDescriptor(mach_thread_self()); // exception-handling thread
|
|
message.AddDescriptor(acknowledge_port.GetPort());// message receive port
|
|
|
|
InspectorInfo info;
|
|
info.exception_type = exception_type;
|
|
info.exception_code = exception_code;
|
|
info.exception_subcode = exception_subcode;
|
|
info.parameter_count = config_params_->GetCount();
|
|
message.SetData(&info, sizeof(info));
|
|
|
|
MachPortSender sender(inspector_.GetServicePort());
|
|
|
|
kern_return_t result = sender.SendMessage(message, 2000);
|
|
|
|
if (result == KERN_SUCCESS) {
|
|
// Now, send a series of key-value pairs to the Inspector.
|
|
const 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, 5000);
|
|
}
|
|
}
|
|
|
|
#if VERBOSE
|
|
PRINT_MACH_RESULT(result, "Breakpad: SendMessage ");
|
|
printf("Breakpad: Inspector service port = %#x\n",
|
|
inspector_.GetServicePort());
|
|
#endif
|
|
|
|
// If we don't want any forwarding, return true here to indicate that we've
|
|
// processed things as much as we want.
|
|
if (send_and_exit_) return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
//=============================================================================
|
|
//=============================================================================
|
|
|
|
#pragma mark -
|
|
#pragma mark Public API
|
|
|
|
//=============================================================================
|
|
BreakpadRef BreakpadCreate(NSDictionary *parameters) {
|
|
try {
|
|
// This is confusing. Our two main allocators for breakpad memory are:
|
|
// - gKeyValueAllocator for the key/value memory
|
|
// - gBreakpadAllocator for the Breakpad, ExceptionHandler, and other
|
|
// breakpad allocations which are accessed at exception handling time.
|
|
//
|
|
// But in order to avoid these two allocators themselves from being smashed,
|
|
// we'll protect them as well by allocating them with gMasterAllocator.
|
|
//
|
|
// gMasterAllocator itself will NOT be protected, but this doesn't matter,
|
|
// since once it does its allocations and locks the memory, smashes to itself
|
|
// don't affect anything we care about.
|
|
gMasterAllocator =
|
|
new ProtectedMemoryAllocator(sizeof(ProtectedMemoryAllocator) * 2);
|
|
|
|
gKeyValueAllocator =
|
|
new (gMasterAllocator->Allocate(sizeof(ProtectedMemoryAllocator)))
|
|
ProtectedMemoryAllocator(sizeof(SimpleStringDictionary));
|
|
|
|
// Create a mutex for use in accessing the SimpleStringDictionary
|
|
int mutexResult = pthread_mutex_init(&gDictionaryMutex, NULL);
|
|
if (mutexResult == 0) {
|
|
|
|
// With the current compiler, gBreakpadAllocator is allocating 1444 bytes.
|
|
// Let's round up to the nearest page size.
|
|
//
|
|
int breakpad_pool_size = 4096;
|
|
|
|
/*
|
|
sizeof(Breakpad)
|
|
+ sizeof(google_breakpad::ExceptionHandler)
|
|
+ sizeof( STUFF ALLOCATED INSIDE ExceptionHandler )
|
|
*/
|
|
|
|
gBreakpadAllocator =
|
|
new (gMasterAllocator->Allocate(sizeof(ProtectedMemoryAllocator)))
|
|
ProtectedMemoryAllocator(breakpad_pool_size);
|
|
|
|
// Stack-based autorelease pool for Breakpad::Create() obj-c code.
|
|
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
Breakpad *breakpad = Breakpad::Create(parameters);
|
|
|
|
if (breakpad) {
|
|
// Make read-only to protect against memory smashers
|
|
gMasterAllocator->Protect();
|
|
gKeyValueAllocator->Protect();
|
|
gBreakpadAllocator->Protect();
|
|
// Can uncomment this line to figure out how much space was actually
|
|
// allocated using this allocator
|
|
// printf("gBreakpadAllocator allocated size = %d\n",
|
|
// gBreakpadAllocator->GetAllocatedSize() );
|
|
[pool release];
|
|
return (BreakpadRef)breakpad;
|
|
}
|
|
|
|
[pool release];
|
|
}
|
|
} catch(...) { // don't let exceptions leave this C API
|
|
fprintf(stderr, "BreakpadCreate() : error\n");
|
|
}
|
|
|
|
if (gKeyValueAllocator) {
|
|
gKeyValueAllocator->~ProtectedMemoryAllocator();
|
|
gKeyValueAllocator = NULL;
|
|
}
|
|
|
|
if (gBreakpadAllocator) {
|
|
gBreakpadAllocator->~ProtectedMemoryAllocator();
|
|
gBreakpadAllocator = NULL;
|
|
}
|
|
|
|
delete gMasterAllocator;
|
|
gMasterAllocator = NULL;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//=============================================================================
|
|
void BreakpadRelease(BreakpadRef ref) {
|
|
try {
|
|
Breakpad *breakpad = (Breakpad *)ref;
|
|
|
|
if (gMasterAllocator) {
|
|
gMasterAllocator->Unprotect();
|
|
gKeyValueAllocator->Unprotect();
|
|
gBreakpadAllocator->Unprotect();
|
|
|
|
breakpad->~Breakpad();
|
|
|
|
// Unfortunately, it's not possible to deallocate this stuff
|
|
// because the exception handling thread is still finishing up
|
|
// asynchronously at this point... OK, it could be done with
|
|
// locks, etc. But since BreakpadRelease() should usually only
|
|
// be called right before the process exits, it's not worth
|
|
// deallocating this stuff.
|
|
#if 0
|
|
gKeyValueAllocator->~ProtectedMemoryAllocator();
|
|
gBreakpadAllocator->~ProtectedMemoryAllocator();
|
|
delete gMasterAllocator;
|
|
|
|
gMasterAllocator = NULL;
|
|
gKeyValueAllocator = NULL;
|
|
gBreakpadAllocator = NULL;
|
|
#endif
|
|
|
|
pthread_mutex_destroy(&gDictionaryMutex);
|
|
}
|
|
} catch(...) { // don't let exceptions leave this C API
|
|
fprintf(stderr, "BreakpadRelease() : error\n");
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
void BreakpadSetKeyValue(BreakpadRef ref, NSString *key, NSString *value) {
|
|
try {
|
|
// Not called at exception time
|
|
Breakpad *breakpad = (Breakpad *)ref;
|
|
|
|
if (breakpad && key && gKeyValueAllocator) {
|
|
ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator);
|
|
|
|
breakpad->SetKeyValue(key, value);
|
|
}
|
|
} catch(...) { // don't let exceptions leave this C API
|
|
fprintf(stderr, "BreakpadSetKeyValue() : error\n");
|
|
}
|
|
}
|
|
|
|
void BreakpadAddUploadParameter(BreakpadRef ref,
|
|
NSString *key,
|
|
NSString *value) {
|
|
// The only difference, internally, between an upload parameter and
|
|
// a key value one that is set with BreakpadSetKeyValue is that we
|
|
// prepend the keyname with a special prefix. This informs the
|
|
// crash sender that the parameter should be sent along with the
|
|
// POST of the crash dump upload.
|
|
try {
|
|
Breakpad *breakpad = (Breakpad *)ref;
|
|
|
|
if (breakpad && key && gKeyValueAllocator) {
|
|
ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator);
|
|
|
|
NSString *prefixedKey = [@BREAKPAD_SERVER_PARAMETER_PREFIX
|
|
stringByAppendingString:key];
|
|
breakpad->SetKeyValue(prefixedKey, value);
|
|
}
|
|
} catch(...) { // don't let exceptions leave this C API
|
|
fprintf(stderr, "BreakpadSetKeyValue() : error\n");
|
|
}
|
|
}
|
|
|
|
void BreakpadRemoveUploadParameter(BreakpadRef ref,
|
|
NSString *key) {
|
|
try {
|
|
// Not called at exception time
|
|
Breakpad *breakpad = (Breakpad *)ref;
|
|
|
|
if (breakpad && key && gKeyValueAllocator) {
|
|
ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator);
|
|
|
|
NSString *prefixedKey = [NSString stringWithFormat:@"%@%@",
|
|
@BREAKPAD_SERVER_PARAMETER_PREFIX, key];
|
|
breakpad->RemoveKeyValue(prefixedKey);
|
|
}
|
|
} catch(...) { // don't let exceptions leave this C API
|
|
fprintf(stderr, "BreakpadRemoveKeyValue() : error\n");
|
|
}
|
|
}
|
|
//=============================================================================
|
|
NSString *BreakpadKeyValue(BreakpadRef ref, NSString *key) {
|
|
NSString *value = nil;
|
|
|
|
try {
|
|
// Not called at exception time
|
|
Breakpad *breakpad = (Breakpad *)ref;
|
|
|
|
if (!breakpad || !key || !gKeyValueAllocator)
|
|
return nil;
|
|
|
|
ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator);
|
|
|
|
value = breakpad->KeyValue(key);
|
|
} catch(...) { // don't let exceptions leave this C API
|
|
fprintf(stderr, "BreakpadKeyValue() : error\n");
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
//=============================================================================
|
|
void BreakpadRemoveKeyValue(BreakpadRef ref, NSString *key) {
|
|
try {
|
|
// Not called at exception time
|
|
Breakpad *breakpad = (Breakpad *)ref;
|
|
|
|
if (breakpad && key && gKeyValueAllocator) {
|
|
ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator);
|
|
|
|
breakpad->RemoveKeyValue(key);
|
|
}
|
|
} catch(...) { // don't let exceptions leave this C API
|
|
fprintf(stderr, "BreakpadRemoveKeyValue() : error\n");
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
void BreakpadGenerateAndSendReport(BreakpadRef ref) {
|
|
try {
|
|
Breakpad *breakpad = (Breakpad *)ref;
|
|
|
|
if (breakpad && gKeyValueAllocator) {
|
|
ProtectedMemoryLocker locker(&gDictionaryMutex, gKeyValueAllocator);
|
|
|
|
gBreakpadAllocator->Unprotect();
|
|
breakpad->GenerateAndSendReport();
|
|
gBreakpadAllocator->Protect();
|
|
}
|
|
} catch(...) { // don't let exceptions leave this C API
|
|
fprintf(stderr, "BreakpadGenerateAndSendReport() : error\n");
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
void BreakpadSetFilterCallback(BreakpadRef ref,
|
|
BreakpadFilterCallback callback,
|
|
void *context) {
|
|
|
|
try {
|
|
Breakpad *breakpad = (Breakpad *)ref;
|
|
|
|
if (breakpad && gBreakpadAllocator) {
|
|
// share the dictionary mutex here (we really don't need a mutex)
|
|
ProtectedMemoryLocker locker(&gDictionaryMutex, gBreakpadAllocator);
|
|
|
|
breakpad->SetFilterCallback(callback, context);
|
|
}
|
|
} catch(...) { // don't let exceptions leave this C API
|
|
fprintf(stderr, "BreakpadSetFilterCallback() : error\n");
|
|
}
|
|
}
|
|
|
|
//============================================================================
|
|
void BreakpadAddLogFile(BreakpadRef ref, NSString *logPathname) {
|
|
int logFileCounter = 0;
|
|
|
|
NSString *logFileKey = [NSString stringWithFormat:@"%@%d",
|
|
@BREAKPAD_LOGFILE_KEY_PREFIX,
|
|
logFileCounter];
|
|
|
|
NSString *existingLogFilename = nil;
|
|
existingLogFilename = BreakpadKeyValue(ref, logFileKey);
|
|
// Find the first log file key that we can use by testing for existence
|
|
while (existingLogFilename) {
|
|
if ([existingLogFilename isEqualToString:logPathname]) {
|
|
return;
|
|
}
|
|
logFileCounter++;
|
|
logFileKey = [NSString stringWithFormat:@"%@%d",
|
|
@BREAKPAD_LOGFILE_KEY_PREFIX,
|
|
logFileCounter];
|
|
existingLogFilename = BreakpadKeyValue(ref, logFileKey);
|
|
}
|
|
|
|
BreakpadSetKeyValue(ref, logFileKey, logPathname);
|
|
}
|