1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-08-30 17:20:26 +02:00

* Added breakpad support for Linux.

This commit is contained in:
Christian Muehlhaeuser
2011-09-15 07:27:31 +02:00
parent d8b07cee9c
commit d8d7347394
1163 changed files with 465521 additions and 4 deletions

View File

@@ -0,0 +1,217 @@
// Copyright (c) 2009, 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.
//
// BreakpadFramework_Test.mm
// Test case file for Breakpad.h/mm.
//
#import "GTMSenTestCase.h"
#import "Breakpad.h"
#include <mach/mach.h>
@interface BreakpadFramework_Test : GTMTestCase {
@private
int last_exception_code_;
int last_exception_type_;
mach_port_t last_exception_thread_;
// We're not using Obj-C BOOL because we need to interop with
// Breakpad's callback.
bool shouldHandleException_;
}
// This method is used by a callback used by test cases to determine
// whether to return true or false to Breakpad when handling an
// exception.
- (bool)shouldHandleException;
// This method returns a minimal dictionary that has what
// Breakpad needs to initialize.
- (NSMutableDictionary *)breakpadInitializationDictionary;
// This method is used by the exception handling callback
// to communicate to test cases the properites of the last
// exception.
- (void)setLastExceptionType:(int)type andCode:(int)code
andThread:(mach_port_t)thread;
@end
// Callback for Breakpad exceptions
bool myBreakpadCallback(int exception_type,
int exception_code,
mach_port_t crashing_thread,
void *context);
bool myBreakpadCallback(int exception_type,
int exception_code,
mach_port_t crashing_thread,
void *context) {
BreakpadFramework_Test *testCaseClass =
(BreakpadFramework_Test *)context;
[testCaseClass setLastExceptionType:exception_type
andCode:exception_code
andThread:crashing_thread];
bool shouldHandleException =
[testCaseClass shouldHandleException];
NSLog(@"Callback returning %d", shouldHandleException);
return shouldHandleException;
}
const int kNoLastExceptionCode = -1;
const int kNoLastExceptionType = -1;
const mach_port_t kNoLastExceptionThread = MACH_PORT_NULL;
@implementation BreakpadFramework_Test
- (void) initializeExceptionStateVariables {
last_exception_code_ = kNoLastExceptionCode;
last_exception_type_ = kNoLastExceptionType;
last_exception_thread_ = kNoLastExceptionThread;
}
- (NSMutableDictionary *)breakpadInitializationDictionary {
NSMutableDictionary *breakpadParams =
[NSMutableDictionary dictionaryWithCapacity:3];
[breakpadParams setObject:@"UnitTests" forKey:@BREAKPAD_PRODUCT];
[breakpadParams setObject:@"1.0" forKey:@BREAKPAD_VERSION];
[breakpadParams setObject:@"http://staging" forKey:@BREAKPAD_URL];
return breakpadParams;
}
- (bool)shouldHandleException {
return shouldHandleException_;
}
- (void)setLastExceptionType:(int)type
andCode:(int)code
andThread:(mach_port_t)thread {
last_exception_type_ = type;
last_exception_code_ = code;
last_exception_thread_ = thread;
}
// Test that the parameters mark required actually enable Breakpad to
// be initialized.
- (void)testBreakpadInstantiationWithRequiredParameters {
BreakpadRef b = BreakpadCreate([self breakpadInitializationDictionary]);
STAssertNotNULL(b, @"BreakpadCreate failed with required parameters");
BreakpadRelease(b);
}
// Test that Breakpad fails to initialize cleanly when required
// parameters are not present.
- (void)testBreakpadInstantiationWithoutRequiredParameters {
NSMutableDictionary *breakpadDictionary =
[self breakpadInitializationDictionary];
// Skip setting version, so that BreakpadCreate fails.
[breakpadDictionary removeObjectForKey:@BREAKPAD_VERSION];
BreakpadRef b = BreakpadCreate(breakpadDictionary);
STAssertNULL(b, @"BreakpadCreate did not fail when missing a required"
" parameter!");
breakpadDictionary = [self breakpadInitializationDictionary];
// Now test with no product
[breakpadDictionary removeObjectForKey:@BREAKPAD_PRODUCT];
b = BreakpadCreate(breakpadDictionary);
STAssertNULL(b, @"BreakpadCreate did not fail when missing a required"
" parameter!");
breakpadDictionary = [self breakpadInitializationDictionary];
// Now test with no URL
[breakpadDictionary removeObjectForKey:@BREAKPAD_URL];
b = BreakpadCreate(breakpadDictionary);
STAssertNULL(b, @"BreakpadCreate did not fail when missing a required"
" parameter!");
BreakpadRelease(b);
}
// Test to ensure that when we call BreakpadAddUploadParameter,
// it's added to the dictionary correctly(this test depends on
// some internal details of Breakpad, namely, the special prefix
// that it uses to figure out which key/value pairs to upload).
- (void)testAddingBreakpadServerVariable {
NSMutableDictionary *breakpadDictionary =
[self breakpadInitializationDictionary];
BreakpadRef b = BreakpadCreate(breakpadDictionary);
STAssertNotNULL(b, @"BreakpadCreate failed with valid dictionary!");
BreakpadAddUploadParameter(b,
@"key",
@"value");
// Test that it did not add the key/value directly, e.g. without
// prepending the key with the prefix.
STAssertNil(BreakpadKeyValue(b, @"key"),
@"AddUploadParameter added key directly to dictionary"
" instead of prepending it!");
NSString *prependedKeyname =
[@BREAKPAD_SERVER_PARAMETER_PREFIX stringByAppendingString:@"key"];
STAssertEqualStrings(BreakpadKeyValue(b, prependedKeyname),
@"value",
@"Calling BreakpadAddUploadParameter did not prepend "
"key name");
BreakpadRelease(b);
}
// Test that when we do on-demand minidump generation,
// the exception code/type/thread are set properly.
- (void)testFilterCallbackReturnsFalse {
NSMutableDictionary *breakpadDictionary =
[self breakpadInitializationDictionary];
BreakpadRef b = BreakpadCreate(breakpadDictionary);
STAssertNotNULL(b, @"BreakpadCreate failed with valid dictionary!");
BreakpadSetFilterCallback(b, &myBreakpadCallback, self);
// This causes the callback to return false, meaning
// Breakpad won't take the exception
shouldHandleException_ = false;
[self initializeExceptionStateVariables];
STAssertEquals(last_exception_type_, kNoLastExceptionType,
@"Last exception type not initialized correctly.");
STAssertEquals(last_exception_code_, kNoLastExceptionCode,
@"Last exception code not initialized correctly.");
STAssertEquals(last_exception_thread_, kNoLastExceptionThread,
@"Last exception thread is not initialized correctly.");
// Cause Breakpad's exception handler to be invoked.
BreakpadGenerateAndSendReport(b);
STAssertEquals(last_exception_type_, 0,
@"Last exception type is not 0 for on demand");
STAssertEquals(last_exception_code_, 0,
@"Last exception code is not 0 for on demand");
STAssertEquals(last_exception_thread_, mach_thread_self(),
@"Last exception thread is not mach_thread_self() "
"for on demand");
}
@end

View 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

View 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

View File

