mirror of
https://git.suyu.dev/suyu/breakpad.git
synced 2026-02-19 00:39:38 +00: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:
1937
src/client/mac/Breakpad.xcodeproj/project.pbxproj
Normal file
1937
src/client/mac/Breakpad.xcodeproj/project.pbxproj
Normal file
File diff suppressed because it is too large
Load Diff
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;
|
||||
}
|
||||
}
|
||||
20
src/client/mac/UnitTests-Info.plist
Normal file
20
src/client/mac/UnitTests-Info.plist
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.yourcompany.${PRODUCT_NAME:identifier}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
200
src/client/mac/crash_generation/Inspector.h
Normal file
200
src/client/mac/crash_generation/Inspector.h
Normal file
@@ -0,0 +1,200 @@
|
||||
// Copyright (c) 2007, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Interface file between the Breakpad.framework and
|
||||
// the Inspector process.
|
||||
|
||||
#import "common/mac/SimpleStringDictionary.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "client/mac/handler/minidump_generator.h"
|
||||
|
||||
#define VERBOSE 0
|
||||
|
||||
extern bool gDebugLog;
|
||||
|
||||
#define DEBUGLOG if (gDebugLog) fprintf
|
||||
|
||||
// Types of mach messsages (message IDs)
|
||||
enum {
|
||||
kMsgType_InspectorInitialInfo = 0, // data is InspectorInfo
|
||||
kMsgType_InspectorKeyValuePair = 1, // data is KeyValueMessageData
|
||||
kMsgType_InspectorAcknowledgement = 2 // no data sent
|
||||
};
|
||||
|
||||
// Initial information sent from the crashed process by
|
||||
// Breakpad.framework to the Inspector process
|
||||
// The mach message with this struct as data will also include
|
||||
// several descriptors for sending mach port rights to the crashed
|
||||
// task, etc.
|
||||
struct InspectorInfo {
|
||||
int exception_type;
|
||||
int exception_code;
|
||||
unsigned int parameter_count; // key-value pairs
|
||||
};
|
||||
|
||||
// Key/value message data to be sent to the Inspector
|
||||
struct KeyValueMessageData {
|
||||
public:
|
||||
KeyValueMessageData() {}
|
||||
KeyValueMessageData(const google_breakpad::KeyValueEntry &inEntry) {
|
||||
strlcpy(key, inEntry.GetKey(), sizeof(key) );
|
||||
strlcpy(value, inEntry.GetValue(), sizeof(value) );
|
||||
}
|
||||
|
||||
char key[google_breakpad::KeyValueEntry::MAX_STRING_STORAGE_SIZE];
|
||||
char value[google_breakpad::KeyValueEntry::MAX_STRING_STORAGE_SIZE];
|
||||
};
|
||||
|
||||
using std::string;
|
||||
using google_breakpad::MinidumpGenerator;
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
static BOOL EnsureDirectoryPathExists(NSString *dirPath);
|
||||
|
||||
//=============================================================================
|
||||
class ConfigFile {
|
||||
public:
|
||||
ConfigFile() {
|
||||
config_file_ = -1;
|
||||
config_file_path_[0] = 0;
|
||||
has_created_file_ = false;
|
||||
};
|
||||
|
||||
~ConfigFile() {
|
||||
};
|
||||
|
||||
void WriteFile(const SimpleStringDictionary *configurationParameters,
|
||||
const char *dump_dir,
|
||||
const char *minidump_id);
|
||||
|
||||
const char *GetFilePath() { return config_file_path_; }
|
||||
|
||||
void Unlink() {
|
||||
if (config_file_ != -1)
|
||||
unlink(config_file_path_);
|
||||
|
||||
config_file_ = -1;
|
||||
}
|
||||
|
||||
private:
|
||||
BOOL WriteData(const void *data, size_t length);
|
||||
|
||||
BOOL AppendConfigData(const char *key,
|
||||
const void *data,
|
||||
size_t length);
|
||||
|
||||
BOOL AppendConfigString(const char *key,
|
||||
const char *value);
|
||||
|
||||
int config_file_; // descriptor for config file
|
||||
char config_file_path_[PATH_MAX]; // Path to configuration file
|
||||
bool has_created_file_;
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
class MinidumpLocation {
|
||||
public:
|
||||
MinidumpLocation() {
|
||||
NSString *minidumpDirBase = NSHomeDirectory();
|
||||
NSString *minidumpDir;
|
||||
|
||||
// Put root processes at root
|
||||
if (geteuid() == 0)
|
||||
minidumpDirBase = @"/";
|
||||
|
||||
minidumpDir =
|
||||
[minidumpDirBase stringByAppendingPathComponent:@"Library/Logs/Google"];
|
||||
|
||||
// Ensure that the path exists. Fallback to /tmp if unable to locate path.
|
||||
if (!EnsureDirectoryPathExists(minidumpDir)) {
|
||||
DEBUGLOG(stderr, "Unable to create: %s\n", [minidumpDir UTF8String]);
|
||||
minidumpDir = @"/tmp";
|
||||
}
|
||||
|
||||
strlcpy(minidump_dir_path_, [minidumpDir fileSystemRepresentation],
|
||||
sizeof(minidump_dir_path_));
|
||||
|
||||
// now generate a unique ID
|
||||
string dump_path(minidump_dir_path_);
|
||||
string next_minidump_id;
|
||||
|
||||
string next_minidump_path_ =
|
||||
(MinidumpGenerator::UniqueNameInDirectory(dump_path, &next_minidump_id));
|
||||
|
||||
strlcpy(minidump_id_, next_minidump_id.c_str(), sizeof(minidump_id_));
|
||||
};
|
||||
|
||||
const char *GetPath() { return minidump_dir_path_; }
|
||||
const char *GetID() { return minidump_id_; }
|
||||
|
||||
private:
|
||||
char minidump_dir_path_[PATH_MAX]; // Path to minidump directory
|
||||
char minidump_id_[128];
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
class Inspector {
|
||||
public:
|
||||
Inspector() {};
|
||||
|
||||
// given a bootstrap service name, receives mach messages
|
||||
// from a crashed process, then inspects it, creates a minidump file
|
||||
// and asks the user if he wants to upload it to a server.
|
||||
void Inspect(const char *receive_port_name);
|
||||
|
||||
private:
|
||||
kern_return_t ServiceCheckIn(const char *receive_port_name);
|
||||
kern_return_t ServiceCheckOut(const char *receive_port_name);
|
||||
|
||||
kern_return_t ReadMessages();
|
||||
|
||||
bool InspectTask();
|
||||
kern_return_t SendAcknowledgement();
|
||||
void LaunchReporter(const char *inConfigFilePath);
|
||||
|
||||
mach_port_t service_rcv_port_;
|
||||
|
||||
int exception_type_;
|
||||
int exception_code_;
|
||||
mach_port_t remote_task_;
|
||||
mach_port_t crashing_thread_;
|
||||
mach_port_t handler_thread_;
|
||||
mach_port_t ack_port_;
|
||||
|
||||
SimpleStringDictionary config_params_;
|
||||
|
||||
ConfigFile config_file_;
|
||||
};
|
||||
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
|
||||
431
src/client/mac/crash_generation/Inspector.mm
Normal file
431
src/client/mac/crash_generation/Inspector.mm
Normal file
@@ -0,0 +1,431 @@
|
||||
// Copyright (c) 2007, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Utility that can inspect another process and write a crash dump
|
||||
|
||||
#import <cstdio>
|
||||
#import <iostream>
|
||||
#import <stdio.h>
|
||||
#import <string.h>
|
||||
#import <string>
|
||||
|
||||
#import "client/mac/crash_generation/Inspector.h"
|
||||
|
||||
#import "client/mac/Framework/Breakpad.h"
|
||||
#import "client/mac/handler/minidump_generator.h"
|
||||
|
||||
#import "common/mac/SimpleStringDictionary.h"
|
||||
#import "common/mac/MachIPC.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if VERBOSE
|
||||
bool gDebugLog = true;
|
||||
#else
|
||||
bool gDebugLog = false;
|
||||
#endif
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
//=============================================================================
|
||||
static BOOL EnsureDirectoryPathExists(NSString *dirPath) {
|
||||
NSFileManager *mgr = [NSFileManager defaultManager];
|
||||
|
||||
// If we got a relative path, prepend the current directory
|
||||
if (![dirPath isAbsolutePath])
|
||||
dirPath = [[mgr currentDirectoryPath] stringByAppendingPathComponent:dirPath];
|
||||
|
||||
NSString *path = dirPath;
|
||||
|
||||
// Ensure that no file exists within the path which would block creation
|
||||
while (1) {
|
||||
BOOL isDir;
|
||||
if ([mgr fileExistsAtPath:path isDirectory:&isDir]) {
|
||||
if (isDir)
|
||||
break;
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
path = [path stringByDeletingLastPathComponent];
|
||||
}
|
||||
|
||||
// Path now contains the first valid directory (or is empty)
|
||||
if (![path length])
|
||||
return NO;
|
||||
|
||||
NSString *common =
|
||||
[dirPath commonPrefixWithString:path options:NSLiteralSearch];
|
||||
|
||||
// If everything is good
|
||||
if ([common isEqualToString:dirPath])
|
||||
return YES;
|
||||
|
||||
// Break up the difference into components
|
||||
NSString *diff = [dirPath substringFromIndex:[common length] + 1];
|
||||
NSArray *components = [diff pathComponents];
|
||||
unsigned count = [components count];
|
||||
|
||||
// Rebuild the path one component at a time
|
||||
NSDictionary *attrs =
|
||||
[NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedLong:0750]
|
||||
forKey:NSFilePosixPermissions];
|
||||
path = common;
|
||||
for (unsigned i = 0; i < count; ++i) {
|
||||
path = [path stringByAppendingPathComponent:[components objectAtIndex:i]];
|
||||
|
||||
if (![mgr createDirectoryAtPath:path attributes:attrs])
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
BOOL ConfigFile::WriteData(const void *data, size_t length) {
|
||||
size_t result = write(config_file_, data, length);
|
||||
|
||||
return result == length;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
BOOL ConfigFile::AppendConfigData(const char *key,
|
||||
const void *data, size_t length) {
|
||||
assert(config_file_ != -1);
|
||||
|
||||
if (!key) {
|
||||
DEBUGLOG(stderr, "Breakpad: Missing Key\n");
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
DEBUGLOG(stderr, "Breakpad: Missing data for key: %s\n", key ? key :
|
||||
"<Unknown Key>");
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Write the key, \n, length of data (ascii integer), \n, data
|
||||
char buffer[16];
|
||||
char nl = '\n';
|
||||
BOOL result = WriteData(key, strlen(key));
|
||||
|
||||
snprintf(buffer, sizeof(buffer) - 1, "\n%lu\n", length);
|
||||
result &= WriteData(buffer, strlen(buffer));
|
||||
result &= WriteData(data, length);
|
||||
result &= WriteData(&nl, 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
BOOL ConfigFile::AppendConfigString(const char *key,
|
||||
const char *value) {
|
||||
return AppendConfigData(key, value, strlen(value));
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
void ConfigFile::WriteFile(const SimpleStringDictionary *configurationParameters,
|
||||
const char *dump_dir,
|
||||
const char *minidump_id) {
|
||||
|
||||
assert(config_file_ == -1);
|
||||
|
||||
// Open and write out configuration file preamble
|
||||
strlcpy(config_file_path_, "/tmp/Config-XXXXXX",
|
||||
sizeof(config_file_path_));
|
||||
config_file_ = mkstemp(config_file_path_);
|
||||
|
||||
if (config_file_ == -1)
|
||||
return;
|
||||
|
||||
has_created_file_ = true;
|
||||
|
||||
// Add the minidump dir
|
||||
AppendConfigString(kReporterMinidumpDirectoryKey, dump_dir);
|
||||
AppendConfigString(kReporterMinidumpIDKey, minidump_id);
|
||||
|
||||
// Write out the configuration parameters
|
||||
BOOL result = YES;
|
||||
const SimpleStringDictionary &dictionary = *configurationParameters;
|
||||
|
||||
const KeyValueEntry *entry = NULL;
|
||||
SimpleStringDictionaryIterator iter(dictionary);
|
||||
|
||||
while ((entry = iter.Next())) {
|
||||
result = AppendConfigString(entry->GetKey(), entry->GetValue());
|
||||
|
||||
if (!result)
|
||||
break;
|
||||
}
|
||||
|
||||
close(config_file_);
|
||||
config_file_ = -1;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
void Inspector::Inspect(const char *receive_port_name) {
|
||||
kern_return_t result = ServiceCheckIn(receive_port_name);
|
||||
|
||||
if (result == KERN_SUCCESS) {
|
||||
result = ReadMessages();
|
||||
|
||||
if (result == KERN_SUCCESS) {
|
||||
// Inspect the task and write a minidump file.
|
||||
InspectTask();
|
||||
|
||||
// Send acknowledgement to the crashed process that the inspection
|
||||
// has finished. It will then be able to cleanly exit.
|
||||
if (SendAcknowledgement() == KERN_SUCCESS) {
|
||||
// Ask the user if he wants to upload the crash report to a server,
|
||||
// and do so if he agrees.
|
||||
LaunchReporter(config_file_.GetFilePath());
|
||||
}
|
||||
|
||||
// Now that we're done reading messages, cleanup the service, but only
|
||||
// if there was an actual exception
|
||||
// Otherwise, it means the dump was generated on demand and the process
|
||||
// lives on, and we might be needed again in the future.
|
||||
if (exception_code_) {
|
||||
ServiceCheckOut(receive_port_name);
|
||||
}
|
||||
} else {
|
||||
PRINT_MACH_RESULT(result, "Inspector: WaitForMessage()");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
kern_return_t Inspector::ServiceCheckIn(const char *receive_port_name) {
|
||||
// We need to get the mach port representing this service, so we can
|
||||
// get information from the crashed process.
|
||||
kern_return_t kr = bootstrap_check_in(bootstrap_port,
|
||||
(char*)receive_port_name,
|
||||
&service_rcv_port_);
|
||||
|
||||
if (kr != KERN_SUCCESS) {
|
||||
#if VERBOSE
|
||||
PRINT_MACH_RESULT(kr, "Inspector: bootstrap_check_in()");
|
||||
#endif
|
||||
}
|
||||
|
||||
return kr;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
kern_return_t Inspector::ServiceCheckOut(const char *receive_port_name) {
|
||||
// We're done receiving mach messages from the crashed process,
|
||||
// so clean up a bit.
|
||||
kern_return_t kr;
|
||||
|
||||
// DO NOT use mach_port_deallocate() here -- it will fail and the
|
||||
// following bootstrap_register() will also fail leaving our service
|
||||
// name hanging around forever (until reboot)
|
||||
kr = mach_port_destroy(mach_task_self(), service_rcv_port_);
|
||||
|
||||
if (kr != KERN_SUCCESS) {
|
||||
PRINT_MACH_RESULT(kr,
|
||||
"Inspector: UNREGISTERING: service_rcv_port mach_port_deallocate()");
|
||||
return kr;
|
||||
}
|
||||
|
||||
// Unregister the service associated with the receive port.
|
||||
kr = bootstrap_register(bootstrap_port,
|
||||
(char*)receive_port_name,
|
||||
MACH_PORT_NULL);
|
||||
|
||||
if (kr != KERN_SUCCESS) {
|
||||
PRINT_MACH_RESULT(kr, "Inspector: UNREGISTERING: bootstrap_register()");
|
||||
}
|
||||
|
||||
return kr;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
kern_return_t Inspector::ReadMessages() {
|
||||
// Wait for an initial message from the crashed process containing basic
|
||||
// information about the crash.
|
||||
ReceivePort receive_port(service_rcv_port_);
|
||||
|
||||
MachReceiveMessage message;
|
||||
kern_return_t result = receive_port.WaitForMessage(&message, 1000);
|
||||
|
||||
if (result == KERN_SUCCESS) {
|
||||
InspectorInfo &info = (InspectorInfo &)*message.GetData();
|
||||
exception_type_ = info.exception_type;
|
||||
exception_code_ = info.exception_code;
|
||||
|
||||
#if VERBOSE
|
||||
printf("message ID = %d\n", message.GetMessageID());
|
||||
#endif
|
||||
|
||||
remote_task_ = message.GetTranslatedPort(0);
|
||||
crashing_thread_ = message.GetTranslatedPort(1);
|
||||
handler_thread_ = message.GetTranslatedPort(2);
|
||||
ack_port_ = message.GetTranslatedPort(3);
|
||||
|
||||
#if VERBOSE
|
||||
printf("exception_type = %d\n", exception_type_);
|
||||
printf("exception_code = %d\n", exception_code_);
|
||||
printf("remote_task = %d\n", remote_task_);
|
||||
printf("crashing_thread = %d\n", crashing_thread_);
|
||||
printf("handler_thread = %d\n", handler_thread_);
|
||||
printf("ack_port_ = %d\n", ack_port_);
|
||||
printf("parameter count = %d\n", info.parameter_count);
|
||||
#endif
|
||||
|
||||
// The initial message contains the number of key value pairs that
|
||||
// we are expected to read.
|
||||
// Read each key/value pair, one mach message per key/value pair.
|
||||
for (unsigned int i = 0; i < info.parameter_count; ++i) {
|
||||
MachReceiveMessage message;
|
||||
result = receive_port.WaitForMessage(&message, 1000);
|
||||
|
||||
if(result == KERN_SUCCESS) {
|
||||
KeyValueMessageData &key_value_data =
|
||||
(KeyValueMessageData&)*message.GetData();
|
||||
|
||||
config_params_.SetKeyValue(key_value_data.key, key_value_data.value);
|
||||
} else {
|
||||
PRINT_MACH_RESULT(result, "Inspector: key/value message");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
bool Inspector::InspectTask() {
|
||||
// keep the task quiet while we're looking at it
|
||||
task_suspend(remote_task_);
|
||||
|
||||
MinidumpLocation minidumpLocation;
|
||||
|
||||
config_file_.WriteFile( &config_params_,
|
||||
minidumpLocation.GetPath(),
|
||||
minidumpLocation.GetID());
|
||||
|
||||
|
||||
MinidumpGenerator generator(remote_task_, handler_thread_);
|
||||
|
||||
if (exception_type_ && exception_code_) {
|
||||
generator.SetExceptionInformation(exception_type_,
|
||||
exception_code_,
|
||||
crashing_thread_);
|
||||
}
|
||||
|
||||
NSString *minidumpPath = [NSString stringWithFormat:@"%s/%s.dmp",
|
||||
minidumpLocation.GetPath(), minidumpLocation.GetID()];
|
||||
|
||||
bool result = generator.Write([minidumpPath fileSystemRepresentation]);
|
||||
|
||||
DEBUGLOG(stderr, "Inspector: finished writing minidump file: %s\n",
|
||||
[minidumpPath fileSystemRepresentation]);
|
||||
|
||||
// let the task continue
|
||||
task_resume(remote_task_);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// The crashed task needs to be told that the inspection has finished.
|
||||
// It will wait on a mach port (with timeout) until we send acknowledgement.
|
||||
kern_return_t Inspector::SendAcknowledgement() {
|
||||
if (ack_port_ != MACH_PORT_DEAD) {
|
||||
MachPortSender sender(ack_port_);
|
||||
MachSendMessage ack_message(kMsgType_InspectorAcknowledgement);
|
||||
|
||||
DEBUGLOG(stderr, "Inspector: trying to send acknowledgement to port %d\n",
|
||||
ack_port_);
|
||||
|
||||
kern_return_t result = sender.SendMessage(ack_message, 2000);
|
||||
|
||||
#if VERBOSE
|
||||
PRINT_MACH_RESULT(result, "Inspector: sent acknowledgement");
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DEBUGLOG(stderr, "Inspector: port translation failure!\n");
|
||||
return KERN_INVALID_NAME;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
void Inspector::LaunchReporter(const char *inConfigFilePath) {
|
||||
// Extract the path to the reporter executable.
|
||||
const char *reporterExecutablePath =
|
||||
config_params_.GetValueForKey(BREAKPAD_REPORTER_EXE_LOCATION);
|
||||
DEBUGLOG(stderr, "reporter path = %s\n", reporterExecutablePath);
|
||||
|
||||
// Setup and launch the crash dump sender.
|
||||
const char *argv[3];
|
||||
argv[0] = reporterExecutablePath;
|
||||
argv[1] = inConfigFilePath;
|
||||
argv[2] = NULL;
|
||||
|
||||
// Launch the reporter
|
||||
pid_t pid = fork();
|
||||
|
||||
// If we're in the child, load in our new executable and run.
|
||||
// The parent will not wait for the child to complete.
|
||||
if (pid == 0) {
|
||||
execv(argv[0], (char * const *)argv);
|
||||
config_file_.Unlink(); // launch failed - get rid of config file
|
||||
DEBUGLOG(stderr, "Inspector: unable to launch reporter app\n");
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
// Wait until the Reporter child process exits.
|
||||
//
|
||||
|
||||
// We'll use a timeout of one minute.
|
||||
int timeoutCount = 60; // 60 seconds
|
||||
|
||||
while (timeoutCount-- > 0) {
|
||||
int status;
|
||||
pid_t result = waitpid(pid, &status, WNOHANG);
|
||||
|
||||
if (result == 0) {
|
||||
// The child has not yet finished.
|
||||
sleep(1);
|
||||
} else if (result == -1) {
|
||||
DEBUGLOG(stderr, "Inspector: waitpid error (%d) waiting for reporter app\n",
|
||||
errno);
|
||||
break;
|
||||
} else {
|
||||
// child has finished
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
65
src/client/mac/crash_generation/InspectorMain.mm
Normal file
65
src/client/mac/crash_generation/InspectorMain.mm
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2007, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Main driver for Inspector
|
||||
|
||||
#import "client/mac/crash_generation/Inspector.h"
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
//=============================================================================
|
||||
extern "C" {
|
||||
|
||||
int main(int argc, char *const argv[]) {
|
||||
#if DEBUG
|
||||
// Since we're launched on-demand, this is necessary to see debugging
|
||||
// output in the console window.
|
||||
freopen("/dev/console", "w", stdout);
|
||||
freopen("/dev/console", "w", stderr);
|
||||
#endif
|
||||
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
if (argc != 2) {
|
||||
exit(0);
|
||||
}
|
||||
// Our first command-line argument contains the name of the service
|
||||
// that we're providing.
|
||||
google_breakpad::Inspector inspector;
|
||||
inspector.Inspect(argv[1]);
|
||||
|
||||
[pool release];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
} // namespace google_breakpad
|
||||
BIN
src/client/mac/gcov/libgcov.a
Normal file
BIN
src/client/mac/gcov/libgcov.a
Normal file
Binary file not shown.
@@ -490,7 +490,6 @@ void *ExceptionHandler::WaitForMessage(void *exception_handler_class) {
|
||||
if (self->use_minidump_write_mutex_)
|
||||
pthread_mutex_unlock(&self->minidump_write_mutex_);
|
||||
} else {
|
||||
|
||||
// When forking a child process with the exception handler installed,
|
||||
// if the child crashes, it will send the exception back to the parent
|
||||
// process. The check for task == self_task() ensures that only
|
||||
|
||||
@@ -55,6 +55,7 @@ static void *SleepyFunction(void *) {
|
||||
while (1) {
|
||||
sleep(10000);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void Crasher() {
|
||||
@@ -77,15 +78,14 @@ bool MDCallback(const char *dump_dir, const char *file_name,
|
||||
|
||||
fprintf(stdout, "Minidump: %s\n", path.c_str());
|
||||
// Indicate that we've handled the callback
|
||||
return true;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int main(int argc, char * const argv[]) {
|
||||
char buffer[PATH_MAX];
|
||||
struct passwd *user = getpwuid(getuid());
|
||||
|
||||
// Home dir
|
||||
snprintf(buffer, sizeof(buffer), "/Users/%s/Desktop/", user->pw_name);
|
||||
snprintf(buffer, sizeof(buffer), "/tmp/");
|
||||
|
||||
string path(buffer);
|
||||
ExceptionHandler eh(path, NULL, MDCallback, NULL, true);
|
||||
@@ -97,8 +97,8 @@ int main(int argc, char * const argv[]) {
|
||||
perror("pthread_create");
|
||||
}
|
||||
|
||||
// Dump a test
|
||||
eh.WriteMinidump();
|
||||
// // Dump a test
|
||||
// eh.WriteMinidump();
|
||||
|
||||
// Test the handler
|
||||
SoonToCrash();
|
||||
|
||||
@@ -45,14 +45,13 @@ static bool doneWritingReport = false;
|
||||
static void *Reporter(void *) {
|
||||
char buffer[PATH_MAX];
|
||||
MinidumpGenerator md;
|
||||
struct passwd *user = getpwuid(getuid());
|
||||
|
||||
// Write it to the desktop
|
||||
snprintf(buffer,
|
||||
sizeof(buffer),
|
||||
"/Users/%s/Desktop/test.dmp",
|
||||
user->pw_name);
|
||||
|
||||
"/tmp/test.dmp");
|
||||
|
||||
|
||||
fprintf(stdout, "Writing %s\n", buffer);
|
||||
unlink(buffer);
|
||||
md.Write(buffer);
|
||||
|
||||
834
src/client/mac/sender/English.lproj/Breakpad.xib
Normal file
834
src/client/mac/sender/English.lproj/Breakpad.xib
Normal file
@@ -0,0 +1,834 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.02">
|
||||
<data>
|
||||
<int key="IBDocument.SystemTarget">1050</int>
|
||||
<string key="IBDocument.SystemVersion">9F33</string>
|
||||
<string key="IBDocument.InterfaceBuilderVersion">670</string>
|
||||
<string key="IBDocument.AppKitVersion">949.34</string>
|
||||
<string key="IBDocument.HIToolboxVersion">352.00</string>
|
||||
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<integer value="2"/>
|
||||
</object>
|
||||
<object class="NSArray" key="IBDocument.PluginDependencies">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
</object>
|
||||
<object class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="NSCustomObject" id="1001">
|
||||
<string key="NSClassName">Reporter</string>
|
||||
</object>
|
||||
<object class="NSCustomObject" id="1003">
|
||||
<string key="NSClassName">FirstResponder</string>
|
||||
</object>
|
||||
<object class="NSCustomObject" id="1004">
|
||||
<string key="NSClassName">NSApplication</string>
|
||||
</object>
|
||||
<object class="NSWindowTemplate" id="1005">
|
||||
<int key="NSWindowStyleMask">1</int>
|
||||
<int key="NSWindowBacking">2</int>
|
||||
<string key="NSWindowRect">{{196, 157}, {484, 353}}</string>
|
||||
<int key="NSWTFlags">536871936</int>
|
||||
<string key="NSWindowTitle"/>
|
||||
<string key="NSWindowClass">NSWindow</string>
|
||||
<nil key="NSViewClass"/>
|
||||
<string key="NSWindowContentMaxSize">{3.40282e+38, 3.40282e+38}</string>
|
||||
<object class="NSView" key="NSWindowView" id="1006">
|
||||
<reference key="NSNextResponder"/>
|
||||
<int key="NSvFlags">264</int>
|
||||
<object class="NSMutableArray" key="NSSubviews">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="NSTextField" id="52942477">
|
||||
<reference key="NSNextResponder" ref="1006"/>
|
||||
<int key="NSvFlags">266</int>
|
||||
<string key="NSFrame">{{89, 279}, {378, 54}}</string>
|
||||
<reference key="NSSuperview" ref="1006"/>
|
||||
<bool key="NSEnabled">YES</bool>
|
||||
<object class="NSTextFieldCell" key="NSCell" id="237530958">
|
||||
<int key="NSCellFlags">67239424</int>
|
||||
<int key="NSCellFlags2">272629760</int>
|
||||
<string type="base64-UTF8" key="NSContents">RE8gTk9UIExPQ0FMSVpFLiBUaGUgPFJlYWxseSBMb25nIENvbXBhbnkgTmFtZT4gcHJvZ3JhbSA8UmVh
|
||||
bGx5IExvbmcgQXBwIE5hbWUgSGVyZT4gaGFzIHVuZXhwZWN0ZWRseSBxdWl0LiA</string>
|
||||
<object class="NSFont" key="NSSupport">
|
||||
<string key="NSName">LucidaGrande-Bold</string>
|
||||
<double key="NSSize">1.400000e+01</double>
|
||||
<int key="NSfFlags">16</int>
|
||||
</object>
|
||||
<reference key="NSControlView" ref="52942477"/>
|
||||
<object class="NSColor" key="NSBackgroundColor" id="738791573">
|
||||
<int key="NSColorSpace">6</int>
|
||||
<string key="NSCatalogName">System</string>
|
||||
<string key="NSColorName">controlColor</string>
|
||||
<object class="NSColor" key="NSColor">
|
||||
<int key="NSColorSpace">3</int>
|
||||
<bytes key="NSWhite">MC42NjY2NjY2OQA</bytes>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSColor" key="NSTextColor" id="909242260">
|
||||
<int key="NSColorSpace">6</int>
|
||||
<string key="NSCatalogName">System</string>
|
||||
<string key="NSColorName">controlTextColor</string>
|
||||
<object class="NSColor" key="NSColor" id="488201583">
|
||||
<int key="NSColorSpace">3</int>
|
||||
<bytes key="NSWhite">MAA</bytes>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSImageView" id="131954859">
|
||||
<reference key="NSNextResponder" ref="1006"/>
|
||||
<int key="NSvFlags">268</int>
|
||||
<object class="NSMutableSet" key="NSDragTypes">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="NSMutableArray" key="set.sortedObjects">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<string>Apple PDF pasteboard type</string>
|
||||
<string>Apple PICT pasteboard type</string>
|
||||
<string>Apple PNG pasteboard type</string>
|
||||
<string>NSFilenamesPboardType</string>
|
||||
<string>NeXT Encapsulated PostScript v1.2 pasteboard type</string>
|
||||
<string>NeXT TIFF v4.0 pasteboard type</string>
|
||||
</object>
|
||||
</object>
|
||||
<string key="NSFrame">{{20, 269}, {64, 64}}</string>
|
||||
<reference key="NSSuperview" ref="1006"/>
|
||||
<bool key="NSEnabled">YES</bool>
|
||||
<object class="NSImageCell" key="NSCell" id="863447683">
|
||||
<int key="NSCellFlags">130560</int>
|
||||
<int key="NSCellFlags2">33554432</int>
|
||||
<object class="NSCustomResource" key="NSContents">
|
||||
<string key="NSClassName">NSImage</string>
|
||||
<string key="NSResourceName">NSApplicationIcon</string>
|
||||
</object>
|
||||
<int key="NSAlign">0</int>
|
||||
<int key="NSScale">0</int>
|
||||
<int key="NSStyle">0</int>
|
||||
<bool key="NSAnimates">NO</bool>
|
||||
</object>
|
||||
<bool key="NSEditable">YES</bool>
|
||||
</object>
|
||||
<object class="NSTextField" id="547141541">
|
||||
<reference key="NSNextResponder" ref="1006"/>
|
||||
<int key="NSvFlags">266</int>
|
||||
<string key="NSFrame">{{17, 191}, {450, 70}}</string>
|
||||
<reference key="NSSuperview" ref="1006"/>
|
||||
<bool key="NSEnabled">YES</bool>
|
||||
<object class="NSTextFieldCell" key="NSCell" id="857260085">
|
||||
<int key="NSCellFlags">67239424</int>
|
||||
<int key="NSCellFlags2">272760832</int>
|
||||
<string type="base64-UTF8" key="NSContents">RE8gTk9UIExPQ0FMSVpFLiBUaGUgc3lzdGVtIGFuZCBvdGhlciBhcHBsaWNhdGlvbnMgaGF2ZSBub3Qg
|
||||
YmVlbiBhZmZlY3RlZC4gQSByZXBvcnQgaGFzIGJlZW4gY3JlYXRlZCB0aGF0IHlvdSBjYW4gc2VuZCB0
|
||||
byA8UmVhbGx5IExvbmcgQ29tcGFueSBOYW1lPiB0byBoZWxwIGlkZW50aWZ5IHRoZSBwcm9ibGVtLgoK
|
||||
UGxlYXNlIGhlbHAgdXMgZml4IHRoZSBwcm9ibGVtIGJ5IGRlc2NyaWJpbmcgd2hhdCBoYXBwZW5lZCBi
|
||||
ZWZvcmUgdGhlIGNyYXNoLg</string>
|
||||
<object class="NSFont" key="NSSupport" id="26">
|
||||
<string key="NSName">LucidaGrande</string>
|
||||
<double key="NSSize">1.100000e+01</double>
|
||||
<int key="NSfFlags">3100</int>
|
||||
</object>
|
||||
<reference key="NSControlView" ref="547141541"/>
|
||||
<reference key="NSBackgroundColor" ref="738791573"/>
|
||||
<reference key="NSTextColor" ref="909242260"/>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSTextField" id="637803025">
|
||||
<reference key="NSNextResponder" ref="1006"/>
|
||||
<int key="NSvFlags">290</int>
|
||||
<string key="NSFrame">{{17, 85}, {450, 28}}</string>
|
||||
<reference key="NSSuperview" ref="1006"/>
|
||||
<bool key="NSEnabled">YES</bool>
|
||||
<object class="NSTextFieldCell" key="NSCell" id="862965674">
|
||||
<int key="NSCellFlags">67239424</int>
|
||||
<int key="NSCellFlags2">272760832</int>
|
||||
<string key="NSContents">DO NOT LOCALIZE. Providing your email address is optional and will allow us contact you in case we need more details.</string>
|
||||
<reference key="NSSupport" ref="26"/>
|
||||
<reference key="NSControlView" ref="637803025"/>
|
||||
<reference key="NSBackgroundColor" ref="738791573"/>
|
||||
<reference key="NSTextColor" ref="909242260"/>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSButton" id="506323760">
|
||||
<reference key="NSNextResponder" ref="1006"/>
|
||||
<int key="NSvFlags">289</int>
|
||||
<string key="NSFrame">{{353, 12}, {117, 32}}</string>
|
||||
<reference key="NSSuperview" ref="1006"/>
|
||||
<bool key="NSEnabled">YES</bool>
|
||||
<object class="NSButtonCell" key="NSCell" id="555878343">
|
||||
<int key="NSCellFlags">67239424</int>
|
||||
<int key="NSCellFlags2">134217728</int>
|
||||
<string key="NSContents">Send Report</string>
|
||||
<object class="NSFont" key="NSSupport" id="890637240">
|
||||
<string key="NSName">LucidaGrande</string>
|
||||
<double key="NSSize">1.300000e+01</double>
|
||||
<int key="NSfFlags">1044</int>
|
||||
</object>
|
||||
<reference key="NSControlView" ref="506323760"/>
|
||||
<int key="NSButtonFlags">-2038284033</int>
|
||||
<int key="NSButtonFlags2">129</int>
|
||||
<reference key="NSAlternateImage" ref="890637240"/>
|
||||
<string key="NSAlternateContents"/>
|
||||
<string type="base64-UTF8" key="NSKeyEquivalent">DQ</string>
|
||||
<int key="NSPeriodicDelay">200</int>
|
||||
<int key="NSPeriodicInterval">25</int>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSButton" id="1066255572">
|
||||
<reference key="NSNextResponder" ref="1006"/>
|
||||
<int key="NSvFlags">289</int>
|
||||
<string key="NSFrame">{{243, 12}, {110, 32}}</string>
|
||||
<reference key="NSSuperview" ref="1006"/>
|
||||
<bool key="NSEnabled">YES</bool>
|
||||
<object class="NSButtonCell" key="NSCell" id="910201563">
|
||||
<int key="NSCellFlags">67239424</int>
|
||||
<int key="NSCellFlags2">134217728</int>
|
||||
<string key="NSContents">Cancel</string>
|
||||
<reference key="NSSupport" ref="890637240"/>
|
||||
<reference key="NSControlView" ref="1066255572"/>
|
||||
<int key="NSButtonFlags">-2038284033</int>
|
||||
<int key="NSButtonFlags2">129</int>
|
||||
<reference key="NSAlternateImage" ref="890637240"/>
|
||||
<string key="NSAlternateContents"/>
|
||||
<string type="base64-UTF8" key="NSKeyEquivalent">Gw</string>
|
||||
<int key="NSPeriodicDelay">200</int>
|
||||
<int key="NSPeriodicInterval">25</int>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSTextField" id="834332784">
|
||||
<reference key="NSNextResponder" ref="1006"/>
|
||||
<int key="NSvFlags">290</int>
|
||||
<string key="NSFrame">{{59, 58}, {195, 19}}</string>
|
||||
<reference key="NSSuperview" ref="1006"/>
|
||||
<bool key="NSEnabled">YES</bool>
|
||||
<object class="NSTextFieldCell" key="NSCell" id="454024475">
|
||||
<int key="NSCellFlags">-1804468671</int>
|
||||
<int key="NSCellFlags2">272761856</int>
|
||||
<string key="NSContents">preston.jackson@gmail.com</string>
|
||||
<reference key="NSSupport" ref="26"/>
|
||||
<string key="NSPlaceholderString">optional</string>
|
||||
<reference key="NSControlView" ref="834332784"/>
|
||||
<bool key="NSDrawsBackground">YES</bool>
|
||||
<object class="NSColor" key="NSBackgroundColor" id="181834562">
|
||||
<int key="NSColorSpace">6</int>
|
||||
<string key="NSCatalogName">System</string>
|
||||
<string key="NSColorName">textBackgroundColor</string>
|
||||
<object class="NSColor" key="NSColor">
|
||||
<int key="NSColorSpace">3</int>
|
||||
<bytes key="NSWhite">MQA</bytes>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSColor" key="NSTextColor" id="332510420">
|
||||
<int key="NSColorSpace">6</int>
|
||||
<string key="NSCatalogName">System</string>
|
||||
<string key="NSColorName">textColor</string>
|
||||
<reference key="NSColor" ref="488201583"/>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSTextField" id="1001751188">
|
||||
<reference key="NSNextResponder" ref="1006"/>
|
||||
<int key="NSvFlags">292</int>
|
||||
<string key="NSFrame">{{17, 60}, {37, 14}}</string>
|
||||
<reference key="NSSuperview" ref="1006"/>
|
||||
<bool key="NSEnabled">YES</bool>
|
||||
<object class="NSTextFieldCell" key="NSCell" id="220926093">
|
||||
<int key="NSCellFlags">68288064</int>
|
||||
<int key="NSCellFlags2">71435264</int>
|
||||
<string key="NSContents">Email:</string>
|
||||
<reference key="NSSupport" ref="26"/>
|
||||
<reference key="NSControlView" ref="1001751188"/>
|
||||
<reference key="NSBackgroundColor" ref="738791573"/>
|
||||
<reference key="NSTextColor" ref="909242260"/>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSButton" id="645396128">
|
||||
<reference key="NSNextResponder" ref="1006"/>
|
||||
<int key="NSvFlags">289</int>
|
||||
<string key="NSFrame">{{352, 59}, {16, 17}}</string>
|
||||
<reference key="NSSuperview" ref="1006"/>
|
||||
<bool key="NSEnabled">YES</bool>
|
||||
<object class="NSButtonCell" key="NSCell" id="776409024">
|
||||
<int key="NSCellFlags">-2080244224</int>
|
||||
<int key="NSCellFlags2">262144</int>
|
||||
<string key="NSContents">Privacy Policy</string>
|
||||
<object class="NSFont" key="NSSupport">
|
||||
<string key="NSName">LucidaGrande</string>
|
||||
<double key="NSSize">9.000000e+00</double>
|
||||
<int key="NSfFlags">3614</int>
|
||||
</object>
|
||||
<reference key="NSControlView" ref="645396128"/>
|
||||
<int key="NSButtonFlags">-2040250113</int>
|
||||
<int key="NSButtonFlags2">36</int>
|
||||
<object class="NSCustomResource" key="NSNormalImage">
|
||||
<string key="NSClassName">NSImage</string>
|
||||
<string key="NSResourceName">NSFollowLinkFreestandingTemplate</string>
|
||||
</object>
|
||||
<string key="NSAlternateContents"/>
|
||||
<string key="NSKeyEquivalent"/>
|
||||
<int key="NSPeriodicDelay">400</int>
|
||||
<int key="NSPeriodicInterval">75</int>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSTextField" id="271427294">
|
||||
<reference key="NSNextResponder" ref="1006"/>
|
||||
<int key="NSvFlags">289</int>
|
||||
<string key="NSFrame">{{259, 60}, {89, 14}}</string>
|
||||
<reference key="NSSuperview" ref="1006"/>
|
||||
<bool key="NSEnabled">YES</bool>
|
||||
<object class="NSTextFieldCell" key="NSCell" id="718717750">
|
||||
<int key="NSCellFlags">68288064</int>
|
||||
<int key="NSCellFlags2">71435264</int>
|
||||
<string key="NSContents">Privacy Policy</string>
|
||||
<reference key="NSSupport" ref="26"/>
|
||||
<reference key="NSControlView" ref="271427294"/>
|
||||
<reference key="NSBackgroundColor" ref="738791573"/>
|
||||
<reference key="NSTextColor" ref="909242260"/>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSTextField" id="652207274">
|
||||
<reference key="NSNextResponder" ref="1006"/>
|
||||
<int key="NSvFlags">274</int>
|
||||
<string key="NSFrame">{{20, 124}, {444, 61}}</string>
|
||||
<reference key="NSSuperview" ref="1006"/>
|
||||
<bool key="NSEnabled">YES</bool>
|
||||
<object class="NSTextFieldCell" key="NSCell" id="748395329">
|
||||
<int key="NSCellFlags">341966337</int>
|
||||
<int key="NSCellFlags2">272760832</int>
|
||||
<string key="NSContents">DO NOT LOCALIZE: Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line 1 Line Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 2 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 3 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 Line 4 </string>
|
||||
<reference key="NSSupport" ref="26"/>
|
||||
<reference key="NSControlView" ref="652207274"/>
|
||||
<bool key="NSDrawsBackground">YES</bool>
|
||||
<reference key="NSBackgroundColor" ref="181834562"/>
|
||||
<reference key="NSTextColor" ref="332510420"/>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<string key="NSFrameSize">{484, 353}</string>
|
||||
<reference key="NSSuperview"/>
|
||||
</object>
|
||||
<string key="NSScreenRect">{{0, 0}, {1680, 1028}}</string>
|
||||
<string key="NSMaxSize">{3.40282e+38, 3.40282e+38}</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBObjectContainer" key="IBDocument.Objects">
|
||||
<object class="NSMutableArray" key="connectionRecords">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">alertWindow</string>
|
||||
<reference key="source" ref="1001"/>
|
||||
<reference key="destination" ref="1005"/>
|
||||
</object>
|
||||
<int key="connectionID">42</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBActionConnection" key="connection">
|
||||
<string key="label">sendReport:</string>
|
||||
<reference key="source" ref="1001"/>
|
||||
<reference key="destination" ref="506323760"/>
|
||||
</object>
|
||||
<int key="connectionID">45</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBActionConnection" key="connection">
|
||||
<string key="label">cancel:</string>
|
||||
<reference key="source" ref="1001"/>
|
||||
<reference key="destination" ref="1066255572"/>
|
||||
</object>
|
||||
<int key="connectionID">46</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBActionConnection" key="connection">
|
||||
<string key="label">showPrivacyPolicy:</string>
|
||||
<reference key="source" ref="1001"/>
|
||||
<reference key="destination" ref="645396128"/>
|
||||
</object>
|
||||
<int key="connectionID">53</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBBindingConnection" key="connection">
|
||||
<string key="label">value: headerMessage</string>
|
||||
<reference key="source" ref="52942477"/>
|
||||
<reference key="destination" ref="1001"/>
|
||||
<object class="NSNibBindingConnector" key="connector">
|
||||
<reference key="NSSource" ref="52942477"/>
|
||||
<reference key="NSDestination" ref="1001"/>
|
||||
<string key="NSLabel">value: headerMessage</string>
|
||||
<string key="NSBinding">value</string>
|
||||
<string key="NSKeyPath">headerMessage</string>
|
||||
<int key="NSNibBindingConnectorVersion">2</int>
|
||||
</object>
|
||||
</object>
|
||||
<int key="connectionID">85</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBBindingConnection" key="connection">
|
||||
<string key="label">value: reportMessage</string>
|
||||
<reference key="source" ref="547141541"/>
|
||||
<reference key="destination" ref="1001"/>
|
||||
<object class="NSNibBindingConnector" key="connector">
|
||||
<reference key="NSSource" ref="547141541"/>
|
||||
<reference key="NSDestination" ref="1001"/>
|
||||
<string key="NSLabel">value: reportMessage</string>
|
||||
<string key="NSBinding">value</string>
|
||||
<string key="NSKeyPath">reportMessage</string>
|
||||
<int key="NSNibBindingConnectorVersion">2</int>
|
||||
</object>
|
||||
</object>
|
||||
<int key="connectionID">86</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBBindingConnection" key="connection">
|
||||
<string key="label">value: emailMessage</string>
|
||||
<reference key="source" ref="637803025"/>
|
||||
<reference key="destination" ref="1001"/>
|
||||
<object class="NSNibBindingConnector" key="connector">
|
||||
<reference key="NSSource" ref="637803025"/>
|
||||
<reference key="NSDestination" ref="1001"/>
|
||||
<string key="NSLabel">value: emailMessage</string>
|
||||
<string key="NSBinding">value</string>
|
||||
<string key="NSKeyPath">emailMessage</string>
|
||||
<int key="NSNibBindingConnectorVersion">2</int>
|
||||
</object>
|
||||
</object>
|
||||
<int key="connectionID">87</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBBindingConnection" key="connection">
|
||||
<string key="label">value: emailValue</string>
|
||||
<reference key="source" ref="834332784"/>
|
||||
<reference key="destination" ref="1001"/>
|
||||
<object class="NSNibBindingConnector" key="connector">
|
||||
<reference key="NSSource" ref="834332784"/>
|
||||
<reference key="NSDestination" ref="1001"/>
|
||||
<string key="NSLabel">value: emailValue</string>
|
||||
<string key="NSBinding">value</string>
|
||||
<string key="NSKeyPath">emailValue</string>
|
||||
<object class="NSDictionary" key="NSOptions">
|
||||
<string key="NS.key.0">NSNullPlaceholder</string>
|
||||
<string key="NS.object.0">optional</string>
|
||||
</object>
|
||||
<int key="NSNibBindingConnectorVersion">2</int>
|
||||
</object>
|
||||
</object>
|
||||
<int key="connectionID">90</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">initialFirstResponder</string>
|
||||
<reference key="source" ref="1005"/>
|
||||
<reference key="destination" ref="506323760"/>
|
||||
</object>
|
||||
<int key="connectionID">91</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBBindingConnection" key="connection">
|
||||
<string key="label">value: commentsValue</string>
|
||||
<reference key="source" ref="652207274"/>
|
||||
<reference key="destination" ref="1001"/>
|
||||
<object class="NSNibBindingConnector" key="connector">
|
||||
<reference key="NSSource" ref="652207274"/>
|
||||
<reference key="NSDestination" ref="1001"/>
|
||||
<string key="NSLabel">value: commentsValue</string>
|
||||
<string key="NSBinding">value</string>
|
||||
<string key="NSKeyPath">commentsValue</string>
|
||||
<object class="NSDictionary" key="NSOptions">
|
||||
<string key="NS.key.0">NSNullPlaceholder</string>
|
||||
<string key="NS.object.0">optional comments</string>
|
||||
</object>
|
||||
<int key="NSNibBindingConnectorVersion">2</int>
|
||||
</object>
|
||||
</object>
|
||||
<int key="connectionID">124</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">nextKeyView</string>
|
||||
<reference key="source" ref="834332784"/>
|
||||
<reference key="destination" ref="506323760"/>
|
||||
</object>
|
||||
<int key="connectionID">125</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">nextKeyView</string>
|
||||
<reference key="source" ref="652207274"/>
|
||||
<reference key="destination" ref="834332784"/>
|
||||
</object>
|
||||
<int key="connectionID">126</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">nextKeyView</string>
|
||||
<reference key="source" ref="506323760"/>
|
||||
<reference key="destination" ref="652207274"/>
|
||||
</object>
|
||||
<int key="connectionID">127</int>
|
||||
</object>
|
||||
<object class="IBConnectionRecord">
|
||||
<object class="IBOutletConnection" key="connection">
|
||||
<string key="label">delegate</string>
|
||||
<reference key="source" ref="652207274"/>
|
||||
<reference key="destination" ref="1001"/>
|
||||
</object>
|
||||
<int key="connectionID">128</int>
|
||||
</object>
|
||||
</object>
|
||||
<object class="IBMutableOrderedSet" key="objectRecords">
|
||||
<object class="NSArray" key="orderedObjects">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">0</int>
|
||||
<object class="NSArray" key="object" id="1002">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
</object>
|
||||
<reference key="children" ref="1000"/>
|
||||
<nil key="parent"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">-2</int>
|
||||
<reference key="object" ref="1001"/>
|
||||
<reference key="parent" ref="1002"/>
|
||||
<string type="base64-UTF8" key="objectName">RmlsZSdzIE93bmVyA</string>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">-1</int>
|
||||
<reference key="object" ref="1003"/>
|
||||
<reference key="parent" ref="1002"/>
|
||||
<string key="objectName">First Responder</string>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">-3</int>
|
||||
<reference key="object" ref="1004"/>
|
||||
<reference key="parent" ref="1002"/>
|
||||
<string key="objectName">Application</string>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">1</int>
|
||||
<reference key="object" ref="1005"/>
|
||||
<object class="NSMutableArray" key="children">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<reference ref="1006"/>
|
||||
</object>
|
||||
<reference key="parent" ref="1002"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">2</int>
|
||||
<reference key="object" ref="1006"/>
|
||||
<object class="NSMutableArray" key="children">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<reference ref="131954859"/>
|
||||
<reference ref="52942477"/>
|
||||
<reference ref="652207274"/>
|
||||
<reference ref="637803025"/>
|
||||
<reference ref="1001751188"/>
|
||||
<reference ref="834332784"/>
|
||||
<reference ref="271427294"/>
|
||||
<reference ref="645396128"/>
|
||||
<reference ref="506323760"/>
|
||||
<reference ref="1066255572"/>
|
||||
<reference ref="547141541"/>
|
||||
</object>
|
||||
<reference key="parent" ref="1005"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">3</int>
|
||||
<reference key="object" ref="52942477"/>
|
||||
<object class="NSMutableArray" key="children">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<reference ref="237530958"/>
|
||||
</object>
|
||||
<reference key="parent" ref="1006"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">4</int>
|
||||
<reference key="object" ref="237530958"/>
|
||||
<reference key="parent" ref="52942477"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">6</int>
|
||||
<reference key="object" ref="131954859"/>
|
||||
<object class="NSMutableArray" key="children">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<reference ref="863447683"/>
|
||||
</object>
|
||||
<reference key="parent" ref="1006"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">7</int>
|
||||
<reference key="object" ref="863447683"/>
|
||||
<reference key="parent" ref="131954859"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">8</int>
|
||||
<reference key="object" ref="547141541"/>
|
||||
<object class="NSMutableArray" key="children">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<reference ref="857260085"/>
|
||||
</object>
|
||||
<reference key="parent" ref="1006"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">9</int>
|
||||
<reference key="object" ref="857260085"/>
|
||||
<reference key="parent" ref="547141541"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">12</int>
|
||||
<reference key="object" ref="506323760"/>
|
||||
<object class="NSMutableArray" key="children">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<reference ref="555878343"/>
|
||||
</object>
|
||||
<reference key="parent" ref="1006"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">13</int>
|
||||
<reference key="object" ref="555878343"/>
|
||||
<reference key="parent" ref="506323760"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">14</int>
|
||||
<reference key="object" ref="1066255572"/>
|
||||
<object class="NSMutableArray" key="children">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<reference ref="910201563"/>
|
||||
</object>
|
||||
<reference key="parent" ref="1006"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">15</int>
|
||||
<reference key="object" ref="910201563"/>
|
||||
<reference key="parent" ref="1066255572"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">18</int>
|
||||
<reference key="object" ref="834332784"/>
|
||||
<object class="NSMutableArray" key="children">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<reference ref="454024475"/>
|
||||
</object>
|
||||
<reference key="parent" ref="1006"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">19</int>
|
||||
<reference key="object" ref="454024475"/>
|
||||
<reference key="parent" ref="834332784"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">20</int>
|
||||
<reference key="object" ref="1001751188"/>
|
||||
<object class="NSMutableArray" key="children">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<reference ref="220926093"/>
|
||||
</object>
|
||||
<reference key="parent" ref="1006"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">21</int>
|
||||
<reference key="object" ref="220926093"/>
|
||||
<reference key="parent" ref="1001751188"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">48</int>
|
||||
<reference key="object" ref="645396128"/>
|
||||
<object class="NSMutableArray" key="children">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<reference ref="776409024"/>
|
||||
</object>
|
||||
<reference key="parent" ref="1006"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">49</int>
|
||||
<reference key="object" ref="776409024"/>
|
||||
<reference key="parent" ref="645396128"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">58</int>
|
||||
<reference key="object" ref="637803025"/>
|
||||
<object class="NSMutableArray" key="children">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<reference ref="862965674"/>
|
||||
</object>
|
||||
<reference key="parent" ref="1006"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">59</int>
|
||||
<reference key="object" ref="862965674"/>
|
||||
<reference key="parent" ref="637803025"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">66</int>
|
||||
<reference key="object" ref="271427294"/>
|
||||
<object class="NSMutableArray" key="children">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<reference ref="718717750"/>
|
||||
</object>
|
||||
<reference key="parent" ref="1006"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">67</int>
|
||||
<reference key="object" ref="718717750"/>
|
||||
<reference key="parent" ref="271427294"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">116</int>
|
||||
<reference key="object" ref="652207274"/>
|
||||
<object class="NSMutableArray" key="children">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<reference ref="748395329"/>
|
||||
</object>
|
||||
<reference key="parent" ref="1006"/>
|
||||
</object>
|
||||
<object class="IBObjectRecord">
|
||||
<int key="objectID">117</int>
|
||||
<reference key="object" ref="748395329"/>
|
||||
<reference key="parent" ref="652207274"/>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="flattenedProperties">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="NSMutableArray" key="dict.sortedKeys">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<string>-1.IBPluginDependency</string>
|
||||
<string>-2.IBPluginDependency</string>
|
||||
<string>-3.IBPluginDependency</string>
|
||||
<string>1.IBEditorWindowLastContentRect</string>
|
||||
<string>1.IBPluginDependency</string>
|
||||
<string>1.IBViewEditorWindowController.showingBoundsRectangles</string>
|
||||
<string>1.IBViewEditorWindowController.showingLayoutRectangles</string>
|
||||
<string>1.IBWindowTemplateEditedContentRect</string>
|
||||
<string>1.NSWindowTemplate.visibleAtLaunch</string>
|
||||
<string>1.WindowOrigin</string>
|
||||
<string>1.editorWindowContentRectSynchronizationRect</string>
|
||||
<string>116.IBPluginDependency</string>
|
||||
<string>117.IBPluginDependency</string>
|
||||
<string>12.IBPluginDependency</string>
|
||||
<string>13.IBPluginDependency</string>
|
||||
<string>14.IBPluginDependency</string>
|
||||
<string>15.IBPluginDependency</string>
|
||||
<string>18.IBPluginDependency</string>
|
||||
<string>19.IBPluginDependency</string>
|
||||
<string>2.IBPluginDependency</string>
|
||||
<string>2.IBUserGuides</string>
|
||||
<string>20.IBPluginDependency</string>
|
||||
<string>21.IBPluginDependency</string>
|
||||
<string>3.IBPluginDependency</string>
|
||||
<string>4.IBPluginDependency</string>
|
||||
<string>48.IBPluginDependency</string>
|
||||
<string>49.IBPluginDependency</string>
|
||||
<string>58.IBPluginDependency</string>
|
||||
<string>59.IBPluginDependency</string>
|
||||
<string>6.IBPluginDependency</string>
|
||||
<string>66.IBPluginDependency</string>
|
||||
<string>67.IBPluginDependency</string>
|
||||
<string>7.IBPluginDependency</string>
|
||||
<string>8.IBPluginDependency</string>
|
||||
<string>9.IBPluginDependency</string>
|
||||
</object>
|
||||
<object class="NSMutableArray" key="dict.values">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>{{641, 409}, {484, 353}}</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<integer value="0" id="6"/>
|
||||
<reference ref="6"/>
|
||||
<string>{{641, 409}, {484, 353}}</string>
|
||||
<reference ref="6"/>
|
||||
<string>{196, 240}</string>
|
||||
<string>{{357, 418}, {480, 270}}</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<object class="NSMutableArray">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="IBUserGuide">
|
||||
<reference key="view" ref="1006"/>
|
||||
<float key="location">1.120000e+02</float>
|
||||
<int key="affinity">1</int>
|
||||
</object>
|
||||
</object>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="unlocalizedProperties">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="NSArray" key="dict.sortedKeys">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
</object>
|
||||
<object class="NSMutableArray" key="dict.values">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
</object>
|
||||
</object>
|
||||
<nil key="activeLocalization"/>
|
||||
<object class="NSMutableDictionary" key="localizations">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="NSArray" key="dict.sortedKeys">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
</object>
|
||||
<object class="NSMutableArray" key="dict.values">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
</object>
|
||||
</object>
|
||||
<nil key="sourceID"/>
|
||||
<int key="maxID">128</int>
|
||||
</object>
|
||||
<object class="IBClassDescriber" key="IBDocument.Classes">
|
||||
<object class="NSMutableArray" key="referencedPartialClassDescriptions">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="IBPartialClassDescription">
|
||||
<string key="className">Reporter</string>
|
||||
<string key="superclassName">NSObject</string>
|
||||
<object class="NSMutableDictionary" key="actions">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<object class="NSMutableArray" key="dict.sortedKeys">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<string>cancel:</string>
|
||||
<string>sendReport:</string>
|
||||
<string>showPrivacyPolicy:</string>
|
||||
</object>
|
||||
<object class="NSMutableArray" key="dict.values">
|
||||
<bool key="EncodedWithXMLCoder">YES</bool>
|
||||
<string>id</string>
|
||||
<string>id</string>
|
||||
<string>id</string>
|
||||
</object>
|
||||
</object>
|
||||
<object class="NSMutableDictionary" key="outlets">
|
||||
<string key="NS.key.0">alertWindow</string>
|
||||
<string key="NS.object.0">NSWindow</string>
|
||||
</object>
|
||||
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||
<string key="majorKey">IBProjectSource</string>
|
||||
<string key="minorKey">sender/crash_report_sender.h</string>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
<int key="IBDocument.localizationMode">0</int>
|
||||
<string key="IBDocument.LastKnownRelativeProjectPath">../../Breakpad.xcodeproj</string>
|
||||
<int key="IBDocument.defaultPropertyAccessControl">3</int>
|
||||
</data>
|
||||
</archive>
|
||||
BIN
src/client/mac/sender/English.lproj/Localizable.strings
Normal file
BIN
src/client/mac/sender/English.lproj/Localizable.strings
Normal file
Binary file not shown.
2489
src/client/mac/sender/ReporterIcon.graffle
Normal file
2489
src/client/mac/sender/ReporterIcon.graffle
Normal file
File diff suppressed because it is too large
Load Diff
BIN
src/client/mac/sender/ReporterIcon.icns
Normal file
BIN
src/client/mac/sender/ReporterIcon.icns
Normal file
Binary file not shown.
24
src/client/mac/sender/crash_report_sender-Info.plist
Normal file
24
src/client/mac/sender/crash_report_sender-Info.plist
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.Breakpad.${PRODUCT_NAME:identifier}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
89
src/client/mac/sender/crash_report_sender.h
Normal file
89
src/client/mac/sender/crash_report_sender.h
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// This component uses the HTTPMultipartUpload of the breakpad project to send
|
||||
// the minidump and associated data to the crash reporting servers.
|
||||
// It will perform throttling based on the parameters passed to it and will
|
||||
// prompt the user to send the minidump.
|
||||
|
||||
#include <Foundation/Foundation.h>
|
||||
|
||||
#include "client/mac/Framework/Breakpad.h"
|
||||
|
||||
#define kClientIdPreferenceKey @"clientid"
|
||||
|
||||
@interface Reporter : NSObject {
|
||||
@public
|
||||
IBOutlet NSWindow *alertWindow; // The alert window
|
||||
|
||||
// Values bound in the XIB
|
||||
NSString *headerMessage_; // Message notifying of the crash
|
||||
NSString *reportMessage_; // Message explaining the crash report
|
||||
NSString *commentsValue_; // Comments from the user
|
||||
NSString *emailMessage_; // Message requesting user email
|
||||
NSString *emailValue_; // Email from the user
|
||||
|
||||
@private
|
||||
int configFile_; // File descriptor for config file
|
||||
NSMutableDictionary *parameters_; // Key value pairs of data (STRONG)
|
||||
NSData *minidumpContents_; // The data in the minidump (STRONG)
|
||||
NSData *logFileData_; // An NSdata for the tar, bz2'd log file
|
||||
}
|
||||
|
||||
// Stops the modal panel with an NSAlertDefaultReturn value. This is the action
|
||||
// invoked by the "Send Report" button.
|
||||
- (IBAction)sendReport:(id)sender;
|
||||
// Stops the modal panel with an NSAlertAlternateReturn value. This is the
|
||||
// action invoked by the "Cancel" button.
|
||||
- (IBAction)cancel:(id)sender;
|
||||
// Opens the Google Privacy Policy in the default web browser.
|
||||
- (IBAction)showPrivacyPolicy:(id)sender;
|
||||
|
||||
// Delegate methods for the NSTextField for comments. We want to capture the
|
||||
// Return key and use it to send the message when no text has been entered.
|
||||
// Otherwise, we want Return to add a carriage return to the comments field.
|
||||
- (BOOL)control:(NSControl*)control textView:(NSTextView*)textView
|
||||
doCommandBySelector:(SEL)commandSelector;
|
||||
|
||||
// Accessors to make bindings work
|
||||
- (NSString *)headerMessage;
|
||||
- (void)setHeaderMessage:(NSString *)value;
|
||||
|
||||
- (NSString *)reportMessage;
|
||||
- (void)setReportMessage:(NSString *)value;
|
||||
|
||||
- (NSString *)commentsValue;
|
||||
- (void)setCommentsValue:(NSString *)value;
|
||||
|
||||
- (NSString *)emailMessage;
|
||||
- (void)setEmailMessage:(NSString *)value;
|
||||
|
||||
- (NSString *)emailValue;
|
||||
- (void)setEmailValue:(NSString *)value;
|
||||
@end
|
||||
762
src/client/mac/sender/crash_report_sender.m
Normal file
762
src/client/mac/sender/crash_report_sender.m
Normal file
@@ -0,0 +1,762 @@
|
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import <pwd.h>
|
||||
#import <sys/stat.h>
|
||||
#import <unistd.h>
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <SystemConfiguration/SystemConfiguration.h>
|
||||
|
||||
#import "common/mac/HTTPMultipartUpload.h"
|
||||
|
||||
#import "crash_report_sender.h"
|
||||
#import "common/mac/GTMLogger.h"
|
||||
|
||||
#define kLastSubmission @"LastSubmission"
|
||||
const int kMinidumpFileLengthLimit = 800000;
|
||||
|
||||
#define kApplePrefsSyncExcludeAllKey @"com.apple.PreferenceSync.ExcludeAllSyncKeys"
|
||||
|
||||
@interface Reporter(PrivateMethods)
|
||||
+ (uid_t)consoleUID;
|
||||
|
||||
- (id)initWithConfigurationFD:(int)fd;
|
||||
|
||||
- (NSString *)readString;
|
||||
- (NSData *)readData:(ssize_t)length;
|
||||
|
||||
- (BOOL)readConfigurationData;
|
||||
- (BOOL)readMinidumpData;
|
||||
- (BOOL)readLogFileData;
|
||||
|
||||
- (BOOL)askUserPermissionToSend:(BOOL)shouldSubmitReport;
|
||||
- (BOOL)shouldSubmitReport;
|
||||
|
||||
// Run an alert window with the given timeout. Returns NSAlertButtonDefault if
|
||||
// the timeout is exceeded. A timeout of 0 queues the message immediately in the
|
||||
// modal run loop.
|
||||
- (int)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout;
|
||||
|
||||
- (NSString*)clientID;
|
||||
@end
|
||||
|
||||
@implementation Reporter
|
||||
//=============================================================================
|
||||
+ (uid_t)consoleUID {
|
||||
SCDynamicStoreRef store =
|
||||
SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("Reporter"), NULL, NULL);
|
||||
uid_t uid = -2; // Default to "nobody"
|
||||
if (store) {
|
||||
CFStringRef user = SCDynamicStoreCopyConsoleUser(store, &uid, NULL);
|
||||
|
||||
if (user)
|
||||
CFRelease(user);
|
||||
else
|
||||
uid = -2;
|
||||
|
||||
CFRelease(store);
|
||||
}
|
||||
|
||||
return uid;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
- (id)initWithConfigurationFD:(int)fd {
|
||||
if ((self = [super init])) {
|
||||
configFile_ = fd;
|
||||
}
|
||||
|
||||
// Because the reporter is embedded in the framework (and many copies
|
||||
// of the framework may exist) its not completely certain that the OS
|
||||
// will obey the com.apple.PreferenceSync.ExcludeAllSyncKeys in our
|
||||
// Info.plist. To make sure, also set the key directly if needed.
|
||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||
if (![ud boolForKey:kApplePrefsSyncExcludeAllKey]) {
|
||||
[ud setBool:YES forKey:kApplePrefsSyncExcludeAllKey];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
- (NSString *)readString {
|
||||
NSMutableString *str = [NSMutableString stringWithCapacity:32];
|
||||
char ch[2] = { 0 };
|
||||
|
||||
while (read(configFile_, &ch[0], 1) == 1) {
|
||||
if (ch[0] == '\n') {
|
||||
// Break if this is the first newline after reading some other string
|
||||
// data.
|
||||
if ([str length])
|
||||
break;
|
||||
} else {
|
||||
[str appendString:[NSString stringWithUTF8String:ch]];
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
- (NSData *)readData:(ssize_t)length {
|
||||
NSMutableData *data = [NSMutableData dataWithLength:length];
|
||||
char *bytes = (char *)[data bytes];
|
||||
|
||||
if (read(configFile_, bytes, length) != length)
|
||||
return nil;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
- (BOOL)readConfigurationData {
|
||||
parameters_ = [[NSMutableDictionary alloc] init];
|
||||
|
||||
while (1) {
|
||||
NSString *key = [self readString];
|
||||
|
||||
if (![key length])
|
||||
break;
|
||||
|
||||
// Read the data. Try to convert to a UTF-8 string, or just save
|
||||
// the data
|
||||
NSString *lenStr = [self readString];
|
||||
ssize_t len = [lenStr intValue];
|
||||
NSData *data = [self readData:len];
|
||||
id value = [[NSString alloc] initWithData:data
|
||||
encoding:NSUTF8StringEncoding];
|
||||
|
||||
[parameters_ setObject:value ? value : data forKey:key];
|
||||
[value release];
|
||||
}
|
||||
|
||||
// generate a unique client ID based on this host's MAC address
|
||||
// then add a key/value pair for it
|
||||
NSString *clientID = [self clientID];
|
||||
[parameters_ setObject:clientID forKey:@"guid"];
|
||||
|
||||
close(configFile_);
|
||||
configFile_ = -1;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Per user per machine
|
||||
- (NSString *)clientID {
|
||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||
NSString *crashClientID = [ud stringForKey:kClientIdPreferenceKey];
|
||||
if (crashClientID) {
|
||||
return crashClientID;
|
||||
}
|
||||
|
||||
// Otherwise, if we have no client id, generate one!
|
||||
srandom([[NSDate date] timeIntervalSince1970]);
|
||||
long clientId1 = random();
|
||||
long clientId2 = random();
|
||||
long clientId3 = random();
|
||||
crashClientID = [NSString stringWithFormat:@"%x%x%x",
|
||||
clientId1, clientId2, clientId3];
|
||||
|
||||
[ud setObject:crashClientID forKey:kClientIdPreferenceKey];
|
||||
[ud synchronize];
|
||||
return crashClientID;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
- (BOOL)readLogFileData {
|
||||
unsigned int logFileCounter = 0;
|
||||
|
||||
NSString *logPath;
|
||||
int logFileTailSize = [[parameters_ objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE]
|
||||
intValue];
|
||||
|
||||
NSMutableArray *logFilenames; // An array of NSString, one per log file
|
||||
logFilenames = [[NSMutableArray alloc] init];
|
||||
|
||||
char tmpDirTemplate[80] = "/tmp/CrashUpload-XXXXX";
|
||||
char *tmpDir = mkdtemp(tmpDirTemplate);
|
||||
|
||||
// Construct key names for the keys we expect to contain log file paths
|
||||
for(logFileCounter = 0;; logFileCounter++) {
|
||||
NSString *logFileKey = [NSString stringWithFormat:@"%@%d",
|
||||
@BREAKPAD_LOGFILE_KEY_PREFIX,
|
||||
logFileCounter];
|
||||
|
||||
logPath = [parameters_ objectForKey:logFileKey];
|
||||
|
||||
// They should all be consecutive, so if we don't find one, assume
|
||||
// we're done
|
||||
|
||||
if (!logPath) {
|
||||
break;
|
||||
}
|
||||
|
||||
NSData *entireLogFile = [[NSData alloc] initWithContentsOfFile:logPath];
|
||||
|
||||
if (entireLogFile == nil) {
|
||||
continue;
|
||||
}
|
||||
|
||||
NSRange fileRange;
|
||||
|
||||
// Truncate the log file, only if necessary
|
||||
|
||||
if ([entireLogFile length] <= logFileTailSize) {
|
||||
fileRange = NSMakeRange(0, [entireLogFile length]);
|
||||
} else {
|
||||
fileRange = NSMakeRange([entireLogFile length] - logFileTailSize,
|
||||
logFileTailSize);
|
||||
}
|
||||
|
||||
char tmpFilenameTemplate[100];
|
||||
|
||||
// Generate a template based on the log filename
|
||||
sprintf(tmpFilenameTemplate,"%s/%s-XXXX", tmpDir,
|
||||
[[logPath lastPathComponent] fileSystemRepresentation]);
|
||||
|
||||
char *tmpFile = mktemp(tmpFilenameTemplate);
|
||||
|
||||
NSData *logSubdata = [entireLogFile subdataWithRange:fileRange];
|
||||
NSString *tmpFileString = [NSString stringWithUTF8String:tmpFile];
|
||||
[logSubdata writeToFile:tmpFileString atomically:NO];
|
||||
|
||||
[logFilenames addObject:[tmpFileString lastPathComponent]];
|
||||
[entireLogFile release];
|
||||
}
|
||||
|
||||
if ([logFilenames count] == 0) {
|
||||
[logFilenames release];
|
||||
logFileData_ = nil;
|
||||
return NO;
|
||||
}
|
||||
|
||||
// now, bzip all files into one
|
||||
NSTask *tarTask = [[NSTask alloc] init];
|
||||
|
||||
[tarTask setCurrentDirectoryPath:[NSString stringWithUTF8String:tmpDir]];
|
||||
[tarTask setLaunchPath:@"/usr/bin/tar"];
|
||||
|
||||
NSMutableArray *bzipArgs = [NSMutableArray arrayWithObjects:@"-cjvf",
|
||||
@"log.tar.bz2",nil];
|
||||
[bzipArgs addObjectsFromArray:logFilenames];
|
||||
|
||||
[logFilenames release];
|
||||
|
||||
[tarTask setArguments:bzipArgs];
|
||||
[tarTask launch];
|
||||
[tarTask waitUntilExit];
|
||||
[tarTask release];
|
||||
|
||||
NSString *logTarFile = [NSString stringWithFormat:@"%s/log.tar.bz2",tmpDir];
|
||||
logFileData_ = [[NSData alloc] initWithContentsOfFile:logTarFile];
|
||||
if (logFileData_ == nil) {
|
||||
GTMLoggerDebug(@"Cannot find temp tar log file: %@", logTarFile);
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
- (BOOL)readMinidumpData {
|
||||
NSString *minidumpDir = [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
|
||||
NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey];
|
||||
|
||||
if (![minidumpID length])
|
||||
return NO;
|
||||
|
||||
NSString *path = [minidumpDir stringByAppendingPathComponent:minidumpID];
|
||||
path = [path stringByAppendingPathExtension:@"dmp"];
|
||||
|
||||
// check the size of the minidump and limit it to a reasonable size
|
||||
// before attempting to load into memory and upload
|
||||
const char *fileName = [path fileSystemRepresentation];
|
||||
struct stat fileStatus;
|
||||
|
||||
BOOL success = YES;
|
||||
|
||||
if (!stat(fileName, &fileStatus)) {
|
||||
if (fileStatus.st_size > kMinidumpFileLengthLimit) {
|
||||
fprintf(stderr, "Breakpad Reporter: minidump file too large " \
|
||||
"to upload : %d\n", (int)fileStatus.st_size);
|
||||
success = NO;
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Breakpad Reporter: unable to determine minidump " \
|
||||
"file length\n");
|
||||
success = NO;
|
||||
}
|
||||
|
||||
if (success) {
|
||||
minidumpContents_ = [[NSData alloc] initWithContentsOfFile:path];
|
||||
success = ([minidumpContents_ length] ? YES : NO);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
// something wrong with the minidump file -- delete it
|
||||
unlink(fileName);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
- (BOOL)askUserPermissionToSend:(BOOL)shouldSubmitReport {
|
||||
// Send without confirmation
|
||||
if ([[parameters_ objectForKey:@BREAKPAD_SKIP_CONFIRM] isEqualToString:@"YES"]) {
|
||||
GTMLoggerDebug(@"Skipping confirmation and sending report");
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Determine if we should create a text box for user feedback
|
||||
BOOL shouldRequestComments =
|
||||
[[parameters_ objectForKey:@BREAKPAD_REQUEST_COMMENTS]
|
||||
isEqual:@"YES"];
|
||||
|
||||
NSString *display = [parameters_ objectForKey:@BREAKPAD_PRODUCT_DISPLAY];
|
||||
|
||||
if (![display length])
|
||||
display = [parameters_ objectForKey:@BREAKPAD_PRODUCT];
|
||||
|
||||
NSString *vendor = [parameters_ objectForKey:@BREAKPAD_VENDOR];
|
||||
|
||||
if (![vendor length])
|
||||
vendor = @"Vendor not specified";
|
||||
|
||||
NSBundle *bundle = [NSBundle mainBundle];
|
||||
[self setHeaderMessage:[NSString stringWithFormat:
|
||||
NSLocalizedStringFromTableInBundle(@"headerFmt", nil,
|
||||
bundle,
|
||||
@""), vendor, display]];
|
||||
NSString *defaultButtonTitle = nil;
|
||||
NSString *otherButtonTitle = nil;
|
||||
NSTimeInterval timeout = 60.0; // timeout value for the user notification
|
||||
|
||||
// Get the localized alert strings
|
||||
// If we're going to submit a report, prompt the user if this is okay.
|
||||
// Otherwise, just let them know that the app crashed.
|
||||
|
||||
if (shouldSubmitReport) {
|
||||
NSString *msgFormat = NSLocalizedStringFromTableInBundle(@"msg",
|
||||
nil,
|
||||
bundle, @"");
|
||||
|
||||
[self setReportMessage:[NSString stringWithFormat:msgFormat, vendor]];
|
||||
|
||||
defaultButtonTitle = NSLocalizedStringFromTableInBundle(@"sendReportButton",
|
||||
nil, bundle, @"");
|
||||
otherButtonTitle = NSLocalizedStringFromTableInBundle(@"cancelButton", nil,
|
||||
bundle, @"");
|
||||
|
||||
// Nominally use the report interval
|
||||
timeout = [[parameters_ objectForKey:@BREAKPAD_REPORT_INTERVAL]
|
||||
floatValue];
|
||||
} else {
|
||||
[self setReportMessage:NSLocalizedStringFromTableInBundle(@"noSendMsg", nil,
|
||||
bundle, @"")];
|
||||
defaultButtonTitle = NSLocalizedStringFromTableInBundle(@"noSendButton",
|
||||
nil, bundle, @"");
|
||||
timeout = 60.0;
|
||||
}
|
||||
// show the notification for at least one minute
|
||||
if (timeout < 60.0) {
|
||||
timeout = 60.0;
|
||||
}
|
||||
|
||||
// Initialize Cocoa, needed to display the alert
|
||||
NSApplicationLoad();
|
||||
|
||||
int buttonPressed = NSAlertAlternateReturn;
|
||||
|
||||
if (shouldRequestComments) {
|
||||
BOOL didLoadNib = [NSBundle loadNibNamed:@"Breakpad" owner:self];
|
||||
if (didLoadNib) {
|
||||
// Append the request for comments to the |reportMessage| string.
|
||||
NSString *commentsMessage =
|
||||
NSLocalizedStringFromTableInBundle(@"commentsMsg", nil, bundle, @"");
|
||||
[self setReportMessage:[NSString stringWithFormat:@"%@\n\n%@",
|
||||
[self reportMessage],
|
||||
commentsMessage]];
|
||||
|
||||
// Add the request for email address.
|
||||
[self setEmailMessage:
|
||||
NSLocalizedStringFromTableInBundle(@"emailMsg", nil, bundle, @"")];
|
||||
|
||||
// Run the alert
|
||||
buttonPressed = [self runModalWindow:alertWindow withTimeout:timeout];
|
||||
|
||||
// Extract info from the user into the parameters_ dictionary
|
||||
if ([self commentsValue]) {
|
||||
[parameters_ setObject:[self commentsValue]
|
||||
forKey:@BREAKPAD_COMMENTS];
|
||||
}
|
||||
if ([self emailValue]) {
|
||||
[parameters_ setObject:[self emailValue]
|
||||
forKey:@BREAKPAD_EMAIL];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Create an alert panel to tell the user something happened
|
||||
NSPanel* alert = NSGetAlertPanel([self headerMessage],
|
||||
[self reportMessage],
|
||||
defaultButtonTitle,
|
||||
otherButtonTitle, nil);
|
||||
|
||||
// Pop the alert with an automatic timeout, and wait for the response
|
||||
buttonPressed = [self runModalWindow:alert withTimeout:timeout];
|
||||
|
||||
// Release the panel memory
|
||||
NSReleaseAlertPanel(alert);
|
||||
}
|
||||
return buttonPressed == NSAlertDefaultReturn;
|
||||
}
|
||||
|
||||
- (int)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout {
|
||||
// Queue a |stopModal| message to be performed in |timeout| seconds.
|
||||
[NSApp performSelector:@selector(stopModal)
|
||||
withObject:nil
|
||||
afterDelay:timeout];
|
||||
|
||||
// Run the window modally and wait for either a |stopModal| message or a
|
||||
// button click.
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
int returnMethod = [NSApp runModalForWindow:window];
|
||||
|
||||
// Cancel the pending |stopModal| message.
|
||||
if (returnMethod != NSRunStoppedResponse) {
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:NSApp
|
||||
selector:@selector(stopModal)
|
||||
object:nil];
|
||||
}
|
||||
return returnMethod;
|
||||
}
|
||||
|
||||
- (IBAction)sendReport:(id)sender {
|
||||
[alertWindow orderOut:self];
|
||||
// Use NSAlertDefaultReturn so that the return value of |runModalWithWindow|
|
||||
// matches the AppKit function NSRunAlertPanel()
|
||||
[NSApp stopModalWithCode:NSAlertDefaultReturn];
|
||||
}
|
||||
|
||||
// UI Button Actions
|
||||
//=============================================================================
|
||||
- (IBAction)cancel:(id)sender {
|
||||
[alertWindow orderOut:self];
|
||||
// Use NSAlertDefaultReturn so that the return value of |runModalWithWindow|
|
||||
// matches the AppKit function NSRunAlertPanel()
|
||||
[NSApp stopModalWithCode:NSAlertAlternateReturn];
|
||||
}
|
||||
|
||||
- (IBAction)showPrivacyPolicy:(id)sender {
|
||||
// Get the localized privacy policy URL and open it in the default browser.
|
||||
NSURL* privacyPolicyURL =
|
||||
[NSURL URLWithString:NSLocalizedStringFromTableInBundle(
|
||||
@"privacyPolicyURL", nil, [NSBundle mainBundle], @"")];
|
||||
[[NSWorkspace sharedWorkspace] openURL:privacyPolicyURL];
|
||||
}
|
||||
|
||||
// Text Field Delegate Methods
|
||||
//=============================================================================
|
||||
- (BOOL)control:(NSControl*)control textView:(NSTextView*)textView
|
||||
doCommandBySelector:(SEL)commandSelector {
|
||||
BOOL result = NO;
|
||||
// If the user has entered text, don't end editing on "return"
|
||||
if (commandSelector == @selector(insertNewline:)
|
||||
&& [[textView string] length] > 0) {
|
||||
[textView insertNewlineIgnoringFieldEditor:self];
|
||||
result = YES;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Accessors
|
||||
//=============================================================================
|
||||
- (NSString *)headerMessage {
|
||||
return [[headerMessage_ retain] autorelease];
|
||||
}
|
||||
|
||||
- (void)setHeaderMessage:(NSString *)value {
|
||||
if (headerMessage_ != value) {
|
||||
[headerMessage_ autorelease];
|
||||
headerMessage_ = [value copy];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)reportMessage {
|
||||
return [[reportMessage_ retain] autorelease];
|
||||
}
|
||||
|
||||
- (void)setReportMessage:(NSString *)value {
|
||||
if (reportMessage_ != value) {
|
||||
[reportMessage_ autorelease];
|
||||
reportMessage_ = [value copy];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)commentsValue {
|
||||
return [[commentsValue_ retain] autorelease];
|
||||
}
|
||||
|
||||
- (void)setCommentsValue:(NSString *)value {
|
||||
if (commentsValue_ != value) {
|
||||
[commentsValue_ autorelease];
|
||||
commentsValue_ = [value copy];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)emailMessage {
|
||||
return [[emailMessage_ retain] autorelease];
|
||||
}
|
||||
|
||||
- (void)setEmailMessage:(NSString *)value {
|
||||
if (emailMessage_ != value) {
|
||||
[emailMessage_ autorelease];
|
||||
emailMessage_ = [value copy];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)emailValue {
|
||||
return [[emailValue_ retain] autorelease];
|
||||
}
|
||||
|
||||
- (void)setEmailValue:(NSString *)value {
|
||||
if (emailValue_ != value) {
|
||||
[emailValue_ autorelease];
|
||||
emailValue_ = [value copy];
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
- (BOOL)shouldSubmitReport {
|
||||
float interval = [[parameters_ objectForKey:@BREAKPAD_REPORT_INTERVAL]
|
||||
floatValue];
|
||||
NSString *program = [parameters_ objectForKey:@BREAKPAD_PRODUCT];
|
||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||
NSMutableDictionary *programDict =
|
||||
[NSMutableDictionary dictionaryWithDictionary:[ud dictionaryForKey:program]];
|
||||
NSNumber *lastTimeNum = [programDict objectForKey:kLastSubmission];
|
||||
NSTimeInterval lastTime = lastTimeNum ? [lastTimeNum floatValue] : 0;
|
||||
NSTimeInterval now = CFAbsoluteTimeGetCurrent();
|
||||
NSTimeInterval spanSeconds = (now - lastTime);
|
||||
|
||||
[programDict setObject:[NSNumber numberWithFloat:now] forKey:kLastSubmission];
|
||||
[ud setObject:programDict forKey:program];
|
||||
[ud synchronize];
|
||||
|
||||
// If we've specified an interval and we're within that time, don't ask the
|
||||
// user if we should report
|
||||
GTMLoggerDebug(@"Reporter Interval: %f", interval);
|
||||
if (interval > spanSeconds) {
|
||||
GTMLoggerDebug(@"Within throttling interval, not sending report");
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
- (void)report {
|
||||
NSURL *url = [NSURL URLWithString:[parameters_ objectForKey:@BREAKPAD_URL]];
|
||||
HTTPMultipartUpload *upload = [[HTTPMultipartUpload alloc] initWithURL:url];
|
||||
NSMutableDictionary *uploadParameters = [NSMutableDictionary dictionary];
|
||||
|
||||
// Set the known parameters. This should be kept up to date with the
|
||||
// parameters defined in the Breakpad.h list of parameters. The intent
|
||||
// is so that if there's a parameter that's not in this list, we consider
|
||||
// it to be a "user-defined" parameter and we'll upload it to the server.
|
||||
NSSet *knownParameters =
|
||||
[NSSet setWithObjects:@kReporterMinidumpDirectoryKey,
|
||||
@kReporterMinidumpIDKey, @BREAKPAD_PRODUCT_DISPLAY,
|
||||
@BREAKPAD_PRODUCT, @BREAKPAD_VERSION, @BREAKPAD_URL,
|
||||
@BREAKPAD_REPORT_INTERVAL, @BREAKPAD_SKIP_CONFIRM,
|
||||
@BREAKPAD_SEND_AND_EXIT, @BREAKPAD_REPORTER_EXE_LOCATION,
|
||||
@BREAKPAD_INSPECTOR_LOCATION, @BREAKPAD_LOGFILES,
|
||||
@BREAKPAD_LOGFILE_UPLOAD_SIZE, @BREAKPAD_EMAIL,
|
||||
@BREAKPAD_REQUEST_COMMENTS, @BREAKPAD_COMMENTS,
|
||||
@BREAKPAD_VENDOR, nil];
|
||||
|
||||
// Add parameters
|
||||
[uploadParameters setObject:[parameters_ objectForKey:@BREAKPAD_PRODUCT]
|
||||
forKey:@"prod"];
|
||||
[uploadParameters setObject:[parameters_ objectForKey:@BREAKPAD_VERSION]
|
||||
forKey:@"ver"];
|
||||
|
||||
if ([parameters_ objectForKey:@BREAKPAD_EMAIL]) {
|
||||
[uploadParameters setObject:[parameters_ objectForKey:@BREAKPAD_EMAIL] forKey:@"email"];
|
||||
}
|
||||
|
||||
NSString* comments = [parameters_ objectForKey:@BREAKPAD_COMMENTS];
|
||||
if (comments != nil) {
|
||||
[uploadParameters setObject:comments forKey:@"comments"];
|
||||
}
|
||||
// Add any user parameters
|
||||
NSArray *parameterKeys = [parameters_ allKeys];
|
||||
int keyCount = [parameterKeys count];
|
||||
for (int i = 0; i < keyCount; ++i) {
|
||||
NSString *key = [parameterKeys objectAtIndex:i];
|
||||
if (![knownParameters containsObject:key] &&
|
||||
![key hasPrefix:@BREAKPAD_LOGFILE_KEY_PREFIX])
|
||||
[uploadParameters setObject:[parameters_ objectForKey:key] forKey:key];
|
||||
}
|
||||
[upload setParameters:uploadParameters];
|
||||
// Add minidump file
|
||||
if (minidumpContents_) {
|
||||
[upload addFileContents:minidumpContents_ name:@"upload_file_minidump"];
|
||||
|
||||
// Send it
|
||||
NSError *error = nil;
|
||||
NSData *data = [upload send:&error];
|
||||
NSString *result = [[NSString alloc] initWithData:data
|
||||
encoding:NSUTF8StringEncoding];
|
||||
const char *reportID = "ERR";
|
||||
|
||||
if (error)
|
||||
fprintf(stderr, "Breakpad Reporter: Send Error: %s\n",
|
||||
[[error description] UTF8String]);
|
||||
else
|
||||
reportID = [result UTF8String];
|
||||
|
||||
// rename the minidump file according to the id returned from the server
|
||||
NSString *minidumpDir = [parameters_ objectForKey:@kReporterMinidumpDirectoryKey];
|
||||
NSString *minidumpID = [parameters_ objectForKey:@kReporterMinidumpIDKey];
|
||||
|
||||
NSString *srcString = [NSString stringWithFormat:@"%@/%@.dmp",
|
||||
minidumpDir, minidumpID];
|
||||
NSString *destString = [NSString stringWithFormat:@"%@/%s.dmp",
|
||||
minidumpDir, reportID];
|
||||
|
||||
const char *src = [srcString fileSystemRepresentation];
|
||||
const char *dest = [destString fileSystemRepresentation];
|
||||
|
||||
if (rename(src, dest) == 0) {
|
||||
fprintf(stderr, "Breakpad Reporter: Renamed %s to %s after successful " \
|
||||
"upload\n",src, dest);
|
||||
}
|
||||
else {
|
||||
// can't rename - don't worry - it's not important for users
|
||||
fprintf(stderr, "Breakpad Reporter: successful upload report ID = %s\n",
|
||||
reportID );
|
||||
}
|
||||
[result release];
|
||||
}
|
||||
|
||||
if (logFileData_) {
|
||||
HTTPMultipartUpload *logUpload = [[HTTPMultipartUpload alloc] initWithURL:url];
|
||||
|
||||
[uploadParameters setObject:@"log" forKey:@"type"];
|
||||
[logUpload setParameters:uploadParameters];
|
||||
[logUpload addFileContents:logFileData_ name:@"log"];
|
||||
|
||||
NSError *error = nil;
|
||||
NSData *data = [logUpload send:&error];
|
||||
NSString *result = [[NSString alloc] initWithData:data
|
||||
encoding:NSUTF8StringEncoding];
|
||||
[result release];
|
||||
[logUpload release];
|
||||
}
|
||||
|
||||
[upload release];
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
- (void)dealloc {
|
||||
[parameters_ release];
|
||||
[minidumpContents_ release];
|
||||
[logFileData_ release];
|
||||
[super dealloc];
|
||||
}
|
||||
@end
|
||||
|
||||
//=============================================================================
|
||||
int main(int argc, const char *argv[]) {
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
// The expectation is that there will be one argument which is the path
|
||||
// to the configuration file
|
||||
if (argc != 2) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Open the file before (potentially) switching to console user
|
||||
int configFile = open(argv[1], O_RDONLY, 0600);
|
||||
|
||||
// we want to avoid a build-up of old config files even if they
|
||||
// have been incorrectly written by the framework
|
||||
unlink(argv[1]);
|
||||
|
||||
if (configFile == -1) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
Reporter *reporter = [[Reporter alloc] initWithConfigurationFD:configFile];
|
||||
|
||||
// Gather the configuration data
|
||||
if (![reporter readConfigurationData]) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Read the minidump into memory before we (potentially) switch from the
|
||||
// root user
|
||||
[reporter readMinidumpData];
|
||||
|
||||
[reporter readLogFileData];
|
||||
|
||||
// only submit a report if we have not recently crashed in the past
|
||||
BOOL shouldSubmitReport = [reporter shouldSubmitReport];
|
||||
BOOL okayToSend = NO;
|
||||
|
||||
// ask user if we should send
|
||||
if (shouldSubmitReport) {
|
||||
okayToSend = [reporter askUserPermissionToSend:shouldSubmitReport];
|
||||
}
|
||||
|
||||
// If we're running as root, switch over to nobody
|
||||
if (getuid() == 0 || geteuid() == 0) {
|
||||
struct passwd *pw = getpwnam("nobody");
|
||||
|
||||
// If we can't get a non-root uid, don't send the report
|
||||
if (!pw)
|
||||
exit(0);
|
||||
|
||||
if (setgid(pw->pw_gid) == -1)
|
||||
exit(0);
|
||||
|
||||
if (setuid(pw->pw_uid) == -1)
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (okayToSend && shouldSubmitReport) {
|
||||
[reporter report];
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
[reporter release];
|
||||
[pool release];
|
||||
|
||||
return 0;
|
||||
}
|
||||
65
src/client/mac/testapp/Controller.h
Normal file
65
src/client/mac/testapp/Controller.h
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#import <Breakpad/Breakpad.h>
|
||||
|
||||
enum BreakpadForkBehavior {
|
||||
DONOTHING = 0,
|
||||
UNINSTALL,
|
||||
RESETEXCEPTIONPORT
|
||||
};
|
||||
|
||||
enum BreakpadForkTestCrashPoint {
|
||||
DURINGLAUNCH = 5,
|
||||
AFTERLAUNCH = 6,
|
||||
BETWEENFORKEXEC = 7
|
||||
};
|
||||
|
||||
@interface Controller : NSObject {
|
||||
IBOutlet NSWindow *window_;
|
||||
IBOutlet NSWindow *forkTestOptions_;
|
||||
|
||||
BreakpadRef breakpad_;
|
||||
|
||||
enum BreakpadForkBehavior bpForkOption;
|
||||
|
||||
BOOL useVFork;
|
||||
enum BreakpadForkTestCrashPoint progCrashPoint;
|
||||
}
|
||||
|
||||
- (IBAction)crash:(id)sender;
|
||||
- (IBAction)forkTestOptions:(id)sender;
|
||||
- (IBAction)forkTestGo:(id)sender;
|
||||
- (IBAction)showForkTestWindow:(id) sender;
|
||||
- (void)generateReportWithoutCrash:(id)sender;
|
||||
- (void)awakeFromNib;
|
||||
|
||||
@end
|
||||
257
src/client/mac/testapp/Controller.m
Normal file
257
src/client/mac/testapp/Controller.m
Normal file
@@ -0,0 +1,257 @@
|
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import <Breakpad/Breakpad.h>
|
||||
|
||||
#import "Controller.h"
|
||||
#import "TestClass.h"
|
||||
#include <unistd.h>
|
||||
#include <mach/mach.h>
|
||||
|
||||
@implementation Controller
|
||||
|
||||
- (void)causeCrash {
|
||||
float *aPtr = nil;
|
||||
NSLog(@"Crash!");
|
||||
NSLog(@"Bad programmer: %f", *aPtr);
|
||||
}
|
||||
|
||||
- (void)generateReportWithoutCrash:(id)sender {
|
||||
BreakpadGenerateAndSendReport(breakpad_);
|
||||
}
|
||||
|
||||
- (IBAction)showForkTestWindow:(id) sender {
|
||||
[forkTestOptions_ setIsVisible:YES];
|
||||
}
|
||||
|
||||
- (IBAction)forkTestOptions:(id)sender {
|
||||
int tag = [[sender selectedCell] tag];
|
||||
NSLog(@"sender tag: %d", tag);
|
||||
if (tag <= 2) {
|
||||
bpForkOption = tag;
|
||||
}
|
||||
|
||||
if (tag == 3) {
|
||||
useVFork = NO;
|
||||
}
|
||||
|
||||
if (tag == 4) {
|
||||
useVFork = YES;
|
||||
}
|
||||
|
||||
if (tag >= 5 && tag <= 7) {
|
||||
progCrashPoint = tag;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
- (IBAction)forkTestGo:(id)sender {
|
||||
|
||||
NSString *resourcePath = [[NSBundle bundleForClass:
|
||||
[self class]] resourcePath];
|
||||
NSString *execProgname;
|
||||
if (progCrashPoint == DURINGLAUNCH) {
|
||||
execProgname = [resourcePath stringByAppendingString:@"/crashduringload"];
|
||||
} else if (progCrashPoint == AFTERLAUNCH) {
|
||||
execProgname = [resourcePath stringByAppendingString:@"/crashInMain"];
|
||||
}
|
||||
|
||||
const char *progName = NULL;
|
||||
if (progCrashPoint != BETWEENFORKEXEC) {
|
||||
progName = [execProgname UTF8String];
|
||||
}
|
||||
|
||||
int pid;
|
||||
|
||||
if (bpForkOption == UNINSTALL) {
|
||||
BreakpadRelease(breakpad_);
|
||||
}
|
||||
|
||||
if (useVFork) {
|
||||
pid = vfork();
|
||||
} else {
|
||||
pid = fork();
|
||||
}
|
||||
|
||||
if (pid == 0) {
|
||||
sleep(3);
|
||||
NSLog(@"Child continuing");
|
||||
FILE *fd = fopen("/tmp/childlog.txt","wt");
|
||||
kern_return_t kr;
|
||||
if (bpForkOption == RESETEXCEPTIONPORT) {
|
||||
kr = task_set_exception_ports(mach_task_self(),
|
||||
EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION |
|
||||
EXC_MASK_ARITHMETIC | EXC_MASK_BREAKPOINT,
|
||||
MACH_PORT_NULL,
|
||||
EXCEPTION_DEFAULT,
|
||||
THREAD_STATE_NONE);
|
||||
fprintf(fd,"task_set_exception_ports returned %d\n", kr);
|
||||
}
|
||||
|
||||
if (progCrashPoint == BETWEENFORKEXEC) {
|
||||
fprintf(fd,"crashing post-fork\n");
|
||||
int *a = NULL;
|
||||
printf("%d\n",*a++);
|
||||
}
|
||||
|
||||
fprintf(fd,"about to call exec with %s\n", progName);
|
||||
fclose(fd);
|
||||
int i = execl(progName, progName, NULL);
|
||||
fprintf(fd, "exec returned! %d\n", i);
|
||||
fclose(fd);
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)crash:(id)sender {
|
||||
int tag = [sender tag];
|
||||
|
||||
if (tag == 1) {
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:self];
|
||||
[self performSelector:@selector(causeCrash) withObject:nil afterDelay:10];
|
||||
[sender setState:NSOnState];
|
||||
return;
|
||||
}
|
||||
|
||||
if (tag == 2 && breakpad_) {
|
||||
BreakpadRelease(breakpad_);
|
||||
breakpad_ = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
[self causeCrash];
|
||||
}
|
||||
|
||||
- (void)anotherThread {
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
TestClass *tc = [[TestClass alloc] init];
|
||||
|
||||
[tc wait];
|
||||
|
||||
[pool release];
|
||||
}
|
||||
|
||||
- (void)awakeFromNib {
|
||||
NSBundle *bundle = [NSBundle mainBundle];
|
||||
NSDictionary *info = [bundle infoDictionary];
|
||||
|
||||
|
||||
breakpad_ = BreakpadCreate(info);
|
||||
|
||||
// Do some unit tests with keys
|
||||
// first a series of bogus values
|
||||
BreakpadSetKeyValue(breakpad_, nil, @"bad2");
|
||||
BreakpadSetKeyValue(nil, @"bad3", @"bad3");
|
||||
|
||||
// Now some good ones
|
||||
BreakpadSetKeyValue(breakpad_,@"key1", @"value1");
|
||||
BreakpadSetKeyValue(breakpad_,@"key2", @"value2");
|
||||
BreakpadSetKeyValue(breakpad_,@"key3", @"value3");
|
||||
|
||||
// Look for a bogus one that we didn't try to set
|
||||
NSString *test = BreakpadKeyValue(breakpad_, @"bad4");
|
||||
if (test) {
|
||||
NSLog(@"Bad BreakpadKeyValue (bad4)");
|
||||
}
|
||||
|
||||
// Look for a bogus one we did try to set
|
||||
test = BreakpadKeyValue(breakpad_, @"bad1");
|
||||
if (test) {
|
||||
NSLog(@"Bad BreakpadKeyValue (bad1)");
|
||||
}
|
||||
|
||||
// Test some bad args for BreakpadKeyValue
|
||||
test = BreakpadKeyValue(nil, @"bad5");
|
||||
if (test) {
|
||||
NSLog(@"Bad BreakpadKeyValue (bad5)");
|
||||
}
|
||||
|
||||
test = BreakpadKeyValue(breakpad_, nil);
|
||||
if (test) {
|
||||
NSLog(@"Bad BreakpadKeyValue (nil)");
|
||||
}
|
||||
|
||||
// Find some we did set
|
||||
test = BreakpadKeyValue(breakpad_, @"key1");
|
||||
if (![test isEqualToString:@"value1"]) {
|
||||
NSLog(@"Can't find BreakpadKeyValue (key1)");
|
||||
}
|
||||
test = BreakpadKeyValue(breakpad_, @"key2");
|
||||
if (![test isEqualToString:@"value2"]) {
|
||||
NSLog(@"Can't find BreakpadKeyValue (key2)");
|
||||
}
|
||||
test = BreakpadKeyValue(breakpad_, @"key3");
|
||||
if (![test isEqualToString:@"value3"]) {
|
||||
NSLog(@"Can't find BreakpadKeyValue (key3)");
|
||||
}
|
||||
|
||||
// Bad args for BreakpadRemoveKeyValue
|
||||
BreakpadRemoveKeyValue(nil, @"bad6");
|
||||
BreakpadRemoveKeyValue(breakpad_, nil);
|
||||
|
||||
// Remove one that is valid
|
||||
BreakpadRemoveKeyValue(breakpad_, @"key3");
|
||||
|
||||
// Try and find it
|
||||
test = BreakpadKeyValue(breakpad_, @"key3");
|
||||
if (test) {
|
||||
NSLog(@"Shouldn't find BreakpadKeyValue (key3)");
|
||||
}
|
||||
|
||||
// Try and remove it again
|
||||
BreakpadRemoveKeyValue(breakpad_, @"key3");
|
||||
|
||||
// Try removal by setting to nil
|
||||
BreakpadSetKeyValue(breakpad_,@"key2", nil);
|
||||
// Try and find it
|
||||
test = BreakpadKeyValue(breakpad_, @"key2");
|
||||
if (test) {
|
||||
NSLog(@"Shouldn't find BreakpadKeyValue (key2)");
|
||||
}
|
||||
|
||||
[NSThread detachNewThreadSelector:@selector(anotherThread)
|
||||
toTarget:self withObject:nil];
|
||||
|
||||
NSUserDefaults *args = [NSUserDefaults standardUserDefaults];
|
||||
|
||||
// If the user specified autocrash on the command line, toggle
|
||||
// Breakpad to not confirm and crash immediately. This is for
|
||||
// automated testing.
|
||||
if ([args boolForKey:@"autocrash"]) {
|
||||
BreakpadSetKeyValue(breakpad_,
|
||||
@BREAKPAD_SKIP_CONFIRM,
|
||||
@"YES");
|
||||
[self causeCrash];
|
||||
}
|
||||
|
||||
progCrashPoint = DURINGLAUNCH;
|
||||
[window_ center];
|
||||
[window_ makeKeyAndOrderFront:self];
|
||||
}
|
||||
|
||||
@end
|
||||
BIN
src/client/mac/testapp/English.lproj/InfoPlist.strings
Normal file
BIN
src/client/mac/testapp/English.lproj/InfoPlist.strings
Normal file
Binary file not shown.
47
src/client/mac/testapp/English.lproj/MainMenu.nib/classes.nib
generated
Normal file
47
src/client/mac/testapp/English.lproj/MainMenu.nib/classes.nib
generated
Normal file
@@ -0,0 +1,47 @@
|
||||
<?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>IBClasses</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>ACTIONS</key>
|
||||
<dict>
|
||||
<key>crash</key>
|
||||
<string>id</string>
|
||||
<key>forkTestGo</key>
|
||||
<string>id</string>
|
||||
<key>forkTestOptions</key>
|
||||
<string>id</string>
|
||||
<key>generateReportWithoutCrash</key>
|
||||
<string>id</string>
|
||||
<key>showForkTestWindow</key>
|
||||
<string>id</string>
|
||||
</dict>
|
||||
<key>CLASS</key>
|
||||
<string>Controller</string>
|
||||
<key>LANGUAGE</key>
|
||||
<string>ObjC</string>
|
||||
<key>OUTLETS</key>
|
||||
<dict>
|
||||
<key>forkTestOptions_</key>
|
||||
<string>NSWindow</string>
|
||||
<key>window_</key>
|
||||
<string>NSWindow</string>
|
||||
</dict>
|
||||
<key>SUPERCLASS</key>
|
||||
<string>NSObject</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CLASS</key>
|
||||
<string>FirstResponder</string>
|
||||
<key>LANGUAGE</key>
|
||||
<string>ObjC</string>
|
||||
<key>SUPERCLASS</key>
|
||||
<string>NSObject</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>IBVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
22
src/client/mac/testapp/English.lproj/MainMenu.nib/info.nib
generated
Normal file
22
src/client/mac/testapp/English.lproj/MainMenu.nib/info.nib
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IBFramework Version</key>
|
||||
<string>670</string>
|
||||
<key>IBLastKnownRelativeProjectPath</key>
|
||||
<string>../GoogleBreakpadTest.xcodeproj</string>
|
||||
<key>IBOldestOS</key>
|
||||
<integer>5</integer>
|
||||
<key>IBOpenObjects</key>
|
||||
<array>
|
||||
<integer>221</integer>
|
||||
<integer>29</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>IBSystem Version</key>
|
||||
<string>9F33</string>
|
||||
<key>targetFramework</key>
|
||||
<string>IBCocoaFramework</string>
|
||||
</dict>
|
||||
</plist>
|
||||
BIN
src/client/mac/testapp/English.lproj/MainMenu.nib/keyedobjects.nib
generated
Normal file
BIN
src/client/mac/testapp/English.lproj/MainMenu.nib/keyedobjects.nib
generated
Normal file
Binary file not shown.
46
src/client/mac/testapp/Info.plist
Normal file
46
src/client/mac/testapp/Info.plist
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>bomb</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.Google.BreakpadTest</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>BreakpadProductDisplay</key>
|
||||
<string>Google Breakpad Tester</string>
|
||||
<key>BreakpadProduct</key>
|
||||
<string>Breakpad_Tester</string>
|
||||
<key>BreakpadVersion</key>
|
||||
<string>1.2.3.4</string>
|
||||
<key>BreakpadReportInterval</key>
|
||||
<string>10</string>
|
||||
<key>BreakpadSkipConfirm</key>
|
||||
<string>NO</string>
|
||||
<key>BreakpadSendAndExit</key>
|
||||
<string>YES</string>
|
||||
<key>BreakpadRequestComments</key>
|
||||
<string>YES</string>
|
||||
<key>BreakpadVendor</key>
|
||||
<string>Foo Bar Corp, Incorporated, LTD, LLC</string>
|
||||
<key>LSUIElement</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
37
src/client/mac/testapp/TestClass.h
Normal file
37
src/client/mac/testapp/TestClass.h
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface TestClass : NSObject {
|
||||
}
|
||||
|
||||
- (void)wait;
|
||||
|
||||
@end
|
||||
95
src/client/mac/testapp/TestClass.mm
Normal file
95
src/client/mac/testapp/TestClass.mm
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#import "TestClass.h"
|
||||
|
||||
struct AStruct {
|
||||
int x;
|
||||
float y;
|
||||
double z;
|
||||
};
|
||||
|
||||
class InternalTestClass {
|
||||
public:
|
||||
InternalTestClass(int a) : a_(a) {}
|
||||
~InternalTestClass() {}
|
||||
|
||||
void snooze(float a);
|
||||
void snooze(int a);
|
||||
int snooze(int a, float b);
|
||||
|
||||
protected:
|
||||
int a_;
|
||||
AStruct s_;
|
||||
|
||||
static void InternalFunction(AStruct &s);
|
||||
static float kStaticFloatValue;
|
||||
};
|
||||
|
||||
void InternalTestClass::snooze(float a) {
|
||||
InternalFunction(s_);
|
||||
sleep(a_ * a);
|
||||
}
|
||||
|
||||
void InternalTestClass::snooze(int a) {
|
||||
InternalFunction(s_);
|
||||
sleep(a_ * a);
|
||||
}
|
||||
|
||||
int InternalTestClass::snooze(int a, float b) {
|
||||
InternalFunction(s_);
|
||||
sleep(a_ * a * b);
|
||||
|
||||
return 33;
|
||||
}
|
||||
|
||||
void InternalTestClass::InternalFunction(AStruct &s) {
|
||||
s.x = InternalTestClass::kStaticFloatValue;
|
||||
}
|
||||
|
||||
float InternalTestClass::kStaticFloatValue = 42;
|
||||
|
||||
static float PlainOldFunction() {
|
||||
return 3.14145;
|
||||
}
|
||||
|
||||
@implementation TestClass
|
||||
|
||||
- (void)wait {
|
||||
InternalTestClass t(10);
|
||||
float z = PlainOldFunction();
|
||||
|
||||
while (1) {
|
||||
t.snooze(z);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
BIN
src/client/mac/testapp/bomb.icns
Normal file
BIN
src/client/mac/testapp/bomb.icns
Normal file
Binary file not shown.
BIN
src/client/mac/testapp/crashInMain
Executable file
BIN
src/client/mac/testapp/crashInMain
Executable file
Binary file not shown.
BIN
src/client/mac/testapp/crashduringload
Executable file
BIN
src/client/mac/testapp/crashduringload
Executable file
Binary file not shown.
34
src/client/mac/testapp/main.m
Normal file
34
src/client/mac/testapp/main.m
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
return NSApplicationMain(argc, (const char **) argv);
|
||||
}
|
||||
40
src/client/mac/tests/SimpleStringDictionaryTest.h
Normal file
40
src/client/mac/tests/SimpleStringDictionaryTest.h
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) 2008, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import <GTMSenTestCase.h>
|
||||
#import "SimpleStringDictionary.h"
|
||||
|
||||
@interface SimpleStringDictionaryTest : GTMTestCase {
|
||||
|
||||
}
|
||||
|
||||
- (void)testKeyValueEntry;
|
||||
- (void)testSimpleStringDictionary;
|
||||
- (void)testSimpleStringDictionaryIterator;
|
||||
@end
|
||||
243
src/client/mac/tests/SimpleStringDictionaryTest.mm
Normal file
243
src/client/mac/tests/SimpleStringDictionaryTest.mm
Normal file
@@ -0,0 +1,243 @@
|
||||
// Copyright (c) 2008, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import "SimpleStringDictionaryTest.h"
|
||||
#import "SimpleStringDictionary.h"
|
||||
|
||||
using google_breakpad::KeyValueEntry;
|
||||
using google_breakpad::SimpleStringDictionary;
|
||||
using google_breakpad::SimpleStringDictionaryIterator;
|
||||
|
||||
@implementation SimpleStringDictionaryTest
|
||||
|
||||
//==============================================================================
|
||||
- (void)testKeyValueEntry {
|
||||
KeyValueEntry entry;
|
||||
|
||||
// Verify that initial state is correct
|
||||
STAssertFalse(entry.IsActive(), @"Initial key value entry is active!");
|
||||
STAssertEquals(strlen(entry.GetKey()), (size_t)0, @"Empty key value did not "
|
||||
@"have length 0");
|
||||
STAssertEquals(strlen(entry.GetValue()), (size_t)0, @"Empty key value did not "
|
||||
@"have length 0");
|
||||
|
||||
// Try setting a key/value and then verify
|
||||
entry.SetKeyValue("key1", "value1");
|
||||
STAssertEqualCStrings(entry.GetKey(), "key1", @"key was not equal to key1");
|
||||
STAssertEqualCStrings(entry.GetValue(), "value1", @"value was not equal");
|
||||
|
||||
// Try setting a new value
|
||||
entry.SetValue("value3");
|
||||
|
||||
// Make sure the new value took
|
||||
STAssertEqualCStrings(entry.GetValue(), "value3", @"value was not equal");
|
||||
|
||||
// Make sure the key didn't change
|
||||
STAssertEqualCStrings(entry.GetKey(), "key1", @"key changed after setting "
|
||||
@"value!");
|
||||
|
||||
// Try setting a new key/value and then verify
|
||||
entry.SetKeyValue("key2", "value2");
|
||||
STAssertEqualCStrings(entry.GetKey(), "key2", @"New key was not equal to "
|
||||
@"key2");
|
||||
STAssertEqualCStrings(entry.GetValue(), "value2", @"New value was not equal "
|
||||
@"to value2");
|
||||
|
||||
// Clear the entry and verify the key and value are empty strings
|
||||
entry.Clear();
|
||||
STAssertFalse(entry.IsActive(), @"Key value clear did not clear object");
|
||||
STAssertEquals(strlen(entry.GetKey()), (size_t)0, @"Length of cleared key "
|
||||
@"was not 0");
|
||||
STAssertEquals(strlen(entry.GetValue()), (size_t)0, @"Length of cleared "
|
||||
@"value was not 0!");
|
||||
}
|
||||
|
||||
- (void)testEmptyKeyValueCombos {
|
||||
KeyValueEntry entry;
|
||||
entry.SetKeyValue(NULL, NULL);
|
||||
STAssertEqualCStrings(entry.GetKey(), "", @"Setting NULL key did not return "
|
||||
@"empty key!");
|
||||
STAssertEqualCStrings(entry.GetValue(), "", @"Setting NULL value did not "
|
||||
@"set empty string value!");
|
||||
}
|
||||
|
||||
|
||||
//==============================================================================
|
||||
- (void)testSimpleStringDictionary {
|
||||
// Make a new dictionary
|
||||
SimpleStringDictionary *dict = new SimpleStringDictionary();
|
||||
STAssertTrue(dict != NULL, nil);
|
||||
|
||||
// try passing in NULL for key
|
||||
//dict->SetKeyValue(NULL, "bad"); // causes assert() to fire
|
||||
|
||||
// Set three distinct values on three keys
|
||||
dict->SetKeyValue("key1", "value1");
|
||||
dict->SetKeyValue("key2", "value2");
|
||||
dict->SetKeyValue("key3", "value3");
|
||||
|
||||
STAssertTrue(!strcmp(dict->GetValueForKey("key1"), "value1"), nil);
|
||||
STAssertTrue(!strcmp(dict->GetValueForKey("key2"), "value2"), nil);
|
||||
STAssertTrue(!strcmp(dict->GetValueForKey("key3"), "value3"), nil);
|
||||
STAssertEquals(dict->GetCount(), 3, @"GetCount did not return 3");
|
||||
// try an unknown key
|
||||
STAssertTrue(dict->GetValueForKey("key4") == NULL, nil);
|
||||
|
||||
// try a NULL key
|
||||
//STAssertTrue(dict->GetValueForKey(NULL) == NULL, nil); // asserts
|
||||
|
||||
// Remove a key
|
||||
dict->RemoveKey("key3");
|
||||
|
||||
// Now make sure it's not there anymore
|
||||
STAssertTrue(dict->GetValueForKey("key3") == NULL, nil);
|
||||
|
||||
// Remove a NULL key
|
||||
//dict->RemoveKey(NULL); // will cause assert() to fire
|
||||
|
||||
// Remove by setting value to NULL
|
||||
dict->SetKeyValue("key2", NULL);
|
||||
|
||||
// Now make sure it's not there anymore
|
||||
STAssertTrue(dict->GetValueForKey("key2") == NULL, nil);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// The idea behind this test is to add a bunch of values to the dictionary,
|
||||
// remove some in the middle, then add a few more in. We then create a
|
||||
// SimpleStringDictionaryIterator and iterate through the dictionary, taking
|
||||
// note of the key/value pairs we see. We then verify that it iterates
|
||||
// through exactly the number of key/value pairs we expect, and that they
|
||||
// match one-for-one with what we would expect. In all cases we're setting
|
||||
// key value pairs of the form:
|
||||
//
|
||||
// key<n>/value<n> (like key0/value0, key17,value17, etc.)
|
||||
//
|
||||
- (void)testSimpleStringDictionaryIterator {
|
||||
SimpleStringDictionary *dict = new SimpleStringDictionary();
|
||||
STAssertTrue(dict != NULL, nil);
|
||||
|
||||
char key[KeyValueEntry::MAX_STRING_STORAGE_SIZE];
|
||||
char value[KeyValueEntry::MAX_STRING_STORAGE_SIZE];
|
||||
|
||||
const int kDictionaryCapacity = SimpleStringDictionary::MAX_NUM_ENTRIES;
|
||||
const int kPartitionIndex = kDictionaryCapacity - 5;
|
||||
|
||||
// We assume at least this size in the tests below
|
||||
STAssertTrue(kDictionaryCapacity >= 64, nil);
|
||||
|
||||
// We'll keep track of the number of key/value pairs we think should
|
||||
// be in the dictionary
|
||||
int expectedDictionarySize = 0;
|
||||
|
||||
// Set a bunch of key/value pairs like key0/value0, key1/value1, ...
|
||||
for (int i = 0; i < kPartitionIndex; ++i) {
|
||||
sprintf(key, "key%d", i);
|
||||
sprintf(value, "value%d", i);
|
||||
dict->SetKeyValue(key, value);
|
||||
}
|
||||
expectedDictionarySize = kPartitionIndex;
|
||||
|
||||
// set a couple of the keys twice (with the same value) - should be nop
|
||||
dict->SetKeyValue("key2", "value2");
|
||||
dict->SetKeyValue("key4", "value4");
|
||||
dict->SetKeyValue("key15", "value15");
|
||||
|
||||
// Remove some random elements in the middle
|
||||
dict->RemoveKey("key7");
|
||||
dict->RemoveKey("key18");
|
||||
dict->RemoveKey("key23");
|
||||
dict->RemoveKey("key31");
|
||||
expectedDictionarySize -= 4; // we just removed four key/value pairs
|
||||
|
||||
// Set some more key/value pairs like key59/value59, key60/value60, ...
|
||||
for (int i = kPartitionIndex; i < kDictionaryCapacity; ++i) {
|
||||
sprintf(key, "key%d", i);
|
||||
sprintf(value, "value%d", i);
|
||||
dict->SetKeyValue(key, value);
|
||||
}
|
||||
expectedDictionarySize += kDictionaryCapacity - kPartitionIndex;
|
||||
|
||||
// Now create an iterator on the dictionary
|
||||
SimpleStringDictionaryIterator iter(*dict);
|
||||
|
||||
// We then verify that it iterates through exactly the number of
|
||||
// key/value pairs we expect, and that they match one-for-one with what we
|
||||
// would expect. The ordering of the iteration does not matter...
|
||||
|
||||
// used to keep track of number of occurrences found for key/value pairs
|
||||
int count[kDictionaryCapacity];
|
||||
memset(count, 0, sizeof(count));
|
||||
|
||||
int totalCount = 0;
|
||||
|
||||
const KeyValueEntry *entry;
|
||||
|
||||
while ((entry = iter.Next())) {
|
||||
totalCount++;
|
||||
|
||||
// Extract keyNumber from a string of the form key<keyNumber>
|
||||
int keyNumber;
|
||||
sscanf(entry->GetKey(), "key%d", &keyNumber);
|
||||
|
||||
// Extract valueNumber from a string of the form value<valueNumber>
|
||||
int valueNumber;
|
||||
sscanf(entry->GetValue(), "value%d", &valueNumber);
|
||||
|
||||
// The value number should equal the key number since that's how we set them
|
||||
STAssertTrue(keyNumber == valueNumber, nil);
|
||||
|
||||
// Key and value numbers should be in proper range:
|
||||
// 0 <= keyNumber < kDictionaryCapacity
|
||||
bool isKeyInGoodRange =
|
||||
(keyNumber >= 0 && keyNumber < kDictionaryCapacity);
|
||||
bool isValueInGoodRange =
|
||||
(valueNumber >= 0 && valueNumber < kDictionaryCapacity);
|
||||
STAssertTrue(isKeyInGoodRange, nil);
|
||||
STAssertTrue(isValueInGoodRange, nil);
|
||||
|
||||
if (isKeyInGoodRange && isValueInGoodRange) {
|
||||
++count[keyNumber];
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure each of the key/value pairs showed up exactly one time, except
|
||||
// for the ones which we removed.
|
||||
for (int i = 0; i < kDictionaryCapacity; ++i) {
|
||||
// Skip over key7, key18, key23, and key31, since we removed them
|
||||
if (!(i == 7 || i == 18 || i == 23 || i == 31)) {
|
||||
STAssertTrue(count[i] == 1, nil);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the number of iterations matches the expected dictionary size.
|
||||
STAssertTrue(totalCount == expectedDictionarySize, nil);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -126,17 +126,22 @@ static bool CompareFile(const char *path) {
|
||||
0x00000007, 0x06000007, 0x00000008, 0x07000008, 0x00000009, 0x08000009,
|
||||
0x0000000a, 0x0900000a, 0x0000000b, 0x00000000
|
||||
#else
|
||||
0x0000beef, 0x0000001e, 0x00000018, 0x00000020, 0x00000038, 0x00000000,
|
||||
0x00000018, 0x00690046, 0x00730072, 0x00200074, 0x00740053, 0x00690072,
|
||||
0x0067006e, 0x00000000, 0x0000001a, 0x00650053, 0x006f0063, 0x0064006e,
|
||||
0x00530020, 0x00720074, 0x006e0069, 0x00000067, 0x0001da00, 0x00000002,
|
||||
0x0002da01, 0x00000003, 0x0003da02, 0x00000004, 0x0004da03, 0x00000005,
|
||||
0x0005da04, 0x00000006, 0x0006da05, 0x00000007, 0x0007da06, 0x00000008,
|
||||
0x0008da07, 0x00000009, 0x0009da08, 0x0000000a, 0x000ada09, 0x0000000b,
|
||||
0x0000000a, 0x00018700, 0x00000002, 0x00028701, 0x00000003, 0x00038702,
|
||||
0x00000004, 0x00048703, 0x00000005, 0x00058704, 0x00000006, 0x00068705,
|
||||
0x00000007, 0x00078706, 0x00000008, 0x00088707, 0x00000009, 0x00098708,
|
||||
0x0000000a, 0x000a8709, 0x0000000b, 0x00000000,
|
||||
0x0000beef, 0x0000001e, 0x00000018, 0x00000020,
|
||||
0x00000038, 0x00000000, 0x00000018, 0x00690046,
|
||||
0x00730072, 0x00200074, 0x00740053, 0x00690072,
|
||||
0x0067006e, 0x00000000, 0x0000001a, 0x00650053,
|
||||
0x006f0063, 0x0064006e, 0x00530020, 0x00720074,
|
||||
0x006e0069, 0x00000067, 0x00011e00, 0x00000002,
|
||||
0x00021e01, 0x00000003, 0x00031e02, 0x00000004,
|
||||
0x00041e03, 0x00000005, 0x00051e04, 0x00000006,
|
||||
0x00061e05, 0x00000007, 0x00071e06, 0x00000008,
|
||||
0x00081e07, 0x00000009, 0x00091e08, 0x0000000a,
|
||||
0x000a1e09, 0x0000000b, 0x0000000a, 0x00011c00,
|
||||
0x00000002, 0x00021c01, 0x00000003, 0x00031c02,
|
||||
0x00000004, 0x00041c03, 0x00000005, 0x00051c04,
|
||||
0x00000006, 0x00061c05, 0x00000007, 0x00071c06,
|
||||
0x00000008, 0x00081c07, 0x00000009, 0x00091c08,
|
||||
0x0000000a, 0x000a1c09, 0x0000000b, 0x00000000,
|
||||
#endif
|
||||
};
|
||||
unsigned int expected_byte_count = sizeof(expected);
|
||||
@@ -156,7 +161,6 @@ static bool CompareFile(const char *path) {
|
||||
|
||||
printf("%d\n",b1 - (char*)buffer);
|
||||
|
||||
|
||||
ASSERT_EQ(memcmp(buffer, expected, expected_byte_count), 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user