mirror of
https://github.com/yuzu-emu/breakpad.git
synced 2024-12-23 15:15:37 +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:
parent
0abe34ce5d
commit
3ebdb1bd7a
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_)
|
if (self->use_minidump_write_mutex_)
|
||||||
pthread_mutex_unlock(&self->minidump_write_mutex_);
|
pthread_mutex_unlock(&self->minidump_write_mutex_);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// When forking a child process with the exception handler installed,
|
// When forking a child process with the exception handler installed,
|
||||||
// if the child crashes, it will send the exception back to the parent
|
// if the child crashes, it will send the exception back to the parent
|
||||||
// process. The check for task == self_task() ensures that only
|
// process. The check for task == self_task() ensures that only
|
||||||
|
|
|
@ -55,6 +55,7 @@ static void *SleepyFunction(void *) {
|
||||||
while (1) {
|
while (1) {
|
||||||
sleep(10000);
|
sleep(10000);
|
||||||
}
|
}
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Crasher() {
|
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());
|
fprintf(stdout, "Minidump: %s\n", path.c_str());
|
||||||
// Indicate that we've handled the callback
|
// Indicate that we've handled the callback
|
||||||
return true;
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char * const argv[]) {
|
int main(int argc, char * const argv[]) {
|
||||||
char buffer[PATH_MAX];
|
char buffer[PATH_MAX];
|
||||||
struct passwd *user = getpwuid(getuid());
|
|
||||||
|
|
||||||
// Home dir
|
// Home dir
|
||||||
snprintf(buffer, sizeof(buffer), "/Users/%s/Desktop/", user->pw_name);
|
snprintf(buffer, sizeof(buffer), "/tmp/");
|
||||||
|
|
||||||
string path(buffer);
|
string path(buffer);
|
||||||
ExceptionHandler eh(path, NULL, MDCallback, NULL, true);
|
ExceptionHandler eh(path, NULL, MDCallback, NULL, true);
|
||||||
|
@ -97,8 +97,8 @@ int main(int argc, char * const argv[]) {
|
||||||
perror("pthread_create");
|
perror("pthread_create");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dump a test
|
// // Dump a test
|
||||||
eh.WriteMinidump();
|
// eh.WriteMinidump();
|
||||||
|
|
||||||
// Test the handler
|
// Test the handler
|
||||||
SoonToCrash();
|
SoonToCrash();
|
||||||
|
|
|
@ -45,14 +45,13 @@ static bool doneWritingReport = false;
|
||||||
static void *Reporter(void *) {
|
static void *Reporter(void *) {
|
||||||
char buffer[PATH_MAX];
|
char buffer[PATH_MAX];
|
||||||
MinidumpGenerator md;
|
MinidumpGenerator md;
|
||||||
struct passwd *user = getpwuid(getuid());
|
|
||||||
|
|
||||||
// Write it to the desktop
|
// Write it to the desktop
|
||||||
snprintf(buffer,
|
snprintf(buffer,
|
||||||
sizeof(buffer),
|
sizeof(buffer),
|
||||||
"/Users/%s/Desktop/test.dmp",
|
"/tmp/test.dmp");
|
||||||
user->pw_name);
|
|
||||||
|
|
||||||
fprintf(stdout, "Writing %s\n", buffer);
|
fprintf(stdout, "Writing %s\n", buffer);
|
||||||
unlink(buffer);
|
unlink(buffer);
|
||||||
md.Write(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,
|
0x00000007, 0x06000007, 0x00000008, 0x07000008, 0x00000009, 0x08000009,
|
||||||
0x0000000a, 0x0900000a, 0x0000000b, 0x00000000
|
0x0000000a, 0x0900000a, 0x0000000b, 0x00000000
|
||||||
#else
|
#else
|
||||||
0x0000beef, 0x0000001e, 0x00000018, 0x00000020, 0x00000038, 0x00000000,
|
0x0000beef, 0x0000001e, 0x00000018, 0x00000020,
|
||||||
0x00000018, 0x00690046, 0x00730072, 0x00200074, 0x00740053, 0x00690072,
|
0x00000038, 0x00000000, 0x00000018, 0x00690046,
|
||||||
0x0067006e, 0x00000000, 0x0000001a, 0x00650053, 0x006f0063, 0x0064006e,
|
0x00730072, 0x00200074, 0x00740053, 0x00690072,
|
||||||
0x00530020, 0x00720074, 0x006e0069, 0x00000067, 0x0001da00, 0x00000002,
|
0x0067006e, 0x00000000, 0x0000001a, 0x00650053,
|
||||||
0x0002da01, 0x00000003, 0x0003da02, 0x00000004, 0x0004da03, 0x00000005,
|
0x006f0063, 0x0064006e, 0x00530020, 0x00720074,
|
||||||
0x0005da04, 0x00000006, 0x0006da05, 0x00000007, 0x0007da06, 0x00000008,
|
0x006e0069, 0x00000067, 0x00011e00, 0x00000002,
|
||||||
0x0008da07, 0x00000009, 0x0009da08, 0x0000000a, 0x000ada09, 0x0000000b,
|
0x00021e01, 0x00000003, 0x00031e02, 0x00000004,
|
||||||
0x0000000a, 0x00018700, 0x00000002, 0x00028701, 0x00000003, 0x00038702,
|
0x00041e03, 0x00000005, 0x00051e04, 0x00000006,
|
||||||
0x00000004, 0x00048703, 0x00000005, 0x00058704, 0x00000006, 0x00068705,
|
0x00061e05, 0x00000007, 0x00071e06, 0x00000008,
|
||||||
0x00000007, 0x00078706, 0x00000008, 0x00088707, 0x00000009, 0x00098708,
|
0x00081e07, 0x00000009, 0x00091e08, 0x0000000a,
|
||||||
0x0000000a, 0x000a8709, 0x0000000b, 0x00000000,
|
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
|
#endif
|
||||||
};
|
};
|
||||||
unsigned int expected_byte_count = sizeof(expected);
|
unsigned int expected_byte_count = sizeof(expected);
|
||||||
|
@ -156,7 +161,6 @@ static bool CompareFile(const char *path) {
|
||||||
|
|
||||||
printf("%d\n",b1 - (char*)buffer);
|
printf("%d\n",b1 - (char*)buffer);
|
||||||
|
|
||||||
|
|
||||||
ASSERT_EQ(memcmp(buffer, expected, expected_byte_count), 0);
|
ASSERT_EQ(memcmp(buffer, expected, expected_byte_count), 0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
241
src/common/mac/GTMDefines.h
Normal file
241
src/common/mac/GTMDefines.h
Normal file
|
@ -0,0 +1,241 @@
|
||||||
|
//
|
||||||
|
// GTMDefines.h
|
||||||
|
//
|
||||||
|
// Copyright 2008 Google Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
// use this file except in compliance with the License. You may obtain a copy
|
||||||
|
// of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
// License for the specific language governing permissions and limitations under
|
||||||
|
// the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#include <AvailabilityMacros.h>
|
||||||
|
#include <TargetConditionals.h>
|
||||||
|
|
||||||
|
// Not all MAC_OS_X_VERSION_10_X macros defined in past SDKs
|
||||||
|
#ifndef MAC_OS_X_VERSION_10_5
|
||||||
|
#define MAC_OS_X_VERSION_10_5 1050
|
||||||
|
#endif
|
||||||
|
#ifndef MAC_OS_X_VERSION_10_6
|
||||||
|
#define MAC_OS_X_VERSION_10_6 1060
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// CPP symbols that can be overridden in a prefix to control how the toolbox
|
||||||
|
// is compiled.
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
// By setting the GTM_CONTAINERS_VALIDATION_FAILED_LOG and
|
||||||
|
// GTM_CONTAINERS_VALIDATION_FAILED_ASSERT macros you can control what happens
|
||||||
|
// when a validation fails. If you implement your own validators, you may want
|
||||||
|
// to control their internals using the same macros for consistency.
|
||||||
|
#ifndef GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
|
||||||
|
#define GTM_CONTAINERS_VALIDATION_FAILED_ASSERT 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Give ourselves a consistent way to do inlines. Apple's macros even use
|
||||||
|
// a few different actual definitions, so we're based off of the foundation
|
||||||
|
// one.
|
||||||
|
#if !defined(GTM_INLINE)
|
||||||
|
#if defined (__GNUC__) && (__GNUC__ == 4)
|
||||||
|
#define GTM_INLINE static __inline__ __attribute__((always_inline))
|
||||||
|
#else
|
||||||
|
#define GTM_INLINE static __inline__
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Give ourselves a consistent way of doing externs that links up nicely
|
||||||
|
// when mixing objc and objc++
|
||||||
|
#if !defined (GTM_EXTERN)
|
||||||
|
#if defined __cplusplus
|
||||||
|
#define GTM_EXTERN extern "C"
|
||||||
|
#else
|
||||||
|
#define GTM_EXTERN extern
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Give ourselves a consistent way of exporting things if we have visibility
|
||||||
|
// set to hidden.
|
||||||
|
#if !defined (GTM_EXPORT)
|
||||||
|
#define GTM_EXPORT __attribute__((visibility("default")))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// _GTMDevLog & _GTMDevAssert
|
||||||
|
//
|
||||||
|
// _GTMDevLog & _GTMDevAssert are meant to be a very lightweight shell for
|
||||||
|
// developer level errors. This implementation simply macros to NSLog/NSAssert.
|
||||||
|
// It is not intended to be a general logging/reporting system.
|
||||||
|
//
|
||||||
|
// Please see http://code.google.com/p/google-toolbox-for-mac/wiki/DevLogNAssert
|
||||||
|
// for a little more background on the usage of these macros.
|
||||||
|
//
|
||||||
|
// _GTMDevLog log some error/problem in debug builds
|
||||||
|
// _GTMDevAssert assert if conditon isn't met w/in a method/function
|
||||||
|
// in all builds.
|
||||||
|
//
|
||||||
|
// To replace this system, just provide different macro definitions in your
|
||||||
|
// prefix header. Remember, any implementation you provide *must* be thread
|
||||||
|
// safe since this could be called by anything in what ever situtation it has
|
||||||
|
// been placed in.
|
||||||
|
//
|
||||||
|
|
||||||
|
// We only define the simple macros if nothing else has defined this.
|
||||||
|
#ifndef _GTMDevLog
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
#define _GTMDevLog(...) NSLog(__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define _GTMDevLog(...) do { } while (0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // _GTMDevLog
|
||||||
|
|
||||||
|
// Declared here so that it can easily be used for logging tracking if
|
||||||
|
// necessary. See GTMUnitTestDevLog.h for details.
|
||||||
|
@class NSString;
|
||||||
|
GTM_EXTERN void _GTMUnitTestDevLog(NSString *format, ...);
|
||||||
|
|
||||||
|
#ifndef _GTMDevAssert
|
||||||
|
// we directly invoke the NSAssert handler so we can pass on the varargs
|
||||||
|
// (NSAssert doesn't have a macro we can use that takes varargs)
|
||||||
|
#if !defined(NS_BLOCK_ASSERTIONS)
|
||||||
|
#define _GTMDevAssert(condition, ...) \
|
||||||
|
do { \
|
||||||
|
if (!(condition)) { \
|
||||||
|
[[NSAssertionHandler currentHandler] \
|
||||||
|
handleFailureInFunction:[NSString stringWithUTF8String:__PRETTY_FUNCTION__] \
|
||||||
|
file:[NSString stringWithUTF8String:__FILE__] \
|
||||||
|
lineNumber:__LINE__ \
|
||||||
|
description:__VA_ARGS__]; \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
#else // !defined(NS_BLOCK_ASSERTIONS)
|
||||||
|
#define _GTMDevAssert(condition, ...) do { } while (0)
|
||||||
|
#endif // !defined(NS_BLOCK_ASSERTIONS)
|
||||||
|
|
||||||
|
#endif // _GTMDevAssert
|
||||||
|
|
||||||
|
// _GTMCompileAssert
|
||||||
|
// _GTMCompileAssert is an assert that is meant to fire at compile time if you
|
||||||
|
// want to check things at compile instead of runtime. For example if you
|
||||||
|
// want to check that a wchar is 4 bytes instead of 2 you would use
|
||||||
|
// _GTMCompileAssert(sizeof(wchar_t) == 4, wchar_t_is_4_bytes_on_OS_X)
|
||||||
|
// Note that the second "arg" is not in quotes, and must be a valid processor
|
||||||
|
// symbol in it's own right (no spaces, punctuation etc).
|
||||||
|
|
||||||
|
// Wrapping this in an #ifndef allows external groups to define their own
|
||||||
|
// compile time assert scheme.
|
||||||
|
#ifndef _GTMCompileAssert
|
||||||
|
// We got this technique from here:
|
||||||
|
// http://unixjunkie.blogspot.com/2007/10/better-compile-time-asserts_29.html
|
||||||
|
|
||||||
|
#define _GTMCompileAssertSymbolInner(line, msg) _GTMCOMPILEASSERT ## line ## __ ## msg
|
||||||
|
#define _GTMCompileAssertSymbol(line, msg) _GTMCompileAssertSymbolInner(line, msg)
|
||||||
|
#define _GTMCompileAssert(test, msg) \
|
||||||
|
typedef char _GTMCompileAssertSymbol(__LINE__, msg) [ ((test) ? 1 : -1) ]
|
||||||
|
#endif // _GTMCompileAssert
|
||||||
|
|
||||||
|
// Macro to allow fast enumeration when building for 10.5 or later, and
|
||||||
|
// reliance on NSEnumerator for 10.4. Remember, NSDictionary w/ FastEnumeration
|
||||||
|
// does keys, so pick the right thing, nothing is done on the FastEnumeration
|
||||||
|
// side to be sure you're getting what you wanted.
|
||||||
|
#ifndef GTM_FOREACH_OBJECT
|
||||||
|
#if TARGET_OS_IPHONE || (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
|
||||||
|
#define GTM_FOREACH_OBJECT(element, collection) \
|
||||||
|
for (element in collection)
|
||||||
|
#define GTM_FOREACH_KEY(element, collection) \
|
||||||
|
for (element in collection)
|
||||||
|
#else
|
||||||
|
#define GTM_FOREACH_OBJECT(element, collection) \
|
||||||
|
for (NSEnumerator * _ ## element ## _enum = [collection objectEnumerator]; \
|
||||||
|
(element = [_ ## element ## _enum nextObject]) != nil; )
|
||||||
|
#define GTM_FOREACH_KEY(element, collection) \
|
||||||
|
for (NSEnumerator * _ ## element ## _enum = [collection keyEnumerator]; \
|
||||||
|
(element = [_ ## element ## _enum nextObject]) != nil; )
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// CPP symbols defined based on the project settings so the GTM code has
|
||||||
|
// simple things to test against w/o scattering the knowledge of project
|
||||||
|
// setting through all the code.
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Provide a single constant CPP symbol that all of GTM uses for ifdefing
|
||||||
|
// iPhone code.
|
||||||
|
#if TARGET_OS_IPHONE // iPhone SDK
|
||||||
|
// For iPhone specific stuff
|
||||||
|
#define GTM_IPHONE_SDK 1
|
||||||
|
#if TARGET_IPHONE_SIMULATOR
|
||||||
|
#define GTM_IPHONE_SIMULATOR 1
|
||||||
|
#else
|
||||||
|
#define GTM_IPHONE_DEVICE 1
|
||||||
|
#endif // TARGET_IPHONE_SIMULATOR
|
||||||
|
#else
|
||||||
|
// For MacOS specific stuff
|
||||||
|
#define GTM_MACOS_SDK 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Provide a symbol to include/exclude extra code for GC support. (This mainly
|
||||||
|
// just controls the inclusion of finalize methods).
|
||||||
|
#ifndef GTM_SUPPORT_GC
|
||||||
|
#if GTM_IPHONE_SDK
|
||||||
|
// iPhone never needs GC
|
||||||
|
#define GTM_SUPPORT_GC 0
|
||||||
|
#else
|
||||||
|
// We can't find a symbol to tell if GC is supported/required, so best we
|
||||||
|
// do on Mac targets is include it if we're on 10.5 or later.
|
||||||
|
#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4
|
||||||
|
#define GTM_SUPPORT_GC 0
|
||||||
|
#else
|
||||||
|
#define GTM_SUPPORT_GC 1
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// To simplify support for 64bit (and Leopard in general), we provide the type
|
||||||
|
// defines for non Leopard SDKs
|
||||||
|
#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4
|
||||||
|
// NSInteger/NSUInteger and Max/Mins
|
||||||
|
#ifndef NSINTEGER_DEFINED
|
||||||
|
#if __LP64__ || NS_BUILD_32_LIKE_64
|
||||||
|
typedef long NSInteger;
|
||||||
|
typedef unsigned long NSUInteger;
|
||||||
|
#else
|
||||||
|
typedef int NSInteger;
|
||||||
|
typedef unsigned int NSUInteger;
|
||||||
|
#endif
|
||||||
|
#define NSIntegerMax LONG_MAX
|
||||||
|
#define NSIntegerMin LONG_MIN
|
||||||
|
#define NSUIntegerMax ULONG_MAX
|
||||||
|
#define NSINTEGER_DEFINED 1
|
||||||
|
#endif // NSINTEGER_DEFINED
|
||||||
|
// CGFloat
|
||||||
|
#ifndef CGFLOAT_DEFINED
|
||||||
|
#if defined(__LP64__) && __LP64__
|
||||||
|
// This really is an untested path (64bit on Tiger?)
|
||||||
|
typedef double CGFloat;
|
||||||
|
#define CGFLOAT_MIN DBL_MIN
|
||||||
|
#define CGFLOAT_MAX DBL_MAX
|
||||||
|
#define CGFLOAT_IS_DOUBLE 1
|
||||||
|
#else /* !defined(__LP64__) || !__LP64__ */
|
||||||
|
typedef float CGFloat;
|
||||||
|
#define CGFLOAT_MIN FLT_MIN
|
||||||
|
#define CGFLOAT_MAX FLT_MAX
|
||||||
|
#define CGFLOAT_IS_DOUBLE 0
|
||||||
|
#endif /* !defined(__LP64__) || !__LP64__ */
|
||||||
|
#define CGFLOAT_DEFINED 1
|
||||||
|
#endif // CGFLOAT_DEFINED
|
||||||
|
#endif // MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4
|
72
src/common/mac/GTMGarbageCollection.h
Normal file
72
src/common/mac/GTMGarbageCollection.h
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
//
|
||||||
|
// GTMGarbageCollection.h
|
||||||
|
//
|
||||||
|
// Copyright 2007-2008 Google Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
// use this file except in compliance with the License. You may obtain a copy
|
||||||
|
// of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
// License for the specific language governing permissions and limitations under
|
||||||
|
// the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
#import "GTMDefines.h"
|
||||||
|
|
||||||
|
// This allows us to easily move our code from GC to non GC.
|
||||||
|
// They are no-ops unless we are require Leopard or above.
|
||||||
|
// See
|
||||||
|
// http://developer.apple.com/documentation/Cocoa/Conceptual/GarbageCollection/index.html
|
||||||
|
// and
|
||||||
|
// http://developer.apple.com/documentation/Cocoa/Conceptual/GarbageCollection/Articles/gcCoreFoundation.html#//apple_ref/doc/uid/TP40006687-SW1
|
||||||
|
// for details.
|
||||||
|
|
||||||
|
#if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) && !GTM_IPHONE_SDK
|
||||||
|
// General use would be to call this through GTMCFAutorelease
|
||||||
|
// but there may be a reason the you want to make something collectable
|
||||||
|
// but not autoreleased, especially in pure GC code where you don't
|
||||||
|
// want to bother with the nop autorelease. Done as a define instead of an
|
||||||
|
// inline so that tools like Clang's scan-build don't report code as leaking.
|
||||||
|
#define GTMNSMakeCollectable(cf) ((id)NSMakeCollectable(cf))
|
||||||
|
|
||||||
|
// GTMNSMakeUncollectable is for global maps, etc. that we don't
|
||||||
|
// want released ever. You should still retain these in non-gc code.
|
||||||
|
GTM_INLINE void GTMNSMakeUncollectable(id object) {
|
||||||
|
[[NSGarbageCollector defaultCollector] disableCollectorForPointer:object];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hopefully no code really needs this, but GTMIsGarbageCollectionEnabled is
|
||||||
|
// a common way to check at runtime if GC is on.
|
||||||
|
// There are some places where GC doesn't work w/ things w/in Apple's
|
||||||
|
// frameworks, so this is here so GTM unittests and detect it, and not run
|
||||||
|
// individual tests to work around bugs in Apple's frameworks.
|
||||||
|
GTM_INLINE BOOL GTMIsGarbageCollectionEnabled(void) {
|
||||||
|
return ([NSGarbageCollector defaultCollector] != nil);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#define GTMNSMakeCollectable(cf) ((id)(cf))
|
||||||
|
|
||||||
|
GTM_INLINE void GTMNSMakeUncollectable(id object) {
|
||||||
|
}
|
||||||
|
|
||||||
|
GTM_INLINE BOOL GTMIsGarbageCollectionEnabled(void) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// GTMCFAutorelease makes a CF object collectable in GC mode, or adds it
|
||||||
|
// to the autorelease pool in non-GC mode. Either way it is taken care
|
||||||
|
// of. Done as a define instead of an inline so that tools like Clang's
|
||||||
|
// scan-build don't report code as leaking.
|
||||||
|
#define GTMCFAutorelease(cf) ([GTMNSMakeCollectable(cf) autorelease])
|
||||||
|
|
458
src/common/mac/GTMLogger.h
Normal file
458
src/common/mac/GTMLogger.h
Normal file
|
@ -0,0 +1,458 @@
|
||||||
|
//
|
||||||
|
// GTMLogger.h
|
||||||
|
//
|
||||||
|
// Copyright 2007-2008 Google Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
// use this file except in compliance with the License. You may obtain a copy
|
||||||
|
// of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
// License for the specific language governing permissions and limitations under
|
||||||
|
// the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
// Key Abstractions
|
||||||
|
// ----------------
|
||||||
|
//
|
||||||
|
// This file declares multiple classes and protocols that are used by the
|
||||||
|
// GTMLogger logging system. The 4 main abstractions used in this file are the
|
||||||
|
// following:
|
||||||
|
//
|
||||||
|
// * logger (GTMLogger) - The main logging class that users interact with. It
|
||||||
|
// has methods for logging at different levels and uses a log writer, a log
|
||||||
|
// formatter, and a log filter to get the job done.
|
||||||
|
//
|
||||||
|
// * log writer (GTMLogWriter) - Writes a given string to some log file, where
|
||||||
|
// a "log file" can be a physical file on disk, a POST over HTTP to some URL,
|
||||||
|
// or even some in-memory structure (e.g., a ring buffer).
|
||||||
|
//
|
||||||
|
// * log formatter (GTMLogFormatter) - Given a format string and arguments as
|
||||||
|
// a va_list, returns a single formatted NSString. A "formatted string" could
|
||||||
|
// be a string with the date prepended, a string with values in a CSV format,
|
||||||
|
// or even a string of XML.
|
||||||
|
//
|
||||||
|
// * log filter (GTMLogFilter) - Given a formatted log message as an NSString
|
||||||
|
// and the level at which the message is to be logged, this class will decide
|
||||||
|
// whether the given message should be logged or not. This is a flexible way
|
||||||
|
// to filter out messages logged at a certain level, messages that contain
|
||||||
|
// certain text, or filter nothing out at all. This gives the caller the
|
||||||
|
// flexibility to dynamically enable debug logging in Release builds.
|
||||||
|
//
|
||||||
|
// This file also declares some classes to handle the common log writer, log
|
||||||
|
// formatter, and log filter cases. Callers can also create their own writers,
|
||||||
|
// formatters, and filters and they can even build them on top of the ones
|
||||||
|
// declared here. Keep in mind that your custom writer/formatter/filter may be
|
||||||
|
// called from multiple threads, so it must be thread-safe.
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
// Predeclaration of used protocols that are declared later in this file.
|
||||||
|
@protocol GTMLogWriter, GTMLogFormatter, GTMLogFilter;
|
||||||
|
|
||||||
|
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
|
||||||
|
#define CHECK_FORMAT_NSSTRING(a, b) __attribute__((format(__NSString__, a, b)))
|
||||||
|
#else
|
||||||
|
#define CHECK_FORMAT_NSSTRING(a, b)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// GTMLogger
|
||||||
|
//
|
||||||
|
// GTMLogger is the primary user-facing class for an object-oriented logging
|
||||||
|
// system. It is built on the concept of log formatters (GTMLogFormatter), log
|
||||||
|
// writers (GTMLogWriter), and log filters (GTMLogFilter). When a message is
|
||||||
|
// sent to a GTMLogger to log a message, the message is formatted using the log
|
||||||
|
// formatter, then the log filter is consulted to see if the message should be
|
||||||
|
// logged, and if so, the message is sent to the log writer to be written out.
|
||||||
|
//
|
||||||
|
// GTMLogger is intended to be a flexible and thread-safe logging solution. Its
|
||||||
|
// flexibility comes from the fact that GTMLogger instances can be customized
|
||||||
|
// with user defined formatters, filters, and writers. And these writers,
|
||||||
|
// filters, and formatters can be combined, stacked, and customized in arbitrary
|
||||||
|
// ways to suit the needs at hand. For example, multiple writers can be used at
|
||||||
|
// the same time, and a GTMLogger instance can even be used as another
|
||||||
|
// GTMLogger's writer. This allows for arbitrarily deep logging trees.
|
||||||
|
//
|
||||||
|
// A standard GTMLogger uses a writer that sends messages to standard out, a
|
||||||
|
// formatter that smacks a timestamp and a few other bits of interesting
|
||||||
|
// information on the message, and a filter that filters out debug messages from
|
||||||
|
// release builds. Using the standard log settings, a log message will look like
|
||||||
|
// the following:
|
||||||
|
//
|
||||||
|
// 2007-12-30 10:29:24.177 myapp[4588/0xa07d0f60] [lvl=1] foo=<Foo: 0x123>
|
||||||
|
//
|
||||||
|
// The output contains the date and time of the log message, the name of the
|
||||||
|
// process followed by its process ID/thread ID, the log level at which the
|
||||||
|
// message was logged (in the previous example the level was 1:
|
||||||
|
// kGTMLoggerLevelDebug), and finally, the user-specified log message itself (in
|
||||||
|
// this case, the log message was @"foo=%@", foo).
|
||||||
|
//
|
||||||
|
// Multiple instances of GTMLogger can be created, each configured their own
|
||||||
|
// way. Though GTMLogger is not a singleton (in the GoF sense), it does provide
|
||||||
|
// access to a shared (i.e., globally accessible) GTMLogger instance. This makes
|
||||||
|
// it convenient for all code in a process to use the same GTMLogger instance.
|
||||||
|
// The shared GTMLogger instance can also be configured in an arbitrary, and
|
||||||
|
// these configuration changes will affect all code that logs through the shared
|
||||||
|
// instance.
|
||||||
|
|
||||||
|
//
|
||||||
|
// Log Levels
|
||||||
|
// ----------
|
||||||
|
// GTMLogger has 3 different log levels: Debug, Info, and Error. GTMLogger
|
||||||
|
// doesn't take any special action based on the log level; it simply forwards
|
||||||
|
// this information on to formatters, filters, and writers, each of which may
|
||||||
|
// optionally take action based on the level. Since log level filtering is
|
||||||
|
// performed at runtime, log messages are typically not filtered out at compile
|
||||||
|
// time. The exception to this rule is that calls to the GTMLoggerDebug() macro
|
||||||
|
// *ARE* filtered out of non-DEBUG builds. This is to be backwards compatible
|
||||||
|
// with behavior that many developers are currently used to. Note that this
|
||||||
|
// means that GTMLoggerDebug(@"hi") will be compiled out of Release builds, but
|
||||||
|
// [[GTMLogger sharedLogger] logDebug:@"hi"] will NOT be compiled out.
|
||||||
|
//
|
||||||
|
// Standard loggers are created with the GTMLogLevelFilter log filter, which
|
||||||
|
// filters out certain log messages based on log level, and some other settings.
|
||||||
|
//
|
||||||
|
// In addition to the -logDebug:, -logInfo:, and -logError: methods defined on
|
||||||
|
// GTMLogger itself, there are also C macros that make usage of the shared
|
||||||
|
// GTMLogger instance very convenient. These macros are:
|
||||||
|
//
|
||||||
|
// GTMLoggerDebug(...)
|
||||||
|
// GTMLoggerInfo(...)
|
||||||
|
// GTMLoggerError(...)
|
||||||
|
//
|
||||||
|
// Again, a notable feature of these macros is that GTMLogDebug() calls *will be
|
||||||
|
// compiled out of non-DEBUG builds*.
|
||||||
|
//
|
||||||
|
// Standard Loggers
|
||||||
|
// ----------------
|
||||||
|
// GTMLogger has the concept of "standard loggers". A standard logger is simply
|
||||||
|
// a logger that is pre-configured with some standard/common writer, formatter,
|
||||||
|
// and filter combination. Standard loggers are created using the creation
|
||||||
|
// methods beginning with "standard". The alternative to a standard logger is a
|
||||||
|
// regular logger, which will send messages to stdout, with no special
|
||||||
|
// formatting, and no filtering.
|
||||||
|
//
|
||||||
|
// How do I use GTMLogger?
|
||||||
|
// ----------------------
|
||||||
|
// The typical way you will want to use GTMLogger is to simply use the
|
||||||
|
// GTMLogger*() macros for logging from code. That way we can easily make
|
||||||
|
// changes to the GTMLogger class and simply update the macros accordingly. Only
|
||||||
|
// your application startup code (perhaps, somewhere in main()) should use the
|
||||||
|
// GTMLogger class directly in order to configure the shared logger, which all
|
||||||
|
// of the code using the macros will be using. Again, this is just the typical
|
||||||
|
// situation.
|
||||||
|
//
|
||||||
|
// To be complete, there are cases where you may want to use GTMLogger directly,
|
||||||
|
// or even create separate GTMLogger instances for some reason. That's fine,
|
||||||
|
// too.
|
||||||
|
//
|
||||||
|
// Examples
|
||||||
|
// --------
|
||||||
|
// The following show some common GTMLogger use cases.
|
||||||
|
//
|
||||||
|
// 1. You want to log something as simply as possible. Also, this call will only
|
||||||
|
// appear in debug builds. In non-DEBUG builds it will be completely removed.
|
||||||
|
//
|
||||||
|
// GTMLoggerDebug(@"foo = %@", foo);
|
||||||
|
//
|
||||||
|
// 2. The previous example is similar to the following. The major difference is
|
||||||
|
// that the previous call (example 1) will be compiled out of Release builds
|
||||||
|
// but this statement will not be compiled out.
|
||||||
|
//
|
||||||
|
// [[GTMLogger sharedLogger] logDebug:@"foo = %@", foo];
|
||||||
|
//
|
||||||
|
// 3. Send all logging output from the shared logger to a file. We do this by
|
||||||
|
// creating an NSFileHandle for writing associated with a file, and setting
|
||||||
|
// that file handle as the logger's writer.
|
||||||
|
//
|
||||||
|
// NSFileHandle *f = [NSFileHandle fileHandleForWritingAtPath:@"/tmp/f.log"
|
||||||
|
// create:YES];
|
||||||
|
// [[GTMLogger sharedLogger] setWriter:f];
|
||||||
|
// GTMLoggerError(@"hi"); // This will be sent to /tmp/f.log
|
||||||
|
//
|
||||||
|
// 4. Create a new GTMLogger that will log to a file. This example differs from
|
||||||
|
// the previous one because here we create a new GTMLogger that is different
|
||||||
|
// from the shared logger.
|
||||||
|
//
|
||||||
|
// GTMLogger *logger = [GTMLogger standardLoggerWithPath:@"/tmp/temp.log"];
|
||||||
|
// [logger logInfo:@"hi temp log file"];
|
||||||
|
//
|
||||||
|
// 5. Create a logger that writes to stdout and does NOT do any formatting to
|
||||||
|
// the log message. This might be useful, for example, when writing a help
|
||||||
|
// screen for a command-line tool to standard output.
|
||||||
|
//
|
||||||
|
// GTMLogger *logger = [GTMLogger logger];
|
||||||
|
// [logger logInfo:@"%@ version 0.1 usage", progName];
|
||||||
|
//
|
||||||
|
// 6. Send log output to stdout AND to a log file. The trick here is that
|
||||||
|
// NSArrays function as composite log writers, which means when an array is
|
||||||
|
// set as the log writer, it forwards all logging messages to all of its
|
||||||
|
// contained GTMLogWriters.
|
||||||
|
//
|
||||||
|
// // Create array of GTMLogWriters
|
||||||
|
// NSArray *writers = [NSArray arrayWithObjects:
|
||||||
|
// [NSFileHandle fileHandleForWritingAtPath:@"/tmp/f.log" create:YES],
|
||||||
|
// [NSFileHandle fileHandleWithStandardOutput], nil];
|
||||||
|
//
|
||||||
|
// GTMLogger *logger = [GTMLogger standardLogger];
|
||||||
|
// [logger setWriter:writers];
|
||||||
|
// [logger logInfo:@"hi"]; // Output goes to stdout and /tmp/f.log
|
||||||
|
//
|
||||||
|
// For futher details on log writers, formatters, and filters, see the
|
||||||
|
// documentation below.
|
||||||
|
//
|
||||||
|
// NOTE: GTMLogger is application level logging. By default it does nothing
|
||||||
|
// with _GTMDevLog/_GTMDevAssert (see GTMDefines.h). An application can choose
|
||||||
|
// to bridge _GTMDevLog/_GTMDevAssert to GTMLogger by providing macro
|
||||||
|
// definitions in its prefix header (see GTMDefines.h for how one would do
|
||||||
|
// that).
|
||||||
|
//
|
||||||
|
@interface GTMLogger : NSObject {
|
||||||
|
@private
|
||||||
|
id<GTMLogWriter> writer_;
|
||||||
|
id<GTMLogFormatter> formatter_;
|
||||||
|
id<GTMLogFilter> filter_;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Accessors for the shared logger instance
|
||||||
|
//
|
||||||
|
|
||||||
|
// Returns a shared/global standard GTMLogger instance. Callers should typically
|
||||||
|
// use this method to get a GTMLogger instance, unless they explicitly want
|
||||||
|
// their own instance to configure for their own needs. This is the only method
|
||||||
|
// that returns a shared instance; all the rest return new GTMLogger instances.
|
||||||
|
+ (id)sharedLogger;
|
||||||
|
|
||||||
|
// Sets the shared logger instance to |logger|. Future calls to +sharedLogger
|
||||||
|
// will return |logger| instead.
|
||||||
|
+ (void)setSharedLogger:(GTMLogger *)logger;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Creation methods
|
||||||
|
//
|
||||||
|
|
||||||
|
// Returns a new autoreleased GTMLogger instance that will log to stdout, using
|
||||||
|
// the GTMLogStandardFormatter, and the GTMLogLevelFilter filter.
|
||||||
|
+ (id)standardLogger;
|
||||||
|
|
||||||
|
// Same as +standardLogger, but logs to stderr.
|
||||||
|
+ (id)standardLoggerWithStderr;
|
||||||
|
|
||||||
|
// Returns a new standard GTMLogger instance with a log writer that will
|
||||||
|
// write to the file at |path|, and will use the GTMLogStandardFormatter and
|
||||||
|
// GTMLogLevelFilter classes. If |path| does not exist, it will be created.
|
||||||
|
+ (id)standardLoggerWithPath:(NSString *)path;
|
||||||
|
|
||||||
|
// Returns an autoreleased GTMLogger instance that will use the specified
|
||||||
|
// |writer|, |formatter|, and |filter|.
|
||||||
|
+ (id)loggerWithWriter:(id<GTMLogWriter>)writer
|
||||||
|
formatter:(id<GTMLogFormatter>)formatter
|
||||||
|
filter:(id<GTMLogFilter>)filter;
|
||||||
|
|
||||||
|
// Returns an autoreleased GTMLogger instance that logs to stdout, with the
|
||||||
|
// basic formatter, and no filter. The returned logger differs from the logger
|
||||||
|
// returned by +standardLogger because this one does not do any filtering and
|
||||||
|
// does not do any special log formatting; this is the difference between a
|
||||||
|
// "regular" logger and a "standard" logger.
|
||||||
|
+ (id)logger;
|
||||||
|
|
||||||
|
// Designated initializer. This method returns a GTMLogger initialized with the
|
||||||
|
// specified |writer|, |formatter|, and |filter|. See the setter methods below
|
||||||
|
// for what values will be used if nil is passed for a parameter.
|
||||||
|
- (id)initWithWriter:(id<GTMLogWriter>)writer
|
||||||
|
formatter:(id<GTMLogFormatter>)formatter
|
||||||
|
filter:(id<GTMLogFilter>)filter;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Logging methods
|
||||||
|
//
|
||||||
|
|
||||||
|
// Logs a message at the debug level (kGTMLoggerLevelDebug).
|
||||||
|
- (void)logDebug:(NSString *)fmt, ... CHECK_FORMAT_NSSTRING(1, 2);
|
||||||
|
// Logs a message at the info level (kGTMLoggerLevelInfo).
|
||||||
|
- (void)logInfo:(NSString *)fmt, ... CHECK_FORMAT_NSSTRING(1, 2);
|
||||||
|
// Logs a message at the error level (kGTMLoggerLevelError).
|
||||||
|
- (void)logError:(NSString *)fmt, ... CHECK_FORMAT_NSSTRING(1, 2);
|
||||||
|
// Logs a message at the assert level (kGTMLoggerLevelAssert).
|
||||||
|
- (void)logAssert:(NSString *)fmt, ... CHECK_FORMAT_NSSTRING(1, 2);
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Accessors
|
||||||
|
//
|
||||||
|
|
||||||
|
// Accessor methods for the log writer. If the log writer is set to nil,
|
||||||
|
// [NSFileHandle fileHandleWithStandardOutput] is used.
|
||||||
|
- (id<GTMLogWriter>)writer;
|
||||||
|
- (void)setWriter:(id<GTMLogWriter>)writer;
|
||||||
|
|
||||||
|
// Accessor methods for the log formatter. If the log formatter is set to nil,
|
||||||
|
// GTMLogBasicFormatter is used. This formatter will format log messages in a
|
||||||
|
// plain printf style.
|
||||||
|
- (id<GTMLogFormatter>)formatter;
|
||||||
|
- (void)setFormatter:(id<GTMLogFormatter>)formatter;
|
||||||
|
|
||||||
|
// Accessor methods for the log filter. If the log filter is set to nil,
|
||||||
|
// GTMLogNoFilter is used, which allows all log messages through.
|
||||||
|
- (id<GTMLogFilter>)filter;
|
||||||
|
- (void)setFilter:(id<GTMLogFilter>)filter;
|
||||||
|
|
||||||
|
@end // GTMLogger
|
||||||
|
|
||||||
|
|
||||||
|
// Helper functions that are used by the convenience GTMLogger*() macros that
|
||||||
|
// enable the logging of function names.
|
||||||
|
@interface GTMLogger (GTMLoggerMacroHelpers)
|
||||||
|
- (void)logFuncDebug:(const char *)func msg:(NSString *)fmt, ...
|
||||||
|
CHECK_FORMAT_NSSTRING(2, 3);
|
||||||
|
- (void)logFuncInfo:(const char *)func msg:(NSString *)fmt, ...
|
||||||
|
CHECK_FORMAT_NSSTRING(2, 3);
|
||||||
|
- (void)logFuncError:(const char *)func msg:(NSString *)fmt, ...
|
||||||
|
CHECK_FORMAT_NSSTRING(2, 3);
|
||||||
|
- (void)logFuncAssert:(const char *)func msg:(NSString *)fmt, ...
|
||||||
|
CHECK_FORMAT_NSSTRING(2, 3);
|
||||||
|
@end // GTMLoggerMacroHelpers
|
||||||
|
|
||||||
|
|
||||||
|
// Convenience macros that log to the shared GTMLogger instance. These macros
|
||||||
|
// are how users should typically log to GTMLogger. Notice that GTMLoggerDebug()
|
||||||
|
// calls will be compiled out of non-Debug builds.
|
||||||
|
#define GTMLoggerDebug(...) \
|
||||||
|
[[GTMLogger sharedLogger] logFuncDebug:__func__ msg:__VA_ARGS__]
|
||||||
|
#define GTMLoggerInfo(...) \
|
||||||
|
[[GTMLogger sharedLogger] logFuncInfo:__func__ msg:__VA_ARGS__]
|
||||||
|
#define GTMLoggerError(...) \
|
||||||
|
[[GTMLogger sharedLogger] logFuncError:__func__ msg:__VA_ARGS__]
|
||||||
|
#define GTMLoggerAssert(...) \
|
||||||
|
[[GTMLogger sharedLogger] logFuncAssert:__func__ msg:__VA_ARGS__]
|
||||||
|
|
||||||
|
// If we're not in a debug build, remove the GTMLoggerDebug statements. This
|
||||||
|
// makes calls to GTMLoggerDebug "compile out" of Release builds
|
||||||
|
#ifndef DEBUG
|
||||||
|
#undef GTMLoggerDebug
|
||||||
|
#define GTMLoggerDebug(...) do {} while(0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Log levels.
|
||||||
|
typedef enum {
|
||||||
|
kGTMLoggerLevelUnknown,
|
||||||
|
kGTMLoggerLevelDebug,
|
||||||
|
kGTMLoggerLevelInfo,
|
||||||
|
kGTMLoggerLevelError,
|
||||||
|
kGTMLoggerLevelAssert,
|
||||||
|
} GTMLoggerLevel;
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Log Writers
|
||||||
|
//
|
||||||
|
|
||||||
|
// Protocol to be implemented by a GTMLogWriter instance.
|
||||||
|
@protocol GTMLogWriter <NSObject>
|
||||||
|
// Writes the given log message to where the log writer is configured to write.
|
||||||
|
- (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level;
|
||||||
|
@end // GTMLogWriter
|
||||||
|
|
||||||
|
|
||||||
|
// Simple category on NSFileHandle that makes NSFileHandles valid log writers.
|
||||||
|
// This is convenient because something like, say, +fileHandleWithStandardError
|
||||||
|
// now becomes a valid log writer. Log messages are written to the file handle
|
||||||
|
// with a newline appended.
|
||||||
|
@interface NSFileHandle (GTMFileHandleLogWriter) <GTMLogWriter>
|
||||||
|
// Opens the file at |path| in append mode, and creates the file with |mode|
|
||||||
|
// if it didn't previously exist.
|
||||||
|
+ (id)fileHandleForLoggingAtPath:(NSString *)path mode:(mode_t)mode;
|
||||||
|
@end // NSFileHandle
|
||||||
|
|
||||||
|
|
||||||
|
// This category makes NSArray a GTMLogWriter that can be composed of other
|
||||||
|
// GTMLogWriters. This is the classic Composite GoF design pattern. When the
|
||||||
|
// GTMLogWriter -logMessage:level: message is sent to the array, the array
|
||||||
|
// forwards the message to all of its elements that implement the GTMLogWriter
|
||||||
|
// protocol.
|
||||||
|
//
|
||||||
|
// This is useful in situations where you would like to send log output to
|
||||||
|
// multiple log writers at the same time. Simply create an NSArray of the log
|
||||||
|
// writers you wish to use, then set the array as the "writer" for your
|
||||||
|
// GTMLogger instance.
|
||||||
|
@interface NSArray (GTMArrayCompositeLogWriter) <GTMLogWriter>
|
||||||
|
@end // GTMArrayCompositeLogWriter
|
||||||
|
|
||||||
|
|
||||||
|
// This category adapts the GTMLogger interface so that it can be used as a log
|
||||||
|
// writer; it's an "adapter" in the GoF Adapter pattern sense.
|
||||||
|
//
|
||||||
|
// This is useful when you want to configure a logger to log to a specific
|
||||||
|
// writer with a specific formatter and/or filter. But you want to also compose
|
||||||
|
// that with a different log writer that may have its own formatter and/or
|
||||||
|
// filter.
|
||||||
|
@interface GTMLogger (GTMLoggerLogWriter) <GTMLogWriter>
|
||||||
|
@end // GTMLoggerLogWriter
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Log Formatters
|
||||||
|
//
|
||||||
|
|
||||||
|
// Protocol to be implemented by a GTMLogFormatter instance.
|
||||||
|
@protocol GTMLogFormatter <NSObject>
|
||||||
|
// Returns a formatted string using the format specified in |fmt| and the va
|
||||||
|
// args specified in |args|.
|
||||||
|
- (NSString *)stringForFunc:(NSString *)func
|
||||||
|
withFormat:(NSString *)fmt
|
||||||
|
valist:(va_list)args
|
||||||
|
level:(GTMLoggerLevel)level;
|
||||||
|
@end // GTMLogFormatter
|
||||||
|
|
||||||
|
|
||||||
|
// A basic log formatter that formats a string the same way that NSLog (or
|
||||||
|
// printf) would. It does not do anything fancy, nor does it add any data of its
|
||||||
|
// own.
|
||||||
|
@interface GTMLogBasicFormatter : NSObject <GTMLogFormatter>
|
||||||
|
@end // GTMLogBasicFormatter
|
||||||
|
|
||||||
|
|
||||||
|
// A log formatter that formats the log string like the basic formatter, but
|
||||||
|
// also prepends a timestamp and some basic process info to the message, as
|
||||||
|
// shown in the following sample output.
|
||||||
|
// 2007-12-30 10:29:24.177 myapp[4588/0xa07d0f60] [lvl=1] log mesage here
|
||||||
|
@interface GTMLogStandardFormatter : GTMLogBasicFormatter {
|
||||||
|
@private
|
||||||
|
NSDateFormatter *dateFormatter_; // yyyy-MM-dd HH:mm:ss.SSS
|
||||||
|
NSString *pname_;
|
||||||
|
pid_t pid_;
|
||||||
|
}
|
||||||
|
@end // GTMLogStandardFormatter
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Log Filters
|
||||||
|
//
|
||||||
|
|
||||||
|
// Protocol to be imlemented by a GTMLogFilter instance.
|
||||||
|
@protocol GTMLogFilter <NSObject>
|
||||||
|
// Returns YES if |msg| at |level| should be filtered out; NO otherwise.
|
||||||
|
- (BOOL)filterAllowsMessage:(NSString *)msg level:(GTMLoggerLevel)level;
|
||||||
|
@end // GTMLogFilter
|
||||||
|
|
||||||
|
|
||||||
|
// A log filter that filters messages at the kGTMLoggerLevelDebug level out of
|
||||||
|
// non-debug builds. Messages at the kGTMLoggerLevelInfo level are also filtered
|
||||||
|
// out of non-debug builds unless GTMVerboseLogging is set in the environment or
|
||||||
|
// the processes's defaults. Messages at the kGTMLoggerLevelError level are
|
||||||
|
// never filtered.
|
||||||
|
@interface GTMLogLevelFilter : NSObject <GTMLogFilter>
|
||||||
|
@end // GTMLogLevelFilter
|
||||||
|
|
||||||
|
|
||||||
|
// A simple log filter that does NOT filter anything out;
|
||||||
|
// -filterAllowsMessage:level will always return YES. This can be a convenient
|
||||||
|
// way to enable debug-level logging in release builds (if you so desire).
|
||||||
|
@interface GTMLogNoFilter : NSObject <GTMLogFilter>
|
||||||
|
@end // GTMLogNoFilter
|
||||||
|
|
445
src/common/mac/GTMLogger.m
Normal file
445
src/common/mac/GTMLogger.m
Normal file
|
@ -0,0 +1,445 @@
|
||||||
|
//
|
||||||
|
// GTMLogger.m
|
||||||
|
//
|
||||||
|
// Copyright 2007-2008 Google Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
// use this file except in compliance with the License. You may obtain a copy
|
||||||
|
// of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
// License for the specific language governing permissions and limitations under
|
||||||
|
// the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "GTMLogger.h"
|
||||||
|
#import "GTMGarbageCollection.h"
|
||||||
|
#import <fcntl.h>
|
||||||
|
#import <unistd.h>
|
||||||
|
#import <stdlib.h>
|
||||||
|
#import <pthread.h>
|
||||||
|
|
||||||
|
|
||||||
|
// Define a trivial assertion macro to avoid dependencies
|
||||||
|
#ifdef DEBUG
|
||||||
|
#define GTMLOGGER_ASSERT(expr) assert(expr)
|
||||||
|
#else
|
||||||
|
#define GTMLOGGER_ASSERT(expr)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
@interface GTMLogger (PrivateMethods)
|
||||||
|
|
||||||
|
- (void)logInternalFunc:(const char *)func
|
||||||
|
format:(NSString *)fmt
|
||||||
|
valist:(va_list)args
|
||||||
|
level:(GTMLoggerLevel)level;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
// Reference to the shared GTMLogger instance. This is not a singleton, it's
|
||||||
|
// just an easy reference to one shared instance.
|
||||||
|
static GTMLogger *gSharedLogger = nil;
|
||||||
|
|
||||||
|
|
||||||
|
@implementation GTMLogger
|
||||||
|
|
||||||
|
// Returns a pointer to the shared logger instance. If none exists, a standard
|
||||||
|
// logger is created and returned.
|
||||||
|
+ (id)sharedLogger {
|
||||||
|
@synchronized(self) {
|
||||||
|
if (gSharedLogger == nil) {
|
||||||
|
gSharedLogger = [[self standardLogger] retain];
|
||||||
|
}
|
||||||
|
GTMLOGGER_ASSERT(gSharedLogger != nil);
|
||||||
|
}
|
||||||
|
return [[gSharedLogger retain] autorelease];
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (void)setSharedLogger:(GTMLogger *)logger {
|
||||||
|
@synchronized(self) {
|
||||||
|
[gSharedLogger autorelease];
|
||||||
|
gSharedLogger = [logger retain];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (id)standardLogger {
|
||||||
|
id<GTMLogWriter> writer = [NSFileHandle fileHandleWithStandardOutput];
|
||||||
|
id<GTMLogFormatter> fr = [[[GTMLogStandardFormatter alloc] init] autorelease];
|
||||||
|
id<GTMLogFilter> filter = [[[GTMLogLevelFilter alloc] init] autorelease];
|
||||||
|
return [self loggerWithWriter:writer formatter:fr filter:filter];
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (id)standardLoggerWithStderr {
|
||||||
|
id me = [self standardLogger];
|
||||||
|
[me setWriter:[NSFileHandle fileHandleWithStandardError]];
|
||||||
|
return me;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (id)standardLoggerWithPath:(NSString *)path {
|
||||||
|
NSFileHandle *fh = [NSFileHandle fileHandleForLoggingAtPath:path mode:0644];
|
||||||
|
if (fh == nil) return nil;
|
||||||
|
id me = [self standardLogger];
|
||||||
|
[me setWriter:fh];
|
||||||
|
return me;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (id)loggerWithWriter:(id<GTMLogWriter>)writer
|
||||||
|
formatter:(id<GTMLogFormatter>)formatter
|
||||||
|
filter:(id<GTMLogFilter>)filter {
|
||||||
|
return [[[self alloc] initWithWriter:writer
|
||||||
|
formatter:formatter
|
||||||
|
filter:filter] autorelease];
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (id)logger {
|
||||||
|
return [[[self alloc] init] autorelease];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)init {
|
||||||
|
return [self initWithWriter:nil formatter:nil filter:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)initWithWriter:(id<GTMLogWriter>)writer
|
||||||
|
formatter:(id<GTMLogFormatter>)formatter
|
||||||
|
filter:(id<GTMLogFilter>)filter {
|
||||||
|
if ((self = [super init])) {
|
||||||
|
[self setWriter:writer];
|
||||||
|
[self setFormatter:formatter];
|
||||||
|
[self setFilter:filter];
|
||||||
|
GTMLOGGER_ASSERT(formatter_ != nil);
|
||||||
|
GTMLOGGER_ASSERT(filter_ != nil);
|
||||||
|
GTMLOGGER_ASSERT(writer_ != nil);
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc {
|
||||||
|
GTMLOGGER_ASSERT(writer_ != nil);
|
||||||
|
GTMLOGGER_ASSERT(formatter_ != nil);
|
||||||
|
GTMLOGGER_ASSERT(filter_ != nil);
|
||||||
|
[writer_ release];
|
||||||
|
[formatter_ release];
|
||||||
|
[filter_ release];
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id<GTMLogWriter>)writer {
|
||||||
|
GTMLOGGER_ASSERT(writer_ != nil);
|
||||||
|
return [[writer_ retain] autorelease];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setWriter:(id<GTMLogWriter>)writer {
|
||||||
|
@synchronized(self) {
|
||||||
|
[writer_ autorelease];
|
||||||
|
if (writer == nil)
|
||||||
|
writer_ = [[NSFileHandle fileHandleWithStandardOutput] retain];
|
||||||
|
else
|
||||||
|
writer_ = [writer retain];
|
||||||
|
}
|
||||||
|
GTMLOGGER_ASSERT(writer_ != nil);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id<GTMLogFormatter>)formatter {
|
||||||
|
GTMLOGGER_ASSERT(formatter_ != nil);
|
||||||
|
return [[formatter_ retain] autorelease];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setFormatter:(id<GTMLogFormatter>)formatter {
|
||||||
|
@synchronized(self) {
|
||||||
|
[formatter_ autorelease];
|
||||||
|
if (formatter == nil)
|
||||||
|
formatter_ = [[GTMLogBasicFormatter alloc] init];
|
||||||
|
else
|
||||||
|
formatter_ = [formatter retain];
|
||||||
|
}
|
||||||
|
GTMLOGGER_ASSERT(formatter_ != nil);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id<GTMLogFilter>)filter {
|
||||||
|
GTMLOGGER_ASSERT(filter_ != nil);
|
||||||
|
return [[filter_ retain] autorelease];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setFilter:(id<GTMLogFilter>)filter {
|
||||||
|
@synchronized(self) {
|
||||||
|
[filter_ autorelease];
|
||||||
|
if (filter == nil)
|
||||||
|
filter_ = [[GTMLogNoFilter alloc] init];
|
||||||
|
else
|
||||||
|
filter_ = [filter retain];
|
||||||
|
}
|
||||||
|
GTMLOGGER_ASSERT(filter_ != nil);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)logDebug:(NSString *)fmt, ... {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
[self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelDebug];
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)logInfo:(NSString *)fmt, ... {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
[self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelInfo];
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)logError:(NSString *)fmt, ... {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
[self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelError];
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)logAssert:(NSString *)fmt, ... {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
[self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelAssert];
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@end // GTMLogger
|
||||||
|
|
||||||
|
|
||||||
|
@implementation GTMLogger (GTMLoggerMacroHelpers)
|
||||||
|
|
||||||
|
- (void)logFuncDebug:(const char *)func msg:(NSString *)fmt, ... {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
[self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelDebug];
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)logFuncInfo:(const char *)func msg:(NSString *)fmt, ... {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
[self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelInfo];
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)logFuncError:(const char *)func msg:(NSString *)fmt, ... {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
[self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelError];
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)logFuncAssert:(const char *)func msg:(NSString *)fmt, ... {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
[self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelAssert];
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@end // GTMLoggerMacroHelpers
|
||||||
|
|
||||||
|
|
||||||
|
@implementation GTMLogger (PrivateMethods)
|
||||||
|
|
||||||
|
- (void)logInternalFunc:(const char *)func
|
||||||
|
format:(NSString *)fmt
|
||||||
|
valist:(va_list)args
|
||||||
|
level:(GTMLoggerLevel)level {
|
||||||
|
GTMLOGGER_ASSERT(formatter_ != nil);
|
||||||
|
GTMLOGGER_ASSERT(filter_ != nil);
|
||||||
|
GTMLOGGER_ASSERT(writer_ != nil);
|
||||||
|
|
||||||
|
NSString *fname = func ? [NSString stringWithUTF8String:func] : nil;
|
||||||
|
NSString *msg = [formatter_ stringForFunc:fname
|
||||||
|
withFormat:fmt
|
||||||
|
valist:args
|
||||||
|
level:level];
|
||||||
|
if (msg && [filter_ filterAllowsMessage:msg level:level])
|
||||||
|
[writer_ logMessage:msg level:level];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end // PrivateMethods
|
||||||
|
|
||||||
|
|
||||||
|
@implementation NSFileHandle (GTMFileHandleLogWriter)
|
||||||
|
|
||||||
|
+ (id)fileHandleForLoggingAtPath:(NSString *)path mode:(mode_t)mode {
|
||||||
|
int fd = -1;
|
||||||
|
if (path) {
|
||||||
|
int flags = O_WRONLY | O_APPEND | O_CREAT;
|
||||||
|
fd = open([path fileSystemRepresentation], flags, mode);
|
||||||
|
}
|
||||||
|
if (fd == -1) return nil;
|
||||||
|
return [[[self alloc] initWithFileDescriptor:fd
|
||||||
|
closeOnDealloc:YES] autorelease];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level {
|
||||||
|
@synchronized(self) {
|
||||||
|
NSString *line = [NSString stringWithFormat:@"%@\n", msg];
|
||||||
|
[self writeData:[line dataUsingEncoding:NSUTF8StringEncoding]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end // GTMFileHandleLogWriter
|
||||||
|
|
||||||
|
|
||||||
|
@implementation NSArray (GTMArrayCompositeLogWriter)
|
||||||
|
|
||||||
|
- (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level {
|
||||||
|
@synchronized(self) {
|
||||||
|
id<GTMLogWriter> child = nil;
|
||||||
|
GTM_FOREACH_OBJECT(child, self) {
|
||||||
|
if ([child conformsToProtocol:@protocol(GTMLogWriter)])
|
||||||
|
[child logMessage:msg level:level];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end // GTMArrayCompositeLogWriter
|
||||||
|
|
||||||
|
|
||||||
|
@implementation GTMLogger (GTMLoggerLogWriter)
|
||||||
|
|
||||||
|
- (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level {
|
||||||
|
switch (level) {
|
||||||
|
case kGTMLoggerLevelDebug:
|
||||||
|
[self logDebug:@"%@", msg];
|
||||||
|
break;
|
||||||
|
case kGTMLoggerLevelInfo:
|
||||||
|
[self logInfo:@"%@", msg];
|
||||||
|
break;
|
||||||
|
case kGTMLoggerLevelError:
|
||||||
|
[self logError:@"%@", msg];
|
||||||
|
break;
|
||||||
|
case kGTMLoggerLevelAssert:
|
||||||
|
[self logAssert:@"%@", msg];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Ignore the message.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end // GTMLoggerLogWriter
|
||||||
|
|
||||||
|
|
||||||
|
@implementation GTMLogBasicFormatter
|
||||||
|
|
||||||
|
- (NSString *)stringForFunc:(NSString *)func
|
||||||
|
withFormat:(NSString *)fmt
|
||||||
|
valist:(va_list)args
|
||||||
|
level:(GTMLoggerLevel)level {
|
||||||
|
// Performance note: since we always have to create a new NSString from the
|
||||||
|
// returned CFStringRef, we may want to do a quick check here to see if |fmt|
|
||||||
|
// contains a '%', and if not, simply return 'fmt'.
|
||||||
|
CFStringRef cfmsg = NULL;
|
||||||
|
cfmsg = CFStringCreateWithFormatAndArguments(kCFAllocatorDefault,
|
||||||
|
NULL, // format options
|
||||||
|
(CFStringRef)fmt,
|
||||||
|
args);
|
||||||
|
return GTMCFAutorelease(cfmsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@end // GTMLogBasicFormatter
|
||||||
|
|
||||||
|
|
||||||
|
@implementation GTMLogStandardFormatter
|
||||||
|
|
||||||
|
- (id)init {
|
||||||
|
if ((self = [super init])) {
|
||||||
|
dateFormatter_ = [[NSDateFormatter alloc] init];
|
||||||
|
[dateFormatter_ setFormatterBehavior:NSDateFormatterBehavior10_4];
|
||||||
|
[dateFormatter_ setDateFormat:@"yyyy-MM-dd HH:mm:ss.SSS"];
|
||||||
|
pname_ = [[[NSProcessInfo processInfo] processName] copy];
|
||||||
|
pid_ = [[NSProcessInfo processInfo] processIdentifier];
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc {
|
||||||
|
[dateFormatter_ release];
|
||||||
|
[pname_ release];
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)stringForFunc:(NSString *)func
|
||||||
|
withFormat:(NSString *)fmt
|
||||||
|
valist:(va_list)args
|
||||||
|
level:(GTMLoggerLevel)level {
|
||||||
|
GTMLOGGER_ASSERT(dateFormatter_ != nil);
|
||||||
|
NSString *tstamp = nil;
|
||||||
|
@synchronized (dateFormatter_) {
|
||||||
|
tstamp = [dateFormatter_ stringFromDate:[NSDate date]];
|
||||||
|
}
|
||||||
|
return [NSString stringWithFormat:@"%@ %@[%d/%p] [lvl=%d] %@ %@",
|
||||||
|
tstamp, pname_, pid_, pthread_self(),
|
||||||
|
level, (func ? func : @"(no func)"),
|
||||||
|
[super stringForFunc:func withFormat:fmt valist:args level:level]];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end // GTMLogStandardFormatter
|
||||||
|
|
||||||
|
|
||||||
|
@implementation GTMLogLevelFilter
|
||||||
|
|
||||||
|
// Check the environment and the user preferences for the GTMVerboseLogging key
|
||||||
|
// to see if verbose logging has been enabled. The environment variable will
|
||||||
|
// override the defaults setting, so check the environment first.
|
||||||
|
// COV_NF_START
|
||||||
|
static BOOL IsVerboseLoggingEnabled(void) {
|
||||||
|
static NSString *const kVerboseLoggingKey = @"GTMVerboseLogging";
|
||||||
|
static char *env = NULL;
|
||||||
|
if (env == NULL)
|
||||||
|
env = getenv([kVerboseLoggingKey UTF8String]);
|
||||||
|
|
||||||
|
if (env && env[0]) {
|
||||||
|
return (strtol(env, NULL, 10) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [[NSUserDefaults standardUserDefaults] boolForKey:kVerboseLoggingKey];
|
||||||
|
}
|
||||||
|
// COV_NF_END
|
||||||
|
|
||||||
|
// In DEBUG builds, log everything. If we're not in a debug build we'll assume
|
||||||
|
// that we're in a Release build.
|
||||||
|
- (BOOL)filterAllowsMessage:(NSString *)msg level:(GTMLoggerLevel)level {
|
||||||
|
#if DEBUG
|
||||||
|
return YES;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
BOOL allow = YES;
|
||||||
|
|
||||||
|
switch (level) {
|
||||||
|
case kGTMLoggerLevelDebug:
|
||||||
|
allow = NO;
|
||||||
|
break;
|
||||||
|
case kGTMLoggerLevelInfo:
|
||||||
|
allow = (IsVerboseLoggingEnabled() == YES);
|
||||||
|
break;
|
||||||
|
case kGTMLoggerLevelError:
|
||||||
|
allow = YES;
|
||||||
|
break;
|
||||||
|
case kGTMLoggerLevelAssert:
|
||||||
|
allow = YES;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
allow = YES;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return allow;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end // GTMLogLevelFilter
|
||||||
|
|
||||||
|
|
||||||
|
@implementation GTMLogNoFilter
|
||||||
|
|
||||||
|
- (BOOL)filterAllowsMessage:(NSString *)msg level:(GTMLoggerLevel)level {
|
||||||
|
return YES; // Allow everything through
|
||||||
|
}
|
||||||
|
|
||||||
|
@end // GTMLogNoFilter
|
304
src/common/mac/MachIPC.h
Normal file
304
src/common/mac/MachIPC.h
Normal file
|
@ -0,0 +1,304 @@
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// MachIPC.h
|
||||||
|
//
|
||||||
|
// Some helpful wrappers for using Mach IPC calls
|
||||||
|
|
||||||
|
#ifndef MACH_IPC_H__
|
||||||
|
#define MACH_IPC_H__
|
||||||
|
|
||||||
|
#import <mach/mach.h>
|
||||||
|
#import <mach/message.h>
|
||||||
|
#import <servers/bootstrap.h>
|
||||||
|
#import <sys/types.h>
|
||||||
|
|
||||||
|
#import <CoreServices/CoreServices.h>
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// DISCUSSION:
|
||||||
|
//
|
||||||
|
// The three main classes of interest are
|
||||||
|
//
|
||||||
|
// MachMessage: a wrapper for a mach message of the following form
|
||||||
|
// mach_msg_header_t
|
||||||
|
// mach_msg_body_t
|
||||||
|
// optional descriptors
|
||||||
|
// optional extra message data
|
||||||
|
//
|
||||||
|
// MachReceiveMessage and MachSendMessage subclass MachMessage
|
||||||
|
// and are used instead of MachMessage which is an abstract base class
|
||||||
|
//
|
||||||
|
// ReceivePort:
|
||||||
|
// Represents a mach port for which we have receive rights
|
||||||
|
//
|
||||||
|
// MachPortSender:
|
||||||
|
// Represents a mach port for which we have send rights
|
||||||
|
//
|
||||||
|
// Here's an example to receive a message on a server port:
|
||||||
|
//
|
||||||
|
// // This creates our named server port
|
||||||
|
// ReceivePort receivePort("com.Google.MyService");
|
||||||
|
//
|
||||||
|
// MachReceiveMessage message;
|
||||||
|
// kern_return_t result = receivePort.WaitForMessage(&message, 0);
|
||||||
|
//
|
||||||
|
// if (result == KERN_SUCCESS && message.GetMessageID() == 57) {
|
||||||
|
// mach_port_t task = message.GetTranslatedPort(0);
|
||||||
|
// mach_port_t thread = message.GetTranslatedPort(1);
|
||||||
|
//
|
||||||
|
// char *messageString = message.GetData();
|
||||||
|
//
|
||||||
|
// printf("message string = %s\n", messageString);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Here is an example of using these classes to send a message to this port:
|
||||||
|
//
|
||||||
|
// // send to already named port
|
||||||
|
// MachPortSender sender("com.Google.MyService");
|
||||||
|
// MachSendMessage message(57); // our message ID is 57
|
||||||
|
//
|
||||||
|
// // add some ports to be translated for us
|
||||||
|
// message.AddDescriptor(mach_task_self()); // our task
|
||||||
|
// message.AddDescriptor(mach_thread_self()); // this thread
|
||||||
|
//
|
||||||
|
// char messageString[] = "Hello server!\n";
|
||||||
|
// message.SetData(messageString, strlen(messageString)+1);
|
||||||
|
//
|
||||||
|
// kern_return_t result = sender.SendMessage(message, 1000); // timeout 1000ms
|
||||||
|
//
|
||||||
|
|
||||||
|
#define PRINT_MACH_RESULT(result_, message_) \
|
||||||
|
printf(message_" %s (%d)\n", mach_error_string(result_), result_ );
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// A wrapper class for mach_msg_port_descriptor_t (with same memory layout)
|
||||||
|
// with convenient constructors and accessors
|
||||||
|
class MachMsgPortDescriptor : public mach_msg_port_descriptor_t {
|
||||||
|
public:
|
||||||
|
// General-purpose constructor
|
||||||
|
MachMsgPortDescriptor(mach_port_t in_name,
|
||||||
|
mach_msg_type_name_t in_disposition) {
|
||||||
|
name = in_name;
|
||||||
|
pad1 = 0;
|
||||||
|
pad2 = 0;
|
||||||
|
disposition = in_disposition;
|
||||||
|
type = MACH_MSG_PORT_DESCRIPTOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For passing send rights to a port
|
||||||
|
MachMsgPortDescriptor(mach_port_t in_name) {
|
||||||
|
name = in_name;
|
||||||
|
pad1 = 0;
|
||||||
|
pad2 = 0;
|
||||||
|
disposition = MACH_MSG_TYPE_COPY_SEND;
|
||||||
|
type = MACH_MSG_PORT_DESCRIPTOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy constructor
|
||||||
|
MachMsgPortDescriptor(const MachMsgPortDescriptor& desc) {
|
||||||
|
name = desc.name;
|
||||||
|
pad1 = desc.pad1;
|
||||||
|
pad2 = desc.pad2;
|
||||||
|
disposition = desc.disposition;
|
||||||
|
type = desc.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
mach_port_t GetMachPort() const {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
mach_msg_type_name_t GetDisposition() const {
|
||||||
|
return disposition;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're just a simple wrapper for mach_msg_port_descriptor_t
|
||||||
|
// and have the same memory layout
|
||||||
|
operator mach_msg_port_descriptor_t&() {
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For convenience
|
||||||
|
operator mach_port_t() const {
|
||||||
|
return GetMachPort();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// MachMessage: a wrapper for a mach message
|
||||||
|
// (mach_msg_header_t, mach_msg_body_t, extra data)
|
||||||
|
//
|
||||||
|
// This considerably simplifies the construction of a message for sending
|
||||||
|
// and the getting at relevant data and descriptors for the receiver.
|
||||||
|
//
|
||||||
|
// Currently the combined size of the descriptors plus data must be
|
||||||
|
// less than 1024. But as a benefit no memory allocation is necessary.
|
||||||
|
//
|
||||||
|
// TODO: could consider adding malloc() support for very large messages
|
||||||
|
//
|
||||||
|
// A MachMessage object is used by ReceivePort::WaitForMessage
|
||||||
|
// and MachPortSender::SendMessage
|
||||||
|
//
|
||||||
|
class MachMessage {
|
||||||
|
public:
|
||||||
|
|
||||||
|
// The receiver of the message can retrieve the raw data this way
|
||||||
|
u_int8_t *GetData() {
|
||||||
|
return GetDataLength() > 0 ? GetDataPacket()->data : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
u_int32_t GetDataLength() {
|
||||||
|
return EndianU32_LtoN(GetDataPacket()->data_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The message ID may be used as a code identifying the type of message
|
||||||
|
void SetMessageID(int32_t message_id) {
|
||||||
|
GetDataPacket()->id = EndianU32_NtoL(message_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t GetMessageID() { return EndianU32_LtoN(GetDataPacket()->id); }
|
||||||
|
|
||||||
|
// Adds a descriptor (typically a mach port) to be translated
|
||||||
|
// returns true if successful, otherwise not enough space
|
||||||
|
bool AddDescriptor(const MachMsgPortDescriptor &desc);
|
||||||
|
|
||||||
|
int GetDescriptorCount() const { return body.msgh_descriptor_count; }
|
||||||
|
MachMsgPortDescriptor *GetDescriptor(int n);
|
||||||
|
|
||||||
|
// Convenience method which gets the mach port described by the descriptor
|
||||||
|
mach_port_t GetTranslatedPort(int n);
|
||||||
|
|
||||||
|
// A simple message is one with no descriptors
|
||||||
|
bool IsSimpleMessage() const { return GetDescriptorCount() == 0; }
|
||||||
|
|
||||||
|
// Sets raw data for the message (returns false if not enough space)
|
||||||
|
bool SetData(void *data, int32_t data_length);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Consider this an abstract base class - must create an actual instance
|
||||||
|
// of MachReceiveMessage or MachSendMessage
|
||||||
|
|
||||||
|
MachMessage() {
|
||||||
|
memset(this, 0, sizeof(MachMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
friend class ReceivePort;
|
||||||
|
friend class MachPortSender;
|
||||||
|
|
||||||
|
// Represents raw data in our message
|
||||||
|
struct MessageDataPacket {
|
||||||
|
int32_t id; // little-endian
|
||||||
|
int32_t data_length; // little-endian
|
||||||
|
u_int8_t data[1]; // actual size limited by sizeof(MachMessage)
|
||||||
|
};
|
||||||
|
|
||||||
|
MessageDataPacket* GetDataPacket();
|
||||||
|
|
||||||
|
void SetDescriptorCount(int n);
|
||||||
|
void SetDescriptor(int n, const MachMsgPortDescriptor &desc);
|
||||||
|
|
||||||
|
// Returns total message size setting msgh_size in the header to this value
|
||||||
|
int CalculateSize();
|
||||||
|
|
||||||
|
mach_msg_header_t head;
|
||||||
|
mach_msg_body_t body;
|
||||||
|
u_int8_t padding[1024]; // descriptors and data may be embedded here
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// MachReceiveMessage and MachSendMessage are useful to separate the idea
|
||||||
|
// of a mach message being sent and being received, and adds increased type
|
||||||
|
// safety:
|
||||||
|
// ReceivePort::WaitForMessage() only accepts a MachReceiveMessage
|
||||||
|
// MachPortSender::SendMessage() only accepts a MachSendMessage
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
class MachReceiveMessage : public MachMessage {
|
||||||
|
public:
|
||||||
|
MachReceiveMessage() : MachMessage() {};
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
class MachSendMessage : public MachMessage {
|
||||||
|
public:
|
||||||
|
MachSendMessage(int32_t message_id);
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// Represents a mach port for which we have receive rights
|
||||||
|
class ReceivePort {
|
||||||
|
public:
|
||||||
|
// Creates a new mach port for receiving messages and registers a name for it
|
||||||
|
ReceivePort(const char *receive_port_name);
|
||||||
|
|
||||||
|
// Given an already existing mach port, use it. We take ownership of the
|
||||||
|
// port and deallocate it in our destructor.
|
||||||
|
ReceivePort(mach_port_t receive_port);
|
||||||
|
|
||||||
|
// Create a new mach port for receiving messages
|
||||||
|
ReceivePort();
|
||||||
|
|
||||||
|
~ReceivePort();
|
||||||
|
|
||||||
|
// Waits on the mach port until message received or timeout
|
||||||
|
kern_return_t WaitForMessage(MachReceiveMessage *out_message,
|
||||||
|
mach_msg_timeout_t timeout);
|
||||||
|
|
||||||
|
// The underlying mach port that we wrap
|
||||||
|
mach_port_t GetPort() const { return port_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
ReceivePort(const ReceivePort&); // disable copy c-tor
|
||||||
|
|
||||||
|
mach_port_t port_;
|
||||||
|
kern_return_t init_result_;
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// Represents a mach port for which we have send rights
|
||||||
|
class MachPortSender {
|
||||||
|
public:
|
||||||
|
// get a port with send rights corresponding to a named registered service
|
||||||
|
MachPortSender(const char *receive_port_name);
|
||||||
|
|
||||||
|
|
||||||
|
// Given an already existing mach port, use it.
|
||||||
|
MachPortSender(mach_port_t send_port);
|
||||||
|
|
||||||
|
kern_return_t SendMessage(MachSendMessage &message,
|
||||||
|
mach_msg_timeout_t timeout);
|
||||||
|
|
||||||
|
private:
|
||||||
|
MachPortSender(const MachPortSender&); // disable copy c-tor
|
||||||
|
|
||||||
|
mach_port_t send_port_;
|
||||||
|
kern_return_t init_result_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MACH_IPC_H__
|
297
src/common/mac/MachIPC.mm
Normal file
297
src/common/mac/MachIPC.mm
Normal file
|
@ -0,0 +1,297 @@
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// MachIPC.mm
|
||||||
|
// Wrapper for mach IPC calls
|
||||||
|
|
||||||
|
#import <stdio.h>
|
||||||
|
#import "MachIPC.h"
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
MachSendMessage::MachSendMessage(int32_t message_id) : MachMessage() {
|
||||||
|
head.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
|
||||||
|
|
||||||
|
// head.msgh_remote_port = ...; // filled out in MachPortSender::SendMessage()
|
||||||
|
head.msgh_local_port = MACH_PORT_NULL;
|
||||||
|
head.msgh_reserved = 0;
|
||||||
|
head.msgh_id = 0;
|
||||||
|
|
||||||
|
SetDescriptorCount(0); // start out with no descriptors
|
||||||
|
|
||||||
|
SetMessageID(message_id);
|
||||||
|
SetData(NULL, 0); // client may add data later
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// returns true if successful
|
||||||
|
bool MachMessage::SetData(void *data,
|
||||||
|
int32_t data_length) {
|
||||||
|
// first check to make sure we have enough space
|
||||||
|
int size = CalculateSize();
|
||||||
|
int new_size = size + data_length;
|
||||||
|
|
||||||
|
if ((unsigned)new_size > sizeof(MachMessage)) {
|
||||||
|
return false; // not enough space
|
||||||
|
}
|
||||||
|
|
||||||
|
GetDataPacket()->data_length = EndianU32_NtoL(data_length);
|
||||||
|
if (data) memcpy(GetDataPacket()->data, data, data_length);
|
||||||
|
|
||||||
|
CalculateSize();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// calculates and returns the total size of the message
|
||||||
|
// Currently, the entire message MUST fit inside of the MachMessage
|
||||||
|
// messsage size <= sizeof(MachMessage)
|
||||||
|
int MachMessage::CalculateSize() {
|
||||||
|
int size = sizeof(mach_msg_header_t) + sizeof(mach_msg_body_t);
|
||||||
|
|
||||||
|
// add space for MessageDataPacket
|
||||||
|
int32_t alignedDataLength = (GetDataLength() + 3) & ~0x3;
|
||||||
|
size += 2*sizeof(int32_t) + alignedDataLength;
|
||||||
|
|
||||||
|
// add space for descriptors
|
||||||
|
size += GetDescriptorCount() * sizeof(MachMsgPortDescriptor);
|
||||||
|
|
||||||
|
head.msgh_size = size;
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
MachMessage::MessageDataPacket *MachMessage::GetDataPacket() {
|
||||||
|
int desc_size = sizeof(MachMsgPortDescriptor)*GetDescriptorCount();
|
||||||
|
MessageDataPacket *packet =
|
||||||
|
reinterpret_cast<MessageDataPacket*>(padding + desc_size);
|
||||||
|
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void MachMessage::SetDescriptor(int n,
|
||||||
|
const MachMsgPortDescriptor &desc) {
|
||||||
|
MachMsgPortDescriptor *desc_array =
|
||||||
|
reinterpret_cast<MachMsgPortDescriptor*>(padding);
|
||||||
|
desc_array[n] = desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// returns true if successful otherwise there was not enough space
|
||||||
|
bool MachMessage::AddDescriptor(const MachMsgPortDescriptor &desc) {
|
||||||
|
// first check to make sure we have enough space
|
||||||
|
int size = CalculateSize();
|
||||||
|
int new_size = size + sizeof(MachMsgPortDescriptor);
|
||||||
|
|
||||||
|
if ((unsigned)new_size > sizeof(MachMessage)) {
|
||||||
|
return false; // not enough space
|
||||||
|
}
|
||||||
|
|
||||||
|
// unfortunately, we need to move the data to allow space for the
|
||||||
|
// new descriptor
|
||||||
|
u_int8_t *p = reinterpret_cast<u_int8_t*>(GetDataPacket());
|
||||||
|
bcopy(p, p+sizeof(MachMsgPortDescriptor), GetDataLength()+2*sizeof(int32_t));
|
||||||
|
|
||||||
|
SetDescriptor(GetDescriptorCount(), desc);
|
||||||
|
SetDescriptorCount(GetDescriptorCount() + 1);
|
||||||
|
|
||||||
|
CalculateSize();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void MachMessage::SetDescriptorCount(int n) {
|
||||||
|
body.msgh_descriptor_count = n;
|
||||||
|
|
||||||
|
if (n > 0) {
|
||||||
|
head.msgh_bits |= MACH_MSGH_BITS_COMPLEX;
|
||||||
|
} else {
|
||||||
|
head.msgh_bits &= ~MACH_MSGH_BITS_COMPLEX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
MachMsgPortDescriptor *MachMessage::GetDescriptor(int n) {
|
||||||
|
if (n < GetDescriptorCount()) {
|
||||||
|
MachMsgPortDescriptor *desc =
|
||||||
|
reinterpret_cast<MachMsgPortDescriptor*>(padding);
|
||||||
|
return desc + n;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
mach_port_t MachMessage::GetTranslatedPort(int n) {
|
||||||
|
if (n < GetDescriptorCount()) {
|
||||||
|
return GetDescriptor(n)->GetMachPort();
|
||||||
|
}
|
||||||
|
return MACH_PORT_NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// create a new mach port for receiving messages and register a name for it
|
||||||
|
ReceivePort::ReceivePort(const char *receive_port_name) {
|
||||||
|
mach_port_t current_task = mach_task_self();
|
||||||
|
|
||||||
|
init_result_ = mach_port_allocate(current_task,
|
||||||
|
MACH_PORT_RIGHT_RECEIVE,
|
||||||
|
&port_);
|
||||||
|
|
||||||
|
if (init_result_ != KERN_SUCCESS)
|
||||||
|
return;
|
||||||
|
|
||||||
|
init_result_ = mach_port_insert_right(current_task,
|
||||||
|
port_,
|
||||||
|
port_,
|
||||||
|
MACH_MSG_TYPE_MAKE_SEND);
|
||||||
|
|
||||||
|
if (init_result_ != KERN_SUCCESS)
|
||||||
|
return;
|
||||||
|
|
||||||
|
mach_port_t bootstrap_port = 0;
|
||||||
|
init_result_ = task_get_bootstrap_port(current_task, &bootstrap_port);
|
||||||
|
|
||||||
|
if (init_result_ != KERN_SUCCESS)
|
||||||
|
return;
|
||||||
|
|
||||||
|
init_result_ = bootstrap_register(bootstrap_port,
|
||||||
|
const_cast<char*>(receive_port_name),
|
||||||
|
port_);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// create a new mach port for receiving messages
|
||||||
|
ReceivePort::ReceivePort() {
|
||||||
|
mach_port_t current_task = mach_task_self();
|
||||||
|
|
||||||
|
init_result_ = mach_port_allocate(current_task,
|
||||||
|
MACH_PORT_RIGHT_RECEIVE,
|
||||||
|
&port_);
|
||||||
|
|
||||||
|
if (init_result_ != KERN_SUCCESS)
|
||||||
|
return;
|
||||||
|
|
||||||
|
init_result_ = mach_port_insert_right(current_task,
|
||||||
|
port_,
|
||||||
|
port_,
|
||||||
|
MACH_MSG_TYPE_MAKE_SEND);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// Given an already existing mach port, use it. We take ownership of the
|
||||||
|
// port and deallocate it in our destructor.
|
||||||
|
ReceivePort::ReceivePort(mach_port_t receive_port)
|
||||||
|
: port_(receive_port),
|
||||||
|
init_result_(KERN_SUCCESS) {
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
ReceivePort::~ReceivePort() {
|
||||||
|
if (init_result_ == KERN_SUCCESS)
|
||||||
|
mach_port_deallocate(mach_task_self(), port_);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
kern_return_t ReceivePort::WaitForMessage(MachReceiveMessage *out_message,
|
||||||
|
mach_msg_timeout_t timeout) {
|
||||||
|
if (!out_message) {
|
||||||
|
return KERN_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return any error condition encountered in constructor
|
||||||
|
if (init_result_ != KERN_SUCCESS)
|
||||||
|
return init_result_;
|
||||||
|
|
||||||
|
out_message->head.msgh_bits = 0;
|
||||||
|
out_message->head.msgh_local_port = port_;
|
||||||
|
out_message->head.msgh_remote_port = MACH_PORT_NULL;
|
||||||
|
out_message->head.msgh_reserved = 0;
|
||||||
|
out_message->head.msgh_id = 0;
|
||||||
|
|
||||||
|
kern_return_t result = mach_msg(&out_message->head,
|
||||||
|
MACH_RCV_MSG | MACH_RCV_TIMEOUT,
|
||||||
|
0,
|
||||||
|
sizeof(MachMessage),
|
||||||
|
port_,
|
||||||
|
timeout, // timeout in ms
|
||||||
|
MACH_PORT_NULL);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark -
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// get a port with send rights corresponding to a named registered service
|
||||||
|
MachPortSender::MachPortSender(const char *receive_port_name) {
|
||||||
|
mach_port_t bootstrap_port = 0;
|
||||||
|
init_result_ = task_get_bootstrap_port(mach_task_self(), &bootstrap_port);
|
||||||
|
|
||||||
|
if (init_result_ != KERN_SUCCESS)
|
||||||
|
return;
|
||||||
|
|
||||||
|
init_result_ = bootstrap_look_up(bootstrap_port,
|
||||||
|
const_cast<char*>(receive_port_name),
|
||||||
|
&send_port_);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
MachPortSender::MachPortSender(mach_port_t send_port)
|
||||||
|
: send_port_(send_port),
|
||||||
|
init_result_(KERN_SUCCESS) {
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
kern_return_t MachPortSender::SendMessage(MachSendMessage &message,
|
||||||
|
mach_msg_timeout_t timeout) {
|
||||||
|
if (message.head.msgh_size == 0) {
|
||||||
|
return KERN_INVALID_VALUE; // just for safety -- never should occur
|
||||||
|
};
|
||||||
|
|
||||||
|
if (init_result_ != KERN_SUCCESS)
|
||||||
|
return init_result_;
|
||||||
|
|
||||||
|
message.head.msgh_remote_port = send_port_;
|
||||||
|
|
||||||
|
kern_return_t result = mach_msg(&message.head,
|
||||||
|
MACH_SEND_MSG | MACH_SEND_TIMEOUT,
|
||||||
|
message.head.msgh_size,
|
||||||
|
0,
|
||||||
|
MACH_PORT_NULL,
|
||||||
|
timeout, // timeout in ms
|
||||||
|
MACH_PORT_NULL);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
195
src/common/mac/SimpleStringDictionary.h
Normal file
195
src/common/mac/SimpleStringDictionary.h
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// SimpleStringDictionary.h
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef SimpleStringDictionary_H__
|
||||||
|
#define SimpleStringDictionary_H__
|
||||||
|
|
||||||
|
#import <string>
|
||||||
|
#import <vector>
|
||||||
|
|
||||||
|
namespace google_breakpad {
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// SimpleStringDictionary (and associated class KeyValueEntry) implement a very
|
||||||
|
// basic dictionary container class. It has the property of not making any
|
||||||
|
// memory allocations when getting and setting values. But it is not very
|
||||||
|
// efficient, with calls to get and set values operating in linear time.
|
||||||
|
// It has the additional limitation of having a fairly small fixed capacity of
|
||||||
|
// SimpleStringDictionary::MAX_NUM_ENTRIES entries. An assert() will fire if
|
||||||
|
// the client attempts to set more than this number of key/value pairs.
|
||||||
|
// Ordinarilly a C++ programmer would use something like the std::map template
|
||||||
|
// class, or on the Macintosh would often choose CFDictionary or NSDictionary.
|
||||||
|
// But these dictionary classes may call malloc() during get and set operations.
|
||||||
|
// Google Breakpad requires that no memory allocations be made in code running
|
||||||
|
// in its exception handling thread, so it uses SimpleStringDictionary as the
|
||||||
|
// underlying implementation for the GoogleBreakpad.framework APIs:
|
||||||
|
// GoogleBreakpadSetKeyValue(), GoogleBreakpadKeyValue(), and
|
||||||
|
// GoogleBreakpadRemoveKeyValue()
|
||||||
|
//
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// KeyValueEntry
|
||||||
|
//
|
||||||
|
// A helper class used by SimpleStringDictionary representing a single
|
||||||
|
// storage cell for a key/value pair. Each key and value string are
|
||||||
|
// limited to MAX_STRING_STORAGE_SIZE-1 bytes (not glyphs). This class
|
||||||
|
// performs no memory allocations. It has methods for setting and getting
|
||||||
|
// key and value strings.
|
||||||
|
//
|
||||||
|
class KeyValueEntry {
|
||||||
|
public:
|
||||||
|
KeyValueEntry() {
|
||||||
|
Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyValueEntry(const char *key, const char *value) {
|
||||||
|
SetKeyValue(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetKeyValue(const char *key, const char *value) {
|
||||||
|
if (!key) {
|
||||||
|
key = "";
|
||||||
|
}
|
||||||
|
if (!value) {
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
strlcpy(key_, key, sizeof(key_));
|
||||||
|
strlcpy(value_, value, sizeof(value_));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetValue(const char *value) {
|
||||||
|
if (!value) {
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
strlcpy(value_, value, sizeof(value_));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Removes the key/value
|
||||||
|
void Clear() {
|
||||||
|
memset(key_, 0, sizeof(key_));
|
||||||
|
memset(value_, 0, sizeof(value_));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsActive() const { return key_[0] != '\0'; }
|
||||||
|
const char *GetKey() const { return key_; }
|
||||||
|
const char *GetValue() const { return value_; }
|
||||||
|
|
||||||
|
// Don't change this without considering the fixed size
|
||||||
|
// of MachMessage (in MachIPC.h)
|
||||||
|
// (see also struct KeyValueMessageData in Inspector.h)
|
||||||
|
enum {MAX_STRING_STORAGE_SIZE = 256};
|
||||||
|
|
||||||
|
private:
|
||||||
|
char key_[MAX_STRING_STORAGE_SIZE];
|
||||||
|
char value_[MAX_STRING_STORAGE_SIZE];
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
// This class is not an efficient dictionary, but for the purposes of breakpad
|
||||||
|
// will be just fine. We're just dealing with ten or so distinct
|
||||||
|
// key/value pairs. The idea is to avoid any malloc() or free() calls
|
||||||
|
// in certain important methods to be called when a process is in a
|
||||||
|
// crashed state. Each key and value string are limited to
|
||||||
|
// KeyValueEntry::MAX_STRING_STORAGE_SIZE-1 bytes (not glyphs). Strings passed
|
||||||
|
// in exceeding this length will be truncated.
|
||||||
|
//
|
||||||
|
class SimpleStringDictionary {
|
||||||
|
public:
|
||||||
|
SimpleStringDictionary() {}; // entries will all be cleared
|
||||||
|
|
||||||
|
// Returns the number of active key/value pairs. The upper limit for this
|
||||||
|
// is MAX_NUM_ENTRIES.
|
||||||
|
int GetCount() const;
|
||||||
|
|
||||||
|
// Given |key|, returns its corresponding |value|.
|
||||||
|
// If |key| is NULL, an assert will fire or NULL will be returned. If |key|
|
||||||
|
// is not found or is an empty string, NULL is returned.
|
||||||
|
const char *GetValueForKey(const char *key);
|
||||||
|
|
||||||
|
// Stores a string |value| represented by |key|. If |key| is NULL or an empty
|
||||||
|
// string, this will assert (or do nothing). If |value| is NULL then
|
||||||
|
// the |key| will be removed. An empty string is OK for |value|.
|
||||||
|
void SetKeyValue(const char *key, const char *value);
|
||||||
|
|
||||||
|
// Given |key|, removes any associated value. It will assert (or do nothing)
|
||||||
|
// if NULL is passed in. It will do nothing if |key| is not found.
|
||||||
|
void RemoveKey(const char *key);
|
||||||
|
|
||||||
|
// This is the maximum number of key/value pairs which may be set in the
|
||||||
|
// dictionary. An assert may fire if more values than this are set.
|
||||||
|
// Don't change this without also changing comment in GoogleBreakpad.h
|
||||||
|
enum {MAX_NUM_ENTRIES = 64};
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class SimpleStringDictionaryIterator;
|
||||||
|
|
||||||
|
const KeyValueEntry *GetEntry(int i) const;
|
||||||
|
|
||||||
|
KeyValueEntry entries_[MAX_NUM_ENTRIES];
|
||||||
|
};
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
class SimpleStringDictionaryIterator {
|
||||||
|
public:
|
||||||
|
SimpleStringDictionaryIterator(const SimpleStringDictionary &dict)
|
||||||
|
: dict_(dict), i_(0) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initializes iterator to the beginning (may later call Next() )
|
||||||
|
void Start() {
|
||||||
|
i_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// like the nextObject method of NSEnumerator (in Cocoa)
|
||||||
|
// returns NULL when there are no more entries
|
||||||
|
//
|
||||||
|
const KeyValueEntry* Next() {
|
||||||
|
for (; i_ < SimpleStringDictionary::MAX_NUM_ENTRIES; ++i_) {
|
||||||
|
const KeyValueEntry *entry = dict_.GetEntry(i_);
|
||||||
|
if (entry->IsActive()) {
|
||||||
|
i_++; // move to next entry for next time
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL; // reached end of array
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const SimpleStringDictionary& dict_;
|
||||||
|
int i_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace google_breakpad
|
||||||
|
|
||||||
|
#endif // SimpleStringDictionary_H__
|
131
src/common/mac/SimpleStringDictionary.mm
Normal file
131
src/common/mac/SimpleStringDictionary.mm
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// SimpleStringDictionary.mm
|
||||||
|
// Simple string dictionary that does not allocate memory
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "SimpleStringDictionary.h"
|
||||||
|
|
||||||
|
namespace google_breakpad {
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
const KeyValueEntry *SimpleStringDictionary::GetEntry(int i) const {
|
||||||
|
return (i >= 0 && i < MAX_NUM_ENTRIES) ? &entries_[i] : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
int SimpleStringDictionary::GetCount() const {
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 0; i < MAX_NUM_ENTRIES; ++i) {
|
||||||
|
if (entries_[i].IsActive() ) {
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
const char *SimpleStringDictionary::GetValueForKey(const char *key) {
|
||||||
|
assert(key);
|
||||||
|
if (!key)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
for (int i = 0; i < MAX_NUM_ENTRIES; ++i) {
|
||||||
|
KeyValueEntry &entry = entries_[i];
|
||||||
|
if (entry.IsActive() && !strcmp(entry.GetKey(), key)) {
|
||||||
|
return entry.GetValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void SimpleStringDictionary::SetKeyValue(const char *key,
|
||||||
|
const char *value) {
|
||||||
|
if (!value) {
|
||||||
|
RemoveKey(key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// key must not be NULL
|
||||||
|
assert(key);
|
||||||
|
if (!key)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// key must not be empty string
|
||||||
|
assert(key[0] != '\0');
|
||||||
|
if (key[0] == '\0')
|
||||||
|
return;
|
||||||
|
|
||||||
|
int free_index = -1;
|
||||||
|
|
||||||
|
// check if key already exists
|
||||||
|
for (int i = 0; i < MAX_NUM_ENTRIES; ++i) {
|
||||||
|
KeyValueEntry &entry = entries_[i];
|
||||||
|
|
||||||
|
if (entry.IsActive()) {
|
||||||
|
if (!strcmp(entry.GetKey(), key)) {
|
||||||
|
entry.SetValue(value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Make a note of an empty slot
|
||||||
|
if (free_index == -1) {
|
||||||
|
free_index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if we've run out of space
|
||||||
|
assert(free_index != -1);
|
||||||
|
|
||||||
|
// Put new key into an empty slot (if found)
|
||||||
|
if (free_index != -1) {
|
||||||
|
entries_[free_index].SetKeyValue(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
void SimpleStringDictionary::RemoveKey(const char *key) {
|
||||||
|
assert(key);
|
||||||
|
if (!key)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int i = 0; i < MAX_NUM_ENTRIES; ++i) {
|
||||||
|
if (!strcmp(entries_[i].GetKey(), key)) {
|
||||||
|
entries_[i].Clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace google_breakpad
|
1004
src/common/mac/testing/GTMSenTestCase.h
Normal file
1004
src/common/mac/testing/GTMSenTestCase.h
Normal file
File diff suppressed because it is too large
Load diff
366
src/common/mac/testing/GTMSenTestCase.m
Normal file
366
src/common/mac/testing/GTMSenTestCase.m
Normal file
|
@ -0,0 +1,366 @@
|
||||||
|
//
|
||||||
|
// GTMSenTestCase.m
|
||||||
|
//
|
||||||
|
// Copyright 2007-2008 Google Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
// use this file except in compliance with the License. You may obtain a copy
|
||||||
|
// of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
// License for the specific language governing permissions and limitations under
|
||||||
|
// the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "GTMSenTestCase.h"
|
||||||
|
#import <unistd.h>
|
||||||
|
|
||||||
|
#if !GTM_IPHONE_SDK
|
||||||
|
#import "GTMGarbageCollection.h"
|
||||||
|
#endif // !GTM_IPHONE_SDK
|
||||||
|
|
||||||
|
#if GTM_IPHONE_SDK
|
||||||
|
#import <stdarg.h>
|
||||||
|
|
||||||
|
@interface NSException (GTMSenTestPrivateAdditions)
|
||||||
|
+ (NSException *)failureInFile:(NSString *)filename
|
||||||
|
atLine:(int)lineNumber
|
||||||
|
reason:(NSString *)reason;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation NSException (GTMSenTestPrivateAdditions)
|
||||||
|
+ (NSException *)failureInFile:(NSString *)filename
|
||||||
|
atLine:(int)lineNumber
|
||||||
|
reason:(NSString *)reason {
|
||||||
|
NSDictionary *userInfo =
|
||||||
|
[NSDictionary dictionaryWithObjectsAndKeys:
|
||||||
|
[NSNumber numberWithInteger:lineNumber], SenTestLineNumberKey,
|
||||||
|
filename, SenTestFilenameKey,
|
||||||
|
nil];
|
||||||
|
|
||||||
|
return [self exceptionWithName:SenTestFailureException
|
||||||
|
reason:reason
|
||||||
|
userInfo:userInfo];
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation NSException (GTMSenTestAdditions)
|
||||||
|
|
||||||
|
+ (NSException *)failureInFile:(NSString *)filename
|
||||||
|
atLine:(int)lineNumber
|
||||||
|
withDescription:(NSString *)formatString, ... {
|
||||||
|
|
||||||
|
NSString *testDescription = @"";
|
||||||
|
if (formatString) {
|
||||||
|
va_list vl;
|
||||||
|
va_start(vl, formatString);
|
||||||
|
testDescription =
|
||||||
|
[[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
|
||||||
|
va_end(vl);
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *reason = testDescription;
|
||||||
|
|
||||||
|
return [self failureInFile:filename atLine:lineNumber reason:reason];
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSException *)failureInCondition:(NSString *)condition
|
||||||
|
isTrue:(BOOL)isTrue
|
||||||
|
inFile:(NSString *)filename
|
||||||
|
atLine:(int)lineNumber
|
||||||
|
withDescription:(NSString *)formatString, ... {
|
||||||
|
|
||||||
|
NSString *testDescription = @"";
|
||||||
|
if (formatString) {
|
||||||
|
va_list vl;
|
||||||
|
va_start(vl, formatString);
|
||||||
|
testDescription =
|
||||||
|
[[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
|
||||||
|
va_end(vl);
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *reason = [NSString stringWithFormat:@"'%@' should be %s. %@",
|
||||||
|
condition, isTrue ? "TRUE" : "FALSE", testDescription];
|
||||||
|
|
||||||
|
return [self failureInFile:filename atLine:lineNumber reason:reason];
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSException *)failureInEqualityBetweenObject:(id)left
|
||||||
|
andObject:(id)right
|
||||||
|
inFile:(NSString *)filename
|
||||||
|
atLine:(int)lineNumber
|
||||||
|
withDescription:(NSString *)formatString, ... {
|
||||||
|
|
||||||
|
NSString *testDescription = @"";
|
||||||
|
if (formatString) {
|
||||||
|
va_list vl;
|
||||||
|
va_start(vl, formatString);
|
||||||
|
testDescription =
|
||||||
|
[[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
|
||||||
|
va_end(vl);
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *reason =
|
||||||
|
[NSString stringWithFormat:@"'%@' should be equal to '%@'. %@",
|
||||||
|
[left description], [right description], testDescription];
|
||||||
|
|
||||||
|
return [self failureInFile:filename atLine:lineNumber reason:reason];
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSException *)failureInEqualityBetweenValue:(NSValue *)left
|
||||||
|
andValue:(NSValue *)right
|
||||||
|
withAccuracy:(NSValue *)accuracy
|
||||||
|
inFile:(NSString *)filename
|
||||||
|
atLine:(int)lineNumber
|
||||||
|
withDescription:(NSString *)formatString, ... {
|
||||||
|
|
||||||
|
NSString *testDescription = @"";
|
||||||
|
if (formatString) {
|
||||||
|
va_list vl;
|
||||||
|
va_start(vl, formatString);
|
||||||
|
testDescription =
|
||||||
|
[[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
|
||||||
|
va_end(vl);
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *reason;
|
||||||
|
if (accuracy) {
|
||||||
|
reason =
|
||||||
|
[NSString stringWithFormat:@"'%@' should be equal to '%@'. %@",
|
||||||
|
left, right, testDescription];
|
||||||
|
} else {
|
||||||
|
reason =
|
||||||
|
[NSString stringWithFormat:@"'%@' should be equal to '%@' +/-'%@'. %@",
|
||||||
|
left, right, accuracy, testDescription];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [self failureInFile:filename atLine:lineNumber reason:reason];
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSException *)failureInRaise:(NSString *)expression
|
||||||
|
inFile:(NSString *)filename
|
||||||
|
atLine:(int)lineNumber
|
||||||
|
withDescription:(NSString *)formatString, ... {
|
||||||
|
|
||||||
|
NSString *testDescription = @"";
|
||||||
|
if (formatString) {
|
||||||
|
va_list vl;
|
||||||
|
va_start(vl, formatString);
|
||||||
|
testDescription =
|
||||||
|
[[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
|
||||||
|
va_end(vl);
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *reason = [NSString stringWithFormat:@"'%@' should raise. %@",
|
||||||
|
expression, testDescription];
|
||||||
|
|
||||||
|
return [self failureInFile:filename atLine:lineNumber reason:reason];
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSException *)failureInRaise:(NSString *)expression
|
||||||
|
exception:(NSException *)exception
|
||||||
|
inFile:(NSString *)filename
|
||||||
|
atLine:(int)lineNumber
|
||||||
|
withDescription:(NSString *)formatString, ... {
|
||||||
|
|
||||||
|
NSString *testDescription = @"";
|
||||||
|
if (formatString) {
|
||||||
|
va_list vl;
|
||||||
|
va_start(vl, formatString);
|
||||||
|
testDescription =
|
||||||
|
[[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
|
||||||
|
va_end(vl);
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *reason;
|
||||||
|
if ([[exception name] isEqualToString:SenTestFailureException]) {
|
||||||
|
// it's our exception, assume it has the right description on it.
|
||||||
|
reason = [exception reason];
|
||||||
|
} else {
|
||||||
|
// not one of our exception, use the exceptions reason and our description
|
||||||
|
reason = [NSString stringWithFormat:@"'%@' raised '%@'. %@",
|
||||||
|
expression, [exception reason], testDescription];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [self failureInFile:filename atLine:lineNumber reason:reason];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NSString *STComposeString(NSString *formatString, ...) {
|
||||||
|
NSString *reason = @"";
|
||||||
|
if (formatString) {
|
||||||
|
va_list vl;
|
||||||
|
va_start(vl, formatString);
|
||||||
|
reason =
|
||||||
|
[[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
|
||||||
|
va_end(vl);
|
||||||
|
}
|
||||||
|
return reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *const SenTestFailureException = @"SenTestFailureException";
|
||||||
|
NSString *const SenTestFilenameKey = @"SenTestFilenameKey";
|
||||||
|
NSString *const SenTestLineNumberKey = @"SenTestLineNumberKey";
|
||||||
|
|
||||||
|
@interface SenTestCase (SenTestCasePrivate)
|
||||||
|
// our method of logging errors
|
||||||
|
+ (void)printException:(NSException *)exception fromTestName:(NSString *)name;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation SenTestCase
|
||||||
|
- (void)failWithException:(NSException*)exception {
|
||||||
|
[exception raise];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setUp {
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)performTest:(SEL)sel {
|
||||||
|
currentSelector_ = sel;
|
||||||
|
@try {
|
||||||
|
[self invokeTest];
|
||||||
|
} @catch (NSException *exception) {
|
||||||
|
[[self class] printException:exception
|
||||||
|
fromTestName:NSStringFromSelector(sel)];
|
||||||
|
[exception raise];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (void)printException:(NSException *)exception fromTestName:(NSString *)name {
|
||||||
|
NSDictionary *userInfo = [exception userInfo];
|
||||||
|
NSString *filename = [userInfo objectForKey:SenTestFilenameKey];
|
||||||
|
NSNumber *lineNumber = [userInfo objectForKey:SenTestLineNumberKey];
|
||||||
|
NSString *className = NSStringFromClass([self class]);
|
||||||
|
if ([filename length] == 0) {
|
||||||
|
filename = @"Unknown.m";
|
||||||
|
}
|
||||||
|
fprintf(stderr, "%s:%ld: error: -[%s %s] : %s\n",
|
||||||
|
[filename UTF8String],
|
||||||
|
(long)[lineNumber integerValue],
|
||||||
|
[className UTF8String],
|
||||||
|
[name UTF8String],
|
||||||
|
[[exception reason] UTF8String]);
|
||||||
|
fflush(stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)invokeTest {
|
||||||
|
NSException *e = nil;
|
||||||
|
@try {
|
||||||
|
// Wrap things in autorelease pools because they may
|
||||||
|
// have an STMacro in their dealloc which may get called
|
||||||
|
// when the pool is cleaned up
|
||||||
|
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||||
|
// We don't log exceptions here, instead we let the person that called
|
||||||
|
// this log the exception. This ensures they are only logged once but the
|
||||||
|
// outer layers get the exceptions to report counts, etc.
|
||||||
|
@try {
|
||||||
|
[self setUp];
|
||||||
|
@try {
|
||||||
|
[self performSelector:currentSelector_];
|
||||||
|
} @catch (NSException *exception) {
|
||||||
|
e = [exception retain];
|
||||||
|
}
|
||||||
|
[self tearDown];
|
||||||
|
} @catch (NSException *exception) {
|
||||||
|
e = [exception retain];
|
||||||
|
}
|
||||||
|
[pool release];
|
||||||
|
} @catch (NSException *exception) {
|
||||||
|
e = [exception retain];
|
||||||
|
}
|
||||||
|
if (e) {
|
||||||
|
[e autorelease];
|
||||||
|
[e raise];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)tearDown {
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)description {
|
||||||
|
// This matches the description OCUnit would return to you
|
||||||
|
return [NSString stringWithFormat:@"-[%@ %@]", [self class],
|
||||||
|
NSStringFromSelector(currentSelector_)];
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
#endif // GTM_IPHONE_SDK
|
||||||
|
|
||||||
|
@implementation GTMTestCase : SenTestCase
|
||||||
|
- (void)invokeTest {
|
||||||
|
Class devLogClass = NSClassFromString(@"GTMUnitTestDevLog");
|
||||||
|
if (devLogClass) {
|
||||||
|
[devLogClass performSelector:@selector(enableTracking)];
|
||||||
|
[devLogClass performSelector:@selector(verifyNoMoreLogsExpected)];
|
||||||
|
|
||||||
|
}
|
||||||
|
[super invokeTest];
|
||||||
|
if (devLogClass) {
|
||||||
|
[devLogClass performSelector:@selector(verifyNoMoreLogsExpected)];
|
||||||
|
[devLogClass performSelector:@selector(disableTracking)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
// Leak detection
|
||||||
|
#if !GTM_IPHONE_DEVICE
|
||||||
|
// Don't want to get leaks on the iPhone Device as the device doesn't
|
||||||
|
// have 'leaks'. The simulator does though.
|
||||||
|
|
||||||
|
// COV_NF_START
|
||||||
|
// We don't have leak checking on by default, so this won't be hit.
|
||||||
|
static void _GTMRunLeaks(void) {
|
||||||
|
// This is an atexit handler. It runs leaks for us to check if we are
|
||||||
|
// leaking anything in our tests.
|
||||||
|
const char* cExclusionsEnv = getenv("GTM_LEAKS_SYMBOLS_TO_IGNORE");
|
||||||
|
NSMutableString *exclusions = [NSMutableString string];
|
||||||
|
if (cExclusionsEnv) {
|
||||||
|
NSString *exclusionsEnv = [NSString stringWithUTF8String:cExclusionsEnv];
|
||||||
|
NSArray *exclusionsArray = [exclusionsEnv componentsSeparatedByString:@","];
|
||||||
|
NSString *exclusion;
|
||||||
|
NSCharacterSet *wcSet = [NSCharacterSet whitespaceCharacterSet];
|
||||||
|
GTM_FOREACH_OBJECT(exclusion, exclusionsArray) {
|
||||||
|
exclusion = [exclusion stringByTrimmingCharactersInSet:wcSet];
|
||||||
|
[exclusions appendFormat:@"-exclude \"%@\" ", exclusion];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NSString *string
|
||||||
|
= [NSString stringWithFormat:@"/usr/bin/leaks %@%d"
|
||||||
|
@"| /usr/bin/sed -e 's/Leak: /Leaks:0: warning: Leak /'",
|
||||||
|
exclusions, getpid()];
|
||||||
|
int ret = system([string UTF8String]);
|
||||||
|
if (ret) {
|
||||||
|
fprintf(stderr, "%s:%d: Error: Unable to run leaks. 'system' returned: %d",
|
||||||
|
__FILE__, __LINE__, ret);
|
||||||
|
fflush(stderr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// COV_NF_END
|
||||||
|
|
||||||
|
static __attribute__((constructor)) void _GTMInstallLeaks(void) {
|
||||||
|
BOOL checkLeaks = YES;
|
||||||
|
#if !GTM_IPHONE_SDK
|
||||||
|
checkLeaks = GTMIsGarbageCollectionEnabled() ? NO : YES;
|
||||||
|
#endif // !GTM_IPHONE_SDK
|
||||||
|
if (checkLeaks) {
|
||||||
|
checkLeaks = getenv("GTM_ENABLE_LEAKS") ? YES : NO;
|
||||||
|
if (checkLeaks) {
|
||||||
|
// COV_NF_START
|
||||||
|
// We don't have leak checking on by default, so this won't be hit.
|
||||||
|
fprintf(stderr, "Leak Checking Enabled\n");
|
||||||
|
fflush(stderr);
|
||||||
|
int ret = atexit(&_GTMRunLeaks);
|
||||||
|
_GTMDevAssert(ret == 0,
|
||||||
|
@"Unable to install _GTMRunLeaks as an atexit handler (%d)",
|
||||||
|
errno);
|
||||||
|
// COV_NF_END
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // !GTM_IPHONE_DEVICE
|
Loading…
Reference in a new issue