@@ -0,0 +1,72 @@
// Copyright (c) 2010, 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 class for creating a temporary directory for unit tests
// that is deleted in the destructor.
#ifndef GOOGLE_BREAKPAD_CLIENT_MAC_TESTS_AUTO_TEMPDIR
#define GOOGLE_BREAKPAD_CLIENT_MAC_TESTS_AUTO_TEMPDIR
#include <dirent.h>
#include <sys/types.h>
#include <string>
namespace google_breakpad {
class AutoTempDir {
public:
AutoTempDir() {
char tempDir[16] = "/tmp/XXXXXXXXXX";
mkdtemp(tempDir);
path = tempDir;
}
~AutoTempDir() {
// First remove any files in the dir
DIR* dir = opendir(path.c_str());
if (!dir)
return;
dirent* entry;
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
std::string entryPath = path + "/" + entry->d_name;
unlink(entryPath.c_str());
}
closedir(dir);
rmdir(path.c_str());
}
std::string path;
};
} // namespace google_breakpad
#endif // GOOGLE_BREAKPAD_CLIENT_MAC_TESTS_AUTO_TEMPDIR

View File

@@ -0,0 +1,345 @@
// Copyright (c) 2010, 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.
//
// crash_generation_server_test.cc
// Unit tests for CrashGenerationServer
#include <dirent.h>
#include <glob.h>
#include <stdint.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string>
#include "breakpad_googletest_includes.h"
#include "client/mac/crash_generation/client_info.h"
#include "client/mac/crash_generation/crash_generation_client.h"
#include "client/mac/crash_generation/crash_generation_server.h"
#include "client/mac/handler/exception_handler.h"
#include "client/mac/tests/auto_tempdir.h"
#include "client/mac/tests/spawn_child_process.h"
#include "google_breakpad/processor/minidump.h"
namespace google_breakpad {
// This acts as the log sink for INFO logging from the processor
// logging code. The logging output confuses XCode and makes it think
// there are unit test failures. testlogging.h handles the overriding.
std::ostringstream info_log;
}
namespace {
using std::string;
using google_breakpad::AutoTempDir;
using google_breakpad::ClientInfo;
using google_breakpad::CrashGenerationClient;
using google_breakpad::CrashGenerationServer;
using google_breakpad::ExceptionHandler;
using google_breakpad::Minidump;
using google_breakpad::MinidumpContext;
using google_breakpad::MinidumpException;
using google_breakpad::MinidumpModule;
using google_breakpad::MinidumpModuleList;
using google_breakpad::MinidumpSystemInfo;
using google_breakpad::MinidumpThread;
using google_breakpad::MinidumpThreadList;
using testing::Test;
using namespace google_breakpad_test;
class CrashGenerationServerTest : public Test {
public:
// The port name to receive messages on
char mach_port_name[128];
// Filename of the last dump that was generated
string last_dump_name;
// PID of the child process
pid_t child_pid;
// A temp dir
AutoTempDir temp_dir;
// Counter just to ensure that we don't hit the same port again
static int i;
void SetUp() {
sprintf(mach_port_name,
"com.google.breakpad.ServerTest.%d.%d", getpid(),
CrashGenerationServerTest::i++);
child_pid = (pid_t)-1;
}
};
int CrashGenerationServerTest::i = 0;
// Test that starting and stopping a server works
TEST_F(CrashGenerationServerTest, testStartStopServer) {
CrashGenerationServer server(mach_port_name,
NULL, // dump callback
NULL, // dump context
NULL, // exit callback
NULL, // exit context
false, // generate dumps
""); // dump path
ASSERT_TRUE(server.Start());
ASSERT_TRUE(server.Stop());
}
// Test that requesting a dump via CrashGenerationClient works
// Test without actually dumping
TEST_F(CrashGenerationServerTest, testRequestDumpNoDump) {
CrashGenerationServer server(mach_port_name,
NULL, // dump callback
NULL, // dump context
NULL, // exit callback
NULL, // exit context
false, // don't generate dumps
temp_dir.path); // dump path
ASSERT_TRUE(server.Start());
pid_t pid = fork();
ASSERT_NE(-1, pid);
if (pid == 0) {
CrashGenerationClient client(mach_port_name);
bool result = client.RequestDump();
exit(result ? 0 : 1);
}
int ret;
ASSERT_EQ(pid, waitpid(pid, &ret, 0));
EXPECT_TRUE(WIFEXITED(ret));
EXPECT_EQ(0, WEXITSTATUS(ret));
EXPECT_TRUE(server.Stop());
// check that no minidump was written
string pattern = temp_dir.path + "/*";
glob_t dirContents;
ret = glob(pattern.c_str(), GLOB_NOSORT, NULL, &dirContents);
EXPECT_EQ(GLOB_NOMATCH, ret);
if (ret != GLOB_NOMATCH)
globfree(&dirContents);
}
void dumpCallback(void *context, const ClientInfo &client_info,
const std::string &file_path) {
if (context) {
CrashGenerationServerTest* self =
reinterpret_cast<CrashGenerationServerTest*>(context);
if (!file_path.empty())
self->last_dump_name = file_path;
self->child_pid = client_info.pid();
}
}
void *RequestDump(void *context) {
CrashGenerationClient client((const char*)context);
bool result = client.RequestDump();
return (void*)(result ? 0 : 1);
}
// Test that actually writing a minidump works
TEST_F(CrashGenerationServerTest, testRequestDump) {
CrashGenerationServer server(mach_port_name,
dumpCallback, // dump callback
this, // dump context
NULL, // exit callback
NULL, // exit context
true, // generate dumps
temp_dir.path); // dump path
ASSERT_TRUE(server.Start());
pid_t pid = fork();
ASSERT_NE(-1, pid);
if (pid == 0) {
// Have to spawn off a separate thread to request the dump,
// because MinidumpGenerator assumes the handler thread is not
// the only thread
pthread_t thread;
if (pthread_create(&thread, NULL, RequestDump, (void*)mach_port_name) != 0)
exit(1);
void* result;
pthread_join(thread, &result);
exit(reinterpret_cast<intptr_t>(result));
}
int ret;
ASSERT_EQ(pid, waitpid(pid, &ret, 0));
EXPECT_TRUE(WIFEXITED(ret));
EXPECT_EQ(0, WEXITSTATUS(ret));
EXPECT_TRUE(server.Stop());
// check that minidump was written
ASSERT_FALSE(last_dump_name.empty());
struct stat st;
EXPECT_EQ(0, stat(last_dump_name.c_str(), &st));
EXPECT_LT(0, st.st_size);
// check client's PID
ASSERT_EQ(pid, child_pid);
}
static void Crasher() {
int *a = (int*)0x42;
fprintf(stdout, "Going to crash...\n");
fprintf(stdout, "A = %d", *a);
}
// Test that crashing a child process with an OOP ExceptionHandler installed
// results in a minidump being written by the CrashGenerationServer in
// the parent.
TEST_F(CrashGenerationServerTest, testChildProcessCrash) {
CrashGenerationServer server(mach_port_name,
dumpCallback, // dump callback
this, // dump context
NULL, // exit callback
NULL, // exit context
true, // generate dumps
temp_dir.path); // dump path
ASSERT_TRUE(server.Start());
pid_t pid = fork();
ASSERT_NE(-1, pid);
if (pid == 0) {
// Instantiate an OOP exception handler.
ExceptionHandler eh("", NULL, NULL, NULL, true, mach_port_name);
Crasher();
// not reached
exit(0);
}
int ret;
ASSERT_EQ(pid, waitpid(pid, &ret, 0));
EXPECT_FALSE(WIFEXITED(ret));
EXPECT_TRUE(server.Stop());
// check that minidump was written
ASSERT_FALSE(last_dump_name.empty());
struct stat st;
EXPECT_EQ(0, stat(last_dump_name.c_str(), &st));
EXPECT_LT(0, st.st_size);
// Read the minidump, sanity check some data.
Minidump minidump(last_dump_name.c_str());
ASSERT_TRUE(minidump.Read());
MinidumpSystemInfo* system_info = minidump.GetSystemInfo();
ASSERT_TRUE(system_info);
const MDRawSystemInfo* raw_info = system_info->system_info();
ASSERT_TRUE(raw_info);
EXPECT_EQ(kNativeArchitecture, raw_info->processor_architecture);
MinidumpThreadList* thread_list = minidump.GetThreadList();
ASSERT_TRUE(thread_list);
ASSERT_EQ((unsigned int)1, thread_list->thread_count());
MinidumpThread* main_thread = thread_list->GetThreadAtIndex(0);
ASSERT_TRUE(main_thread);
MinidumpContext* context = main_thread->GetContext();
ASSERT_TRUE(context);
EXPECT_EQ(kNativeContext, context->GetContextCPU());
MinidumpModuleList* module_list = minidump.GetModuleList();
ASSERT_TRUE(module_list);
const MinidumpModule* main_module = module_list->GetMainModule();
ASSERT_TRUE(main_module);
EXPECT_EQ(GetExecutablePath(), main_module->code_file());
}
#if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6) && \
(defined(__x86_64__) || defined(__i386__))
// Test that crashing a child process of a different architecture
// produces a valid minidump.
TEST_F(CrashGenerationServerTest, testChildProcessCrashCrossArchitecture) {
CrashGenerationServer server(mach_port_name,
dumpCallback, // dump callback
this, // dump context
NULL, // exit callback
NULL, // exit context
true, // generate dumps
temp_dir.path); // dump path
ASSERT_TRUE(server.Start());
// Spawn a child process
string helper_path = GetHelperPath();
const char* argv[] = {
helper_path.c_str(),
"crash",
mach_port_name,
NULL
};
pid_t pid = spawn_child_process(argv);
ASSERT_NE(-1, pid);
int ret;
ASSERT_EQ(pid, waitpid(pid, &ret, 0));
EXPECT_FALSE(WIFEXITED(ret));
EXPECT_TRUE(server.Stop());
// check that minidump was written
ASSERT_FALSE(last_dump_name.empty());
struct stat st;
EXPECT_EQ(0, stat(last_dump_name.c_str(), &st));
EXPECT_LT(0, st.st_size);
const MDCPUArchitecture kExpectedArchitecture =
#if defined(__x86_64__)
MD_CPU_ARCHITECTURE_X86
#elif defined(__i386__)
MD_CPU_ARCHITECTURE_AMD64
#endif
;
const u_int32_t kExpectedContext =
#if defined(__i386__)
MD_CONTEXT_AMD64
#elif defined(__x86_64__)
MD_CONTEXT_X86
#endif
;
// Read the minidump, sanity check some data.
Minidump minidump(last_dump_name.c_str());
ASSERT_TRUE(minidump.Read());
MinidumpSystemInfo* system_info = minidump.GetSystemInfo();
ASSERT_TRUE(system_info);
const MDRawSystemInfo* raw_info = system_info->system_info();
ASSERT_TRUE(raw_info);
EXPECT_EQ(kExpectedArchitecture, raw_info->processor_architecture);
MinidumpThreadList* thread_list = minidump.GetThreadList();
ASSERT_TRUE(thread_list);
ASSERT_EQ((unsigned int)1, thread_list->thread_count());
MinidumpThread* main_thread = thread_list->GetThreadAtIndex(0);
ASSERT_TRUE(main_thread);
MinidumpContext* context = main_thread->GetContext();
ASSERT_TRUE(context);
EXPECT_EQ(kExpectedContext, context->GetContextCPU());
MinidumpModuleList* module_list = minidump.GetModuleList();
ASSERT_TRUE(module_list);
const MinidumpModule* main_module = module_list->GetMainModule();
ASSERT_TRUE(main_module);
EXPECT_EQ(helper_path, main_module->code_file());
}
#endif
} // namespace

