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:
217
thirdparty/breakpad/client/mac/tests/BreakpadFramework_Test.mm
vendored
Normal file
217
thirdparty/breakpad/client/mac/tests/BreakpadFramework_Test.mm
vendored
Normal 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
|
40
thirdparty/breakpad/client/mac/tests/SimpleStringDictionaryTest.h
vendored
Normal file
40
thirdparty/breakpad/client/mac/tests/SimpleStringDictionaryTest.h
vendored
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
thirdparty/breakpad/client/mac/tests/SimpleStringDictionaryTest.mm
vendored
Normal file
243
thirdparty/breakpad/client/mac/tests/SimpleStringDictionaryTest.mm
vendored
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
|
72
thirdparty/breakpad/client/mac/tests/auto_tempdir.h
vendored
Normal file
72
thirdparty/breakpad/client/mac/tests/auto_tempdir.h
vendored
Normal 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
|
345
thirdparty/breakpad/client/mac/tests/crash_generation_server_test.cc
vendored
Normal file
345
thirdparty/breakpad/client/mac/tests/crash_generation_server_test.cc
vendored
Normal 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
|
698
thirdparty/breakpad/client/mac/tests/exception_handler_test.cc
vendored
Normal file
698
thirdparty/breakpad/client/mac/tests/exception_handler_test.cc
vendored
Normal 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());
|
||||
}
|
||||
|
||||
}
|
319
thirdparty/breakpad/client/mac/tests/minidump_generator_test.cc
vendored
Normal file
319
thirdparty/breakpad/client/mac/tests/minidump_generator_test.cc
vendored
Normal 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)
|
||||
|
||||
}
|
74
thirdparty/breakpad/client/mac/tests/minidump_generator_test_helper.cc
vendored
Normal file
74
thirdparty/breakpad/client/mac/tests/minidump_generator_test_helper.cc
vendored
Normal 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;
|
||||
}
|
149
thirdparty/breakpad/client/mac/tests/spawn_child_process.h
vendored
Normal file
149
thirdparty/breakpad/client/mac/tests/spawn_child_process.h
vendored
Normal 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
|
9
thirdparty/breakpad/client/mac/tests/testlogging.h
vendored
Normal file
9
thirdparty/breakpad/client/mac/tests/testlogging.h
vendored
Normal 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
|
Reference in New Issue
Block a user