View File

@@ -0,0 +1,698 @@
// Copyright (c) 2010, 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.
// exception_handler_test.cc: Unit tests for google_breakpad::ExceptionHandler
#include <pthread.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include "breakpad_googletest_includes.h"
#include "client/mac/handler/exception_handler.h"
#include "client/mac/tests/auto_tempdir.h"
#include "common/mac/MachIPC.h"
#include "google_breakpad/processor/minidump.h"
namespace google_breakpad {
// This acts as the log sink for INFO logging from the processor
// logging code. The logging output confuses XCode and makes it think
// there are unit test failures. testlogging.h handles the overriding.
std::ostringstream info_log;
}
namespace {
using std::string;
using google_breakpad::AutoTempDir;
using google_breakpad::ExceptionHandler;
using google_breakpad::MachPortSender;
using google_breakpad::MachReceiveMessage;
using google_breakpad::MachSendMessage;
using google_breakpad::Minidump;
using google_breakpad::MinidumpContext;
using google_breakpad::MinidumpException;
using google_breakpad::MinidumpMemoryList;
using google_breakpad::MinidumpMemoryRegion;
using google_breakpad::ReceivePort;
using testing::Test;
class ExceptionHandlerTest : public Test {
public:
AutoTempDir tempDir;
string lastDumpName;
};
static void Crasher() {
int *a = (int*)0x42;
fprintf(stdout, "Going to crash...\n");
fprintf(stdout, "A = %d", *a);
}
static void SoonToCrash() {
Crasher();
}
static bool MDCallback(const char *dump_dir, const char *file_name,
void *context, bool success) {
string path(dump_dir);
path.append("/");
path.append(file_name);
path.append(".dmp");
int fd = *reinterpret_cast<int*>(context);
(void)write(fd, path.c_str(), path.length() + 1);
close(fd);
exit(0);
// not reached
return true;
}
TEST_F(ExceptionHandlerTest, InProcess) {
// Give the child process a pipe to report back on.
int fds[2];
ASSERT_EQ(0, pipe(fds));
// Fork off a child process so it can crash.
pid_t pid = fork();
if (pid == 0) {
// In the child process.
close(fds[0]);
ExceptionHandler eh(tempDir.path, NULL, MDCallback, &fds[1], true, NULL);
// crash
SoonToCrash();
// not reached
exit(1);
}
// In the parent process.
ASSERT_NE(-1, pid);
// Wait for the background process to return the minidump file.
close(fds[1]);
char minidump_file[PATH_MAX];
ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file));
ASSERT_NE(0, nbytes);
// Ensure that minidump file exists and is > 0 bytes.
struct stat st;
ASSERT_EQ(0, stat(minidump_file, &st));
ASSERT_LT(0, st.st_size);
// Child process should have exited with a zero status.
int ret;
ASSERT_EQ(pid, waitpid(pid, &ret, 0));
EXPECT_NE(0, WIFEXITED(ret));
EXPECT_EQ(0, WEXITSTATUS(ret));
}
static bool DumpNameMDCallback(const char *dump_dir, const char *file_name,
void *context, bool success) {
ExceptionHandlerTest *self = reinterpret_cast<ExceptionHandlerTest*>(context);
if (dump_dir && file_name) {
self->lastDumpName = dump_dir;
self->lastDumpName += "/";
self->lastDumpName += file_name;
self->lastDumpName += ".dmp";
}
return true;
}
TEST_F(ExceptionHandlerTest, WriteMinidump) {
ExceptionHandler eh(tempDir.path, NULL, DumpNameMDCallback, this, true, NULL);
ASSERT_TRUE(eh.WriteMinidump());
// Ensure that minidump file exists and is > 0 bytes.
ASSERT_FALSE(lastDumpName.empty());
struct stat st;
ASSERT_EQ(0, stat(lastDumpName.c_str(), &st));
ASSERT_LT(0, st.st_size);
// The minidump should not contain an exception stream.
Minidump minidump(lastDumpName);
ASSERT_TRUE(minidump.Read());
MinidumpException* exception = minidump.GetException();
EXPECT_FALSE(exception);
}
TEST_F(ExceptionHandlerTest, WriteMinidumpWithException) {
ExceptionHandler eh(tempDir.path, NULL, DumpNameMDCallback, this, true, NULL);
ASSERT_TRUE(eh.WriteMinidump(true));
// Ensure that minidump file exists and is > 0 bytes.
ASSERT_FALSE(lastDumpName.empty());
struct stat st;
ASSERT_EQ(0, stat(lastDumpName.c_str(), &st));
ASSERT_LT(0, st.st_size);
// The minidump should contain an exception stream.
Minidump minidump(lastDumpName);
ASSERT_TRUE(minidump.Read());
MinidumpException* exception = minidump.GetException();
ASSERT_TRUE(exception);
const MDRawExceptionStream* raw_exception = exception->exception();
ASSERT_TRUE(raw_exception);
EXPECT_EQ(MD_EXCEPTION_MAC_BREAKPOINT,
raw_exception->exception_record.exception_code);
}
TEST_F(ExceptionHandlerTest, DumpChildProcess) {
const int kTimeoutMs = 2000;
// Create a mach port to receive the child task on.
char machPortName[128];
sprintf(machPortName, "ExceptionHandlerTest.%d", getpid());
ReceivePort parent_recv_port(machPortName);
// Give the child process a pipe to block on.
int fds[2];
ASSERT_EQ(0, pipe(fds));
// Fork off a child process to dump.
pid_t pid = fork();
if (pid == 0) {
// In the child process
close(fds[1]);
// Send parent process the task and thread ports.
MachSendMessage child_message(0);
child_message.AddDescriptor(mach_task_self());
child_message.AddDescriptor(mach_thread_self());
MachPortSender child_sender(machPortName);
if (child_sender.SendMessage(child_message, kTimeoutMs) != KERN_SUCCESS)
exit(1);
// Wait for the parent process.
uint8_t data;
read(fds[0], &data, 1);
exit(0);
}
// In the parent process.
ASSERT_NE(-1, pid);
close(fds[0]);
// Read the child's task and thread ports.
MachReceiveMessage child_message;
ASSERT_EQ(KERN_SUCCESS,
parent_recv_port.WaitForMessage(&child_message, kTimeoutMs));
mach_port_t child_task = child_message.GetTranslatedPort(0);
mach_port_t child_thread = child_message.GetTranslatedPort(1);
ASSERT_NE((mach_port_t)MACH_PORT_NULL, child_task);
ASSERT_NE((mach_port_t)MACH_PORT_NULL, child_thread);
// Write a minidump of the child process.
bool result = ExceptionHandler::WriteMinidumpForChild(child_task,
child_thread,
tempDir.path,
DumpNameMDCallback,
this);
ASSERT_EQ(true, result);
// Ensure that minidump file exists and is > 0 bytes.
ASSERT_FALSE(lastDumpName.empty());
struct stat st;
ASSERT_EQ(0, stat(lastDumpName.c_str(), &st));
ASSERT_LT(0, st.st_size);
// Unblock child process
uint8_t data = 1;
(void)write(fds[1], &data, 1);
// Child process should have exited with a zero status.
int ret;
ASSERT_EQ(pid, waitpid(pid, &ret, 0));
EXPECT_NE(0, WIFEXITED(ret));
EXPECT_EQ(0, WEXITSTATUS(ret));
}
// Test that memory around the instruction pointer is written
// to the dump as a MinidumpMemoryRegion.
TEST_F(ExceptionHandlerTest, InstructionPointerMemory) {
// Give the child process a pipe to report back on.
int fds[2];
ASSERT_EQ(0, pipe(fds));
// These are defined here so the parent can use them to check the
// data from the minidump afterwards.
const u_int32_t kMemorySize = 256; // bytes
const int kOffset = kMemorySize / 2;
// This crashes with SIGILL on x86/x86-64/arm.
const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
pid_t pid = fork();
if (pid == 0) {
close(fds[0]);
ExceptionHandler eh(tempDir.path, NULL, MDCallback, &fds[1], true, NULL);
// Get some executable memory.
char* memory =
reinterpret_cast<char*>(mmap(NULL,
kMemorySize,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANON,
-1,
0));
if (!memory)
exit(0);
// Write some instructions that will crash. Put them in the middle
// of the block of memory, because the minidump should contain 128
// bytes on either side of the instruction pointer.
memcpy(memory + kOffset, instructions, sizeof(instructions));
// Now execute the instructions, which should crash.
typedef void (*void_function)(void);
void_function memory_function =
reinterpret_cast<void_function>(memory + kOffset);
memory_function();
// not reached
exit(1);
}
// In the parent process.
ASSERT_NE(-1, pid);
close(fds[1]);
// Wait for the background process to return the minidump file.
close(fds[1]);
char minidump_file[PATH_MAX];
ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file));
ASSERT_NE(0, nbytes);
// Ensure that minidump file exists and is > 0 bytes.
struct stat st;
ASSERT_EQ(0, stat(minidump_file, &st));
ASSERT_LT(0, st.st_size);
// Child process should have exited with a zero status.
int ret;
ASSERT_EQ(pid, waitpid(pid, &ret, 0));
EXPECT_NE(0, WIFEXITED(ret));
EXPECT_EQ(0, WEXITSTATUS(ret));
// Read the minidump. Locate the exception record and the
// memory list, and then ensure that there is a memory region
// in the memory list that covers the instruction pointer from
// the exception record.
Minidump minidump(minidump_file);
ASSERT_TRUE(minidump.Read());
MinidumpException* exception = minidump.GetException();
MinidumpMemoryList* memory_list = minidump.GetMemoryList();
ASSERT_TRUE(exception);
ASSERT_TRUE(memory_list);
ASSERT_NE((unsigned int)0, memory_list->region_count());
MinidumpContext* context = exception->GetContext();
ASSERT_TRUE(context);
u_int64_t instruction_pointer;
switch (context->GetContextCPU()) {
case MD_CONTEXT_X86:
instruction_pointer = context->GetContextX86()->eip;
break;
case MD_CONTEXT_AMD64:
instruction_pointer = context->GetContextAMD64()->rip;
break;
case MD_CONTEXT_ARM:
instruction_pointer = context->GetContextARM()->iregs[15];
break;
default:
FAIL() << "Unknown context CPU: " << context->GetContextCPU();
break;
}
MinidumpMemoryRegion* region =
memory_list->GetMemoryRegionForAddress(instruction_pointer);
EXPECT_TRUE(region);
EXPECT_EQ(kMemorySize, region->GetSize());
const u_int8_t* bytes = region->GetMemory();
ASSERT_TRUE(bytes);
u_int8_t prefix_bytes[kOffset];
u_int8_t suffix_bytes[kMemorySize - kOffset - sizeof(instructions)];
memset(prefix_bytes, 0, sizeof(prefix_bytes));
memset(suffix_bytes, 0, sizeof(suffix_bytes));
EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0);
EXPECT_TRUE(memcmp(bytes + kOffset, instructions, sizeof(instructions)) == 0);
EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(instructions),
suffix_bytes, sizeof(suffix_bytes)) == 0);
}
// Test that the memory region around the instruction pointer is
// bounded correctly on the low end.
TEST_F(ExceptionHandlerTest, InstructionPointerMemoryMinBound) {
// Give the child process a pipe to report back on.
int fds[2];
ASSERT_EQ(0, pipe(fds));
// These are defined here so the parent can use them to check the
// data from the minidump afterwards.
const u_int32_t kMemorySize = 256; // bytes
const int kOffset = 0;
// This crashes with SIGILL on x86/x86-64/arm.
const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
pid_t pid = fork();
if (pid == 0) {
close(fds[0]);
ExceptionHandler eh(tempDir.path, NULL, MDCallback, &fds[1], true, NULL);
// Get some executable memory.
char* memory =
reinterpret_cast<char*>(mmap(NULL,
kMemorySize,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANON,
-1,
0));
if (!memory)
exit(0);
// Write some instructions that will crash. Put them at the start
// of the block of memory, to ensure that the memory bounding
// works properly.
memcpy(memory + kOffset, instructions, sizeof(instructions));
// Now execute the instructions, which should crash.
typedef void (*void_function)(void);
void_function memory_function =
reinterpret_cast<void_function>(memory + kOffset);
memory_function();
// not reached
exit(1);
}
// In the parent process.
ASSERT_NE(-1, pid);
close(fds[1]);
// Wait for the background process to return the minidump file.
close(fds[1]);
char minidump_file[PATH_MAX];
ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file));
ASSERT_NE(0, nbytes);
// Ensure that minidump file exists and is > 0 bytes.
struct stat st;
ASSERT_EQ(0, stat(minidump_file, &st));
ASSERT_LT(0, st.st_size);
// Child process should have exited with a zero status.
int ret;
ASSERT_EQ(pid, waitpid(pid, &ret, 0));
EXPECT_NE(0, WIFEXITED(ret));
EXPECT_EQ(0, WEXITSTATUS(ret));
// Read the minidump. Locate the exception record and the
// memory list, and then ensure that there is a memory region
// in the memory list that covers the instruction pointer from
// the exception record.
Minidump minidump(minidump_file);
ASSERT_TRUE(minidump.Read());
MinidumpException* exception = minidump.GetException();
MinidumpMemoryList* memory_list = minidump.GetMemoryList();
ASSERT_TRUE(exception);
ASSERT_TRUE(memory_list);
ASSERT_NE((unsigned int)0, memory_list->region_count());
MinidumpContext* context = exception->GetContext();
ASSERT_TRUE(context);
u_int64_t instruction_pointer;
switch (context->GetContextCPU()) {
case MD_CONTEXT_X86:
instruction_pointer = context->GetContextX86()->eip;
break;
case MD_CONTEXT_AMD64:
instruction_pointer = context->GetContextAMD64()->rip;
break;
case MD_CONTEXT_ARM:
instruction_pointer = context->GetContextARM()->iregs[15];
break;
default:
FAIL() << "Unknown context CPU: " << context->GetContextCPU();
break;
}
MinidumpMemoryRegion* region =
memory_list->GetMemoryRegionForAddress(instruction_pointer);
EXPECT_TRUE(region);
EXPECT_EQ(kMemorySize / 2, region->GetSize());
const u_int8_t* bytes = region->GetMemory();
ASSERT_TRUE(bytes);
u_int8_t suffix_bytes[kMemorySize / 2 - sizeof(instructions)];
memset(suffix_bytes, 0, sizeof(suffix_bytes));
EXPECT_TRUE(memcmp(bytes + kOffset, instructions, sizeof(instructions)) == 0);
EXPECT_TRUE(memcmp(bytes + kOffset + sizeof(instructions),
suffix_bytes, sizeof(suffix_bytes)) == 0);
}
// Test that the memory region around the instruction pointer is
// bounded correctly on the high end.
TEST_F(ExceptionHandlerTest, InstructionPointerMemoryMaxBound) {
// Give the child process a pipe to report back on.
int fds[2];
ASSERT_EQ(0, pipe(fds));
// These are defined here so the parent can use them to check the
// data from the minidump afterwards.
// Use 4k here because the OS will hand out a single page even
// if a smaller size is requested, and this test wants to
// test the upper bound of the memory range.
const u_int32_t kMemorySize = 4096; // bytes
// This crashes with SIGILL on x86/x86-64/arm.
const unsigned char instructions[] = { 0xff, 0xff, 0xff, 0xff };
const int kOffset = kMemorySize - sizeof(instructions);
pid_t pid = fork();
if (pid == 0) {
close(fds[0]);
ExceptionHandler eh(tempDir.path, NULL, MDCallback, &fds[1], true, NULL);
// Get some executable memory.
char* memory =
reinterpret_cast<char*>(mmap(NULL,
kMemorySize,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANON,
-1,
0));
if (!memory)
exit(0);
// Write some instructions that will crash. Put them at the start
// of the block of memory, to ensure that the memory bounding
// works properly.
memcpy(memory + kOffset, instructions, sizeof(instructions));
// Now execute the instructions, which should crash.
typedef void (*void_function)(void);
void_function memory_function =
reinterpret_cast<void_function>(memory + kOffset);
memory_function();
// not reached
exit(1);
}
// In the parent process.
ASSERT_NE(-1, pid);
close(fds[1]);
// Wait for the background process to return the minidump file.
close(fds[1]);
char minidump_file[PATH_MAX];
ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file));
ASSERT_NE(0, nbytes);
// Ensure that minidump file exists and is > 0 bytes.
struct stat st;
ASSERT_EQ(0, stat(minidump_file, &st));
ASSERT_LT(0, st.st_size);
// Child process should have exited with a zero status.
int ret;
ASSERT_EQ(pid, waitpid(pid, &ret, 0));
EXPECT_NE(0, WIFEXITED(ret));
EXPECT_EQ(0, WEXITSTATUS(ret));
// Read the minidump. Locate the exception record and the
// memory list, and then ensure that there is a memory region
// in the memory list that covers the instruction pointer from
// the exception record.
Minidump minidump(minidump_file);
ASSERT_TRUE(minidump.Read());
MinidumpException* exception = minidump.GetException();
MinidumpMemoryList* memory_list = minidump.GetMemoryList();
ASSERT_TRUE(exception);
ASSERT_TRUE(memory_list);
ASSERT_NE((unsigned int)0, memory_list->region_count());
MinidumpContext* context = exception->GetContext();
ASSERT_TRUE(context);
u_int64_t instruction_pointer;
switch (context->GetContextCPU()) {
case MD_CONTEXT_X86:
instruction_pointer = context->GetContextX86()->eip;
break;
case MD_CONTEXT_AMD64:
instruction_pointer = context->GetContextAMD64()->rip;
break;
case MD_CONTEXT_ARM:
instruction_pointer = context->GetContextARM()->iregs[15];
break;
default:
FAIL() << "Unknown context CPU: " << context->GetContextCPU();
break;
}
MinidumpMemoryRegion* region =
memory_list->GetMemoryRegionForAddress(instruction_pointer);
EXPECT_TRUE(region);
const size_t kPrefixSize = 128; // bytes
EXPECT_EQ(kPrefixSize + sizeof(instructions), region->GetSize());
const u_int8_t* bytes = region->GetMemory();
ASSERT_TRUE(bytes);
u_int8_t prefix_bytes[kPrefixSize];
memset(prefix_bytes, 0, sizeof(prefix_bytes));
EXPECT_TRUE(memcmp(bytes, prefix_bytes, sizeof(prefix_bytes)) == 0);
EXPECT_TRUE(memcmp(bytes + kPrefixSize,
instructions, sizeof(instructions)) == 0);
}
// Ensure that an extra memory block doesn't get added when the
// instruction pointer is not in mapped memory.
TEST_F(ExceptionHandlerTest, InstructionPointerMemoryNullPointer) {
// Give the child process a pipe to report back on.
int fds[2];
ASSERT_EQ(0, pipe(fds));
pid_t pid = fork();
if (pid == 0) {
close(fds[0]);
ExceptionHandler eh(tempDir.path, NULL, MDCallback, &fds[1], true, NULL);
// Try calling a NULL pointer.
typedef void (*void_function)(void);
void_function memory_function =
reinterpret_cast<void_function>(NULL);
memory_function();
// not reached
exit(1);
}
// In the parent process.
ASSERT_NE(-1, pid);
close(fds[1]);
// Wait for the background process to return the minidump file.
close(fds[1]);
char minidump_file[PATH_MAX];
ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file));
ASSERT_NE(0, nbytes);
// Ensure that minidump file exists and is > 0 bytes.
struct stat st;
ASSERT_EQ(0, stat(minidump_file, &st));
ASSERT_LT(0, st.st_size);
// Child process should have exited with a zero status.
int ret;
ASSERT_EQ(pid, waitpid(pid, &ret, 0));
EXPECT_NE(0, WIFEXITED(ret));
EXPECT_EQ(0, WEXITSTATUS(ret));
// Read the minidump. Locate the exception record and the
// memory list, and then ensure that there is only one memory region
// in the memory list (the thread memory from the single thread).
Minidump minidump(minidump_file);
ASSERT_TRUE(minidump.Read());
MinidumpException* exception = minidump.GetException();
MinidumpMemoryList* memory_list = minidump.GetMemoryList();
ASSERT_TRUE(exception);
ASSERT_TRUE(memory_list);
ASSERT_EQ((unsigned int)1, memory_list->region_count());
}
static void *Junk(void *) {
sleep(1000000);
return NULL;
}
// Test that the memory list gets written correctly when multiple
// threads are running.
TEST_F(ExceptionHandlerTest, MemoryListMultipleThreads) {
// Give the child process a pipe to report back on.
int fds[2];
ASSERT_EQ(0, pipe(fds));
pid_t pid = fork();
if (pid == 0) {
close(fds[0]);
ExceptionHandler eh(tempDir.path, NULL, MDCallback, &fds[1], true, NULL);
// Run an extra thread so >2 memory regions will be written.
pthread_t junk_thread;
if (pthread_create(&junk_thread, NULL, Junk, NULL) == 0)
pthread_detach(junk_thread);
// Just crash.
Crasher();
// not reached
exit(1);
}
// In the parent process.
ASSERT_NE(-1, pid);
close(fds[1]);
// Wait for the background process to return the minidump file.
close(fds[1]);
char minidump_file[PATH_MAX];
ssize_t nbytes = read(fds[0], minidump_file, sizeof(minidump_file));
ASSERT_NE(0, nbytes);
// Ensure that minidump file exists and is > 0 bytes.
struct stat st;
ASSERT_EQ(0, stat(minidump_file, &st));
ASSERT_LT(0, st.st_size);
// Child process should have exited with a zero status.
int ret;
ASSERT_EQ(pid, waitpid(pid, &ret, 0));
EXPECT_NE(0, WIFEXITED(ret));
EXPECT_EQ(0, WEXITSTATUS(ret));
// Read the minidump, and verify that the memory list can be read.
Minidump minidump(minidump_file);
ASSERT_TRUE(minidump.Read());
MinidumpMemoryList* memory_list = minidump.GetMemoryList();
ASSERT_TRUE(memory_list);
// Verify that there are three memory regions:
// one per thread, and one for the instruction pointer memory.
ASSERT_EQ((unsigned int)3, memory_list->region_count());
}
}

View File

@@ -0,0 +1,319 @@
// Copyright (c) 2010, 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.
// minidump_generator_test.cc: Unit tests for google_breakpad::MinidumpGenerator
#include <AvailabilityMacros.h>
#ifndef MAC_OS_X_VERSION_10_6
#define MAC_OS_X_VERSION_10_6 1060
#endif
#include <sys/stat.h>
#include <unistd.h>
#include <string>
#include <vector>
#include "breakpad_googletest_includes.h"
#include "client/mac/handler/minidump_generator.h"
#include "client/mac/tests/auto_tempdir.h"
#include "client/mac/tests/spawn_child_process.h"
#include "common/mac/MachIPC.h"
#include "google_breakpad/processor/minidump.h"
namespace google_breakpad {
// This acts as the log sink for INFO logging from the processor
// logging code. The logging output confuses XCode and makes it think
// there are unit test failures. testlogging.h handles the overriding.
std::ostringstream info_log;
}
namespace {
using std::string;
using std::vector;
using google_breakpad::AutoTempDir;
using google_breakpad::MinidumpGenerator;
using google_breakpad::MachPortSender;
using google_breakpad::MachReceiveMessage;
using google_breakpad::MachSendMessage;
using google_breakpad::Minidump;
using google_breakpad::MinidumpContext;
using google_breakpad::MinidumpException;
using google_breakpad::MinidumpModule;
using google_breakpad::MinidumpModuleList;
using google_breakpad::MinidumpSystemInfo;
using google_breakpad::MinidumpThread;
using google_breakpad::MinidumpThreadList;
using google_breakpad::ReceivePort;
using testing::Test;
using namespace google_breakpad_test;
class MinidumpGeneratorTest : public Test {
public:
AutoTempDir tempDir;
};
static void *Junk(void* data) {
bool* wait = reinterpret_cast<bool*>(data);
while (!*wait) {
usleep(10000);
}
return NULL;
}
TEST_F(MinidumpGeneratorTest, InProcess) {
MinidumpGenerator generator;
string dump_filename = MinidumpGenerator::UniqueNameInDirectory(tempDir.path,
NULL);
// Run an extra thread since MinidumpGenerator assumes there
// are 2 or more threads.
pthread_t junk_thread;
bool quit = false;
ASSERT_EQ(0, pthread_create(&junk_thread, NULL, Junk, &quit));
ASSERT_TRUE(generator.Write(dump_filename.c_str()));
// Ensure that minidump file exists and is > 0 bytes.
struct stat st;
ASSERT_EQ(0, stat(dump_filename.c_str(), &st));
ASSERT_LT(0, st.st_size);
// join the background thread
quit = true;
pthread_join(junk_thread, NULL);
// Read the minidump, sanity check some data.
Minidump minidump(dump_filename.c_str());
ASSERT_TRUE(minidump.Read());
MinidumpSystemInfo* system_info = minidump.GetSystemInfo();
ASSERT_TRUE(system_info);
const MDRawSystemInfo* raw_info = system_info->system_info();
ASSERT_TRUE(raw_info);
EXPECT_EQ(kNativeArchitecture, raw_info->processor_architecture);
MinidumpThreadList* thread_list = minidump.GetThreadList();
ASSERT_TRUE(thread_list);
ASSERT_EQ((unsigned int)1, thread_list->thread_count());
MinidumpThread* main_thread = thread_list->GetThreadAtIndex(0);
ASSERT_TRUE(main_thread);
MinidumpContext* context = main_thread->GetContext();
ASSERT_TRUE(context);
EXPECT_EQ(kNativeContext, context->GetContextCPU());
MinidumpModuleList* module_list = minidump.GetModuleList();
ASSERT_TRUE(module_list);
const MinidumpModule* main_module = module_list->GetMainModule();
ASSERT_TRUE(main_module);
EXPECT_EQ(GetExecutablePath(), main_module->code_file());
}
TEST_F(MinidumpGeneratorTest, OutOfProcess) {
const int kTimeoutMs = 2000;
// Create a mach port to receive the child task on.
char machPortName[128];
sprintf(machPortName, "MinidumpGeneratorTest.OutOfProcess.%d", getpid());
ReceivePort parent_recv_port(machPortName);
// Give the child process a pipe to block on.
int fds[2];
ASSERT_EQ(0, pipe(fds));
// Fork off a child process to dump.
pid_t pid = fork();
if (pid == 0) {
// In the child process
close(fds[1]);
// Send parent process the task port.
MachSendMessage child_message(0);
child_message.AddDescriptor(mach_task_self());
MachPortSender child_sender(machPortName);
if (child_sender.SendMessage(child_message, kTimeoutMs) != KERN_SUCCESS) {
fprintf(stderr, "Error sending message from child process!\n");
exit(1);
}
// Wait for the parent process.
uint8_t data;
read(fds[0], &data, 1);
exit(0);
}
// In the parent process.
ASSERT_NE(-1, pid);
close(fds[0]);
// Read the child's task port.
MachReceiveMessage child_message;
ASSERT_EQ(KERN_SUCCESS,
parent_recv_port.WaitForMessage(&child_message, kTimeoutMs));
mach_port_t child_task = child_message.GetTranslatedPort(0);
ASSERT_NE((mach_port_t)MACH_PORT_NULL, child_task);
// Write a minidump of the child process.
MinidumpGenerator generator(child_task, MACH_PORT_NULL);
string dump_filename = MinidumpGenerator::UniqueNameInDirectory(tempDir.path,
NULL);
ASSERT_TRUE(generator.Write(dump_filename.c_str()));
// Ensure that minidump file exists and is > 0 bytes.
struct stat st;
ASSERT_EQ(0, stat(dump_filename.c_str(), &st));
ASSERT_LT(0, st.st_size);
// Unblock child process
uint8_t data = 1;
(void)write(fds[1], &data, 1);
// Child process should have exited with a zero status.
int ret;
ASSERT_EQ(pid, waitpid(pid, &ret, 0));
EXPECT_NE(0, WIFEXITED(ret));
EXPECT_EQ(0, WEXITSTATUS(ret));
// Read the minidump, sanity check some data.
Minidump minidump(dump_filename.c_str());
ASSERT_TRUE(minidump.Read());
MinidumpSystemInfo* system_info = minidump.GetSystemInfo();
ASSERT_TRUE(system_info);
const MDRawSystemInfo* raw_info = system_info->system_info();
ASSERT_TRUE(raw_info);
EXPECT_EQ(kNativeArchitecture, raw_info->processor_architecture);
MinidumpThreadList* thread_list = minidump.GetThreadList();
ASSERT_TRUE(thread_list);
ASSERT_EQ((unsigned int)1, thread_list->thread_count());
MinidumpThread* main_thread = thread_list->GetThreadAtIndex(0);
ASSERT_TRUE(main_thread);
MinidumpContext* context = main_thread->GetContext();
ASSERT_TRUE(context);
EXPECT_EQ(kNativeContext, context->GetContextCPU());
MinidumpModuleList* module_list = minidump.GetModuleList();
ASSERT_TRUE(module_list);
const MinidumpModule* main_module = module_list->GetMainModule();
ASSERT_TRUE(main_module);
EXPECT_EQ(GetExecutablePath(), main_module->code_file());
}
// This test fails on 10.5, but I don't have easy access to a 10.5 machine,
// so it's simpler to just limit it to 10.6 for now.
#if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6) && \
(defined(__x86_64__) || defined(__i386__))
TEST_F(MinidumpGeneratorTest, CrossArchitectureDump) {
const int kTimeoutMs = 5000;
// Create a mach port to receive the child task on.
char machPortName[128];
sprintf(machPortName,
"MinidumpGeneratorTest.CrossArchitectureDump.%d", getpid());
ReceivePort parent_recv_port(machPortName);
// Spawn a child process to dump.
string helper_path = GetHelperPath();
const char* argv[] = {
helper_path.c_str(),
machPortName,
NULL
};
pid_t pid = spawn_child_process(argv);
ASSERT_NE(-1, pid);
// Read the child's task port.
MachReceiveMessage child_message;
ASSERT_EQ(KERN_SUCCESS,
parent_recv_port.WaitForMessage(&child_message, kTimeoutMs));
mach_port_t child_task = child_message.GetTranslatedPort(0);
ASSERT_NE((mach_port_t)MACH_PORT_NULL, child_task);
// Write a minidump of the child process.
MinidumpGenerator generator(child_task, MACH_PORT_NULL);
string dump_filename = MinidumpGenerator::UniqueNameInDirectory(tempDir.path,
NULL);
ASSERT_TRUE(generator.Write(dump_filename.c_str()));
// Ensure that minidump file exists and is > 0 bytes.
struct stat st;
ASSERT_EQ(0, stat(dump_filename.c_str(), &st));
ASSERT_LT(0, st.st_size);
// Kill child process.
kill(pid, SIGKILL);
int ret;
ASSERT_EQ(pid, waitpid(pid, &ret, 0));
const MDCPUArchitecture kExpectedArchitecture =
#if defined(__x86_64__)
MD_CPU_ARCHITECTURE_X86
#elif defined(__i386__)
MD_CPU_ARCHITECTURE_AMD64
#endif
;
const u_int32_t kExpectedContext =
#if defined(__i386__)
MD_CONTEXT_AMD64
#elif defined(__x86_64__)
MD_CONTEXT_X86
#endif
;
// Read the minidump, sanity check some data.
Minidump minidump(dump_filename.c_str());
ASSERT_TRUE(minidump.Read());
MinidumpSystemInfo* system_info = minidump.GetSystemInfo();
ASSERT_TRUE(system_info);
const MDRawSystemInfo* raw_info = system_info->system_info();
ASSERT_TRUE(raw_info);
EXPECT_EQ(kExpectedArchitecture, raw_info->processor_architecture);
MinidumpThreadList* thread_list = minidump.GetThreadList();
ASSERT_TRUE(thread_list);
ASSERT_EQ((unsigned int)1, thread_list->thread_count());
MinidumpThread* main_thread = thread_list->GetThreadAtIndex(0);
ASSERT_TRUE(main_thread);
MinidumpContext* context = main_thread->GetContext();
ASSERT_TRUE(context);
EXPECT_EQ(kExpectedContext, context->GetContextCPU());
MinidumpModuleList* module_list = minidump.GetModuleList();
ASSERT_TRUE(module_list);
const MinidumpModule* main_module = module_list->GetMainModule();
ASSERT_TRUE(main_module);
EXPECT_EQ(helper_path, main_module->code_file());
}
#endif // 10.6 && (x86-64 || i386)
}

View File

@@ -0,0 +1,74 @@
// Copyright (c) 2010, 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.
// minidump_generator_test_helper.cc: A helper program that
// minidump_generator_test.cc can launch to test certain things
// that require a separate executable.
#include <unistd.h>
#include "client/mac/handler/exception_handler.h"
#include "common/mac/MachIPC.h"
using google_breakpad::MachPortSender;
using google_breakpad::MachReceiveMessage;
using google_breakpad::MachSendMessage;
using google_breakpad::ReceivePort;
int main(int argc, char** argv) {
if (argc < 2)
return 1;
if (strcmp(argv[1], "crash") != 0) {
const int kTimeoutMs = 2000;
// Send parent process the task and thread ports.
MachSendMessage child_message(0);
child_message.AddDescriptor(mach_task_self());
child_message.AddDescriptor(mach_thread_self());
MachPortSender child_sender(argv[1]);
if (child_sender.SendMessage(child_message, kTimeoutMs) != KERN_SUCCESS) {
fprintf(stderr, "Error sending message from child process!\n");
exit(1);
}
// Loop forever.
while (true) {
sleep(100);
}
} else if (argc == 3 && strcmp(argv[1], "crash") == 0) {
// Instantiate an OOP exception handler
google_breakpad::ExceptionHandler eh("", NULL, NULL, NULL, true, argv[2]);
// and crash.
int *a = (int*)0x42;
*a = 1;
}
return 0;
}

View File

@@ -0,0 +1,149 @@
// Copyright (c) 2010, 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 functions for spawning a helper process using a different
// CPU architecture.
#ifndef GOOGLE_BREAKPAD_CLIENT_MAC_TESTS_SPAWN_CHILD_PROCESS
#define GOOGLE_BREAKPAD_CLIENT_MAC_TESTS_SPAWN_CHILD_PROCESS
#include <AvailabilityMacros.h>
#ifndef MAC_OS_X_VERSION_10_6
#define MAC_OS_X_VERSION_10_6 1060
#endif
#include <crt_externs.h>
#include <mach-o/dyld.h>
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6
#include <spawn.h>
#endif
#include <string>
#include <vector>
#include "google_breakpad/common/minidump_format.h"
namespace google_breakpad_test {
using std::string;
using std::vector;
const MDCPUArchitecture kNativeArchitecture =
#if defined(__i386__)
MD_CPU_ARCHITECTURE_X86
#elif defined(__x86_64__)
MD_CPU_ARCHITECTURE_AMD64
#elif defined(__ppc__) || defined(__ppc64__)
MD_CPU_ARCHITECTURE_PPC
#else
#error "This file has not been ported to this CPU architecture."
#endif
;
const u_int32_t kNativeContext =
#if defined(__i386__)
MD_CONTEXT_X86
#elif defined(__x86_64__)
MD_CONTEXT_AMD64
#elif defined(__ppc__) || defined(__ppc64__)
MD_CONTEXT_PPC
#else
#error "This file has not been ported to this CPU architecture."
#endif
;
string GetExecutablePath() {
char self_path[PATH_MAX];
uint32_t size = sizeof(self_path);
if (_NSGetExecutablePath(self_path, &size) != 0)
return "";
return self_path;
}
string GetHelperPath() {
string helper_path(GetExecutablePath());
size_t pos = helper_path.rfind('/');
if (pos == string::npos)
return "";
helper_path.erase(pos + 1);
helper_path += "minidump_generator_test_helper";
return helper_path;
}
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6
pid_t spawn_child_process(const char** argv) {
posix_spawnattr_t spawnattr;
if (posix_spawnattr_init(&spawnattr) != 0)
return (pid_t)-1;
cpu_type_t pref_cpu_types[2] = {
#if defined(__x86_64__)
CPU_TYPE_X86,
#elif defined(__i386__)
CPU_TYPE_X86_64,
#endif
CPU_TYPE_ANY
};
// Set spawn attributes.
size_t attr_count = sizeof(pref_cpu_types) / sizeof(pref_cpu_types[0]);
size_t attr_ocount = 0;
if (posix_spawnattr_setbinpref_np(&spawnattr,
attr_count,
pref_cpu_types,
&attr_ocount) != 0 ||
attr_ocount != attr_count) {
posix_spawnattr_destroy(&spawnattr);
return (pid_t)-1;
}
// Create an argv array.
vector<char*> argv_v;
while (*argv) {
argv_v.push_back(strdup(*argv));
argv++;
}
argv_v.push_back(NULL);
pid_t new_pid = 0;
int result = posix_spawnp(&new_pid, argv_v[0], NULL, &spawnattr,
&argv_v[0], *_NSGetEnviron());
posix_spawnattr_destroy(&spawnattr);
for (unsigned i = 0; i < argv_v.size(); i++) {
free(argv_v[i]);
}
return result == 0 ? new_pid : -1;
}
#endif
} // namespace google_breakpad_test
#endif // GOOGLE_BREAKPAD_CLIENT_MAC_TESTS_SPAWN_CHILD_PROCESS

View File

@@ -0,0 +1,9 @@
// This file exists to override the processor logging for unit tests,
// since it confuses XCode into thinking unit tests have failed.
#include <sstream>
namespace google_breakpad {
extern std::ostringstream info_log;
}
#define BPLOG_INFO_STREAM google_breakpad::info_log