Chromium Code Reviews| Index: util/mach/exc_server_variants_test.cc |
| diff --git a/util/mach/exc_server_variants_test.cc b/util/mach/exc_server_variants_test.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..ca5df597d435ea833bc384488b5ec31907bbb8e0 |
| --- /dev/null |
| +++ b/util/mach/exc_server_variants_test.cc |
| @@ -0,0 +1,1072 @@ |
| +// Copyright 2014 The Crashpad Authors. All rights reserved. |
| +// |
| +// Licensed under the Apache License, Version 2.0 (the "License"); |
| +// you may not use this file except in compliance with the License. |
| +// You may obtain a copy of the License at |
| +// |
| +// http://www.apache.org/licenses/LICENSE-2.0 |
| +// |
| +// Unless required by applicable law or agreed to in writing, software |
| +// distributed under the License is distributed on an "AS IS" BASIS, |
| +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| +// See the License for the specific language governing permissions and |
| +// limitations under the License. |
| + |
| +#include "util/mach/exc_server_variants.h" |
| + |
| +#include <mach/mach.h> |
| +#include <string.h> |
| + |
| +#include "base/basictypes.h" |
| +#include "base/strings/stringprintf.h" |
| +#include "gmock/gmock.h" |
| +#include "gtest/gtest.h" |
| +#include "util/test/mac/mach_errors.h" |
| +#include "util/test/mac/mach_multiprocess.h" |
| + |
| +namespace { |
| + |
| +using namespace crashpad; |
| +using namespace crashpad::test; |
| +using namespace testing; |
| + |
| +// Fake Mach ports. These aren’t used as ports in these tests, they’re just used |
| +// as cookies to make sure that the correct values get passed to the correct |
| +// places. |
| +const mach_port_t kClientRemotePort = 0x01010101; |
| +const mach_port_t kServerLocalPort = 0x02020202; |
| +const mach_port_t kExceptionThreadPort = 0x03030303; |
| +const mach_port_t kExceptionTaskPort = 0x04040404; |
| + |
| +// Other fake exception values. |
| +const exception_type_t kExceptionType = EXC_BAD_ACCESS; |
| + |
| +// Test using an exception code with the high bit set to ensure that it gets |
| +// promoted to the wider mach_exception_data_type_t type as a signed quantity. |
| +const exception_data_type_t kExceptionCodes[] = { |
| + KERN_PROTECTION_FAILURE, |
| + static_cast<exception_data_type_t>(0xfedcba98), |
| +}; |
| + |
| +const exception_data_type_t kMachExceptionCodes[] = { |
| + KERN_PROTECTION_FAILURE, |
| + static_cast<exception_data_type_t>(0xfedcba9876543210), |
| +}; |
| + |
| +const thread_state_flavor_t kThreadStateFlavor = MACHINE_THREAD_STATE; |
| +const mach_msg_type_number_t kThreadStateFlavorCount = |
| + MACHINE_THREAD_STATE_COUNT; |
| + |
| +void InitializeMachMsgPortDescriptor(mach_msg_port_descriptor_t* descriptor, |
| + mach_port_t port) { |
| + descriptor->name = port; |
| + descriptor->disposition = MACH_MSG_TYPE_MOVE_SEND; |
| + descriptor->type = MACH_MSG_PORT_DESCRIPTOR; |
| +} |
| + |
| +// The definitions of the request and reply structures from mach_exc.h aren’t |
| +// available here. They need custom initialization code, and the reply |
| +// structures need verification code too, so duplicate the expected definitions |
| +// of the structures from both exc.h and mach_exc.h here in this file, and |
| +// provide the initialization and verification code as methods in true |
| +// object-oriented fashion. |
| + |
| +struct __attribute__((packed, aligned(4))) ExceptionRaiseRequest { |
| + mach_msg_header_t Head; |
| + mach_msg_body_t msgh_body; |
| + mach_msg_port_descriptor_t thread; |
| + mach_msg_port_descriptor_t task; |
| + NDR_record_t NDR; |
| + exception_type_t exception; |
| + mach_msg_type_number_t codeCnt; |
| + integer_t code[2]; |
| + |
| + void InitializeForTesting() { |
| + memset(this, 0xa5, sizeof(*this)); |
| + Head.msgh_bits = |
| + MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE) | |
| + MACH_MSGH_BITS_COMPLEX; |
| + Head.msgh_size = sizeof(*this); |
| + Head.msgh_remote_port = kClientRemotePort; |
| + Head.msgh_local_port = kServerLocalPort; |
| + Head.msgh_id = 2401; |
| + msgh_body.msgh_descriptor_count = 2; |
| + InitializeMachMsgPortDescriptor(&thread, kExceptionThreadPort); |
| + InitializeMachMsgPortDescriptor(&task, kExceptionTaskPort); |
| + NDR = NDR_record; |
| + exception = kExceptionType; |
| + codeCnt = 2; |
| + code[0] = kExceptionCodes[0]; |
| + code[1] = kExceptionCodes[1]; |
| + } |
| +}; |
| + |
| +struct __attribute__((packed, aligned(4))) ExceptionRaiseReply { |
| + mach_msg_header_t Head; |
| + NDR_record_t NDR; |
| + kern_return_t RetCode; |
| + |
| + void InitializeForTesting() { |
| + memset(this, 0x5a, sizeof(*this)); |
| + RetCode = KERN_FAILURE; |
| + } |
| + |
| + // Verify accepts a |behavior| parameter because the same message format and |
| + // verification function is used for ExceptionRaiseReply and |
| + // MachExceptionRaiseReply. Knowing which behavior is expected allows the |
| + // message ID to be checked. |
| + void Verify(exception_behavior_t behavior) { |
| + EXPECT_EQ(static_cast<mach_msg_bits_t>( |
| + MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0)), |
| + Head.msgh_bits); |
| + EXPECT_EQ(sizeof(*this), Head.msgh_size); |
| + EXPECT_EQ(kClientRemotePort, Head.msgh_remote_port); |
| + EXPECT_EQ(static_cast<mach_port_t>(MACH_PORT_NULL), Head.msgh_local_port); |
| + switch (behavior) { |
| + case EXCEPTION_DEFAULT: |
| + EXPECT_EQ(2501, Head.msgh_id); |
| + break; |
| + case static_cast<exception_behavior_t>(EXCEPTION_DEFAULT | |
| + MACH_EXCEPTION_CODES): |
| + EXPECT_EQ(2505, Head.msgh_id); |
| + break; |
| + default: |
| + ADD_FAILURE() << "behavior " << behavior << ", Head.msgh_id " |
| + << Head.msgh_id; |
| + break; |
| + } |
| + EXPECT_EQ(0, memcmp(&NDR, &NDR_record, sizeof(NDR))); |
| + EXPECT_EQ(KERN_SUCCESS, RetCode); |
| + } |
| +}; |
| + |
| +struct __attribute__((packed, aligned(4))) ExceptionRaiseStateRequest { |
| + mach_msg_header_t Head; |
| + NDR_record_t NDR; |
| + exception_type_t exception; |
| + mach_msg_type_number_t codeCnt; |
| + integer_t code[2]; |
| + int flavor; |
| + mach_msg_type_number_t old_stateCnt; |
| + natural_t old_state[THREAD_STATE_MAX]; |
| + |
| + void InitializeForTesting() { |
| + memset(this, 0xa5, sizeof(*this)); |
| + Head.msgh_bits = |
| + MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE); |
| + Head.msgh_size = sizeof(*this); |
| + Head.msgh_remote_port = kClientRemotePort; |
| + Head.msgh_local_port = kServerLocalPort; |
| + Head.msgh_id = 2402; |
| + NDR = NDR_record; |
| + exception = kExceptionType; |
| + codeCnt = 2; |
| + code[0] = kExceptionCodes[0]; |
| + code[1] = kExceptionCodes[1]; |
| + flavor = kThreadStateFlavor; |
| + old_stateCnt = kThreadStateFlavorCount; |
| + |
| + // Adjust the message size for the data that it’s actually carrying, which |
| + // may be smaller than the maximum that it can carry. |
| + Head.msgh_size += sizeof(old_state[0]) * old_stateCnt - sizeof(old_state); |
| + } |
| +}; |
| + |
| +struct __attribute__((packed, aligned(4))) ExceptionRaiseStateReply { |
| + mach_msg_header_t Head; |
| + NDR_record_t NDR; |
| + kern_return_t RetCode; |
| + int flavor; |
| + mach_msg_type_number_t new_stateCnt; |
| + natural_t new_state[THREAD_STATE_MAX]; |
| + |
| + void InitializeForTesting() { |
| + memset(this, 0x5a, sizeof(*this)); |
| + RetCode = KERN_FAILURE; |
| + } |
| + |
| + // Verify accepts a |behavior| parameter because the same message format and |
| + // verification function is used for ExceptionRaiseStateReply, |
| + // ExceptionRaiseStateIdentityReply, MachExceptionRaiseStateReply, and |
| + // MachExceptionRaiseStateIdentityReply. Knowing which behavior is expected |
| + // allows the message ID to be checked. |
| + void Verify(exception_behavior_t behavior) { |
| + EXPECT_EQ(static_cast<mach_msg_bits_t>( |
| + MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0)), |
| + Head.msgh_bits); |
| + EXPECT_EQ(sizeof(*this), Head.msgh_size); |
| + EXPECT_EQ(kClientRemotePort, Head.msgh_remote_port); |
| + EXPECT_EQ(static_cast<mach_port_t>(MACH_PORT_NULL), Head.msgh_local_port); |
| + switch (behavior) { |
| + case EXCEPTION_STATE: |
| + EXPECT_EQ(2502, Head.msgh_id); |
| + break; |
| + case EXCEPTION_STATE_IDENTITY: |
| + EXPECT_EQ(2503, Head.msgh_id); |
| + break; |
| + case static_cast<exception_behavior_t>(EXCEPTION_STATE | |
| + MACH_EXCEPTION_CODES): |
| + EXPECT_EQ(2506, Head.msgh_id); |
| + break; |
| + case static_cast<exception_behavior_t>(EXCEPTION_STATE_IDENTITY | |
| + MACH_EXCEPTION_CODES): |
| + EXPECT_EQ(2507, Head.msgh_id); |
| + break; |
| + default: |
| + ADD_FAILURE() << "behavior " << behavior << ", Head.msgh_id " |
| + << Head.msgh_id; |
| + break; |
| + } |
| + EXPECT_EQ(0, memcmp(&NDR, &NDR_record, sizeof(NDR))); |
| + EXPECT_EQ(KERN_SUCCESS, RetCode); |
| + EXPECT_EQ(kThreadStateFlavor, flavor); |
| + EXPECT_EQ(arraysize(new_state), new_stateCnt); |
| + } |
| +}; |
| + |
| +struct __attribute__((packed, aligned(4))) ExceptionRaiseStateIdentityRequest { |
| + mach_msg_header_t Head; |
| + mach_msg_body_t msgh_body; |
| + mach_msg_port_descriptor_t thread; |
| + mach_msg_port_descriptor_t task; |
| + NDR_record_t NDR; |
| + exception_type_t exception; |
| + mach_msg_type_number_t codeCnt; |
| + integer_t code[2]; |
| + int flavor; |
| + mach_msg_type_number_t old_stateCnt; |
| + natural_t old_state[THREAD_STATE_MAX]; |
| + |
| + void InitializeForTesting() { |
| + memset(this, 0xa5, sizeof(*this)); |
| + Head.msgh_bits = |
| + MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE) | |
| + MACH_MSGH_BITS_COMPLEX; |
| + Head.msgh_size = sizeof(*this); |
| + Head.msgh_remote_port = kClientRemotePort; |
| + Head.msgh_local_port = kServerLocalPort; |
| + Head.msgh_id = 2403; |
| + msgh_body.msgh_descriptor_count = 2; |
| + InitializeMachMsgPortDescriptor(&thread, kExceptionThreadPort); |
| + InitializeMachMsgPortDescriptor(&task, kExceptionTaskPort); |
| + NDR = NDR_record; |
| + exception = kExceptionType; |
| + codeCnt = 2; |
| + code[0] = kExceptionCodes[0]; |
| + code[1] = kExceptionCodes[1]; |
| + flavor = kThreadStateFlavor; |
| + old_stateCnt = kThreadStateFlavorCount; |
| + |
| + // Adjust the message size for the data that it’s actually carrying, which |
| + // may be smaller than the maximum that it can carry. |
| + Head.msgh_size += sizeof(old_state[0]) * old_stateCnt - sizeof(old_state); |
| + } |
| +}; |
| + |
| +// The reply messages for exception_raise_state and |
| +// exception_raise_state_identity are identical. |
| +typedef ExceptionRaiseStateReply ExceptionRaiseStateIdentityReply; |
| + |
| +struct __attribute__((packed, aligned(4))) MachExceptionRaiseRequest { |
| + mach_msg_header_t Head; |
| + mach_msg_body_t msgh_body; |
| + mach_msg_port_descriptor_t thread; |
| + mach_msg_port_descriptor_t task; |
| + NDR_record_t NDR; |
| + exception_type_t exception; |
| + mach_msg_type_number_t codeCnt; |
| + int64_t code[2]; |
| + |
| + void InitializeForTesting() { |
| + memset(this, 0xa5, sizeof(*this)); |
| + Head.msgh_bits = |
| + MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE) | |
| + MACH_MSGH_BITS_COMPLEX; |
| + Head.msgh_size = sizeof(*this); |
| + Head.msgh_remote_port = kClientRemotePort; |
| + Head.msgh_local_port = kServerLocalPort; |
| + Head.msgh_id = 2405; |
| + msgh_body.msgh_descriptor_count = 2; |
| + InitializeMachMsgPortDescriptor(&thread, kExceptionThreadPort); |
| + InitializeMachMsgPortDescriptor(&task, kExceptionTaskPort); |
| + NDR = NDR_record; |
| + exception = kExceptionType; |
| + codeCnt = 2; |
| + code[0] = kMachExceptionCodes[0]; |
| + code[1] = kMachExceptionCodes[1]; |
| + } |
| +}; |
| + |
| +// The reply messages for exception_raise and mach_exception_raise are |
| +// identical. |
| +typedef ExceptionRaiseReply MachExceptionRaiseReply; |
| + |
| +struct __attribute__((packed, aligned(4))) MachExceptionRaiseStateRequest { |
| + mach_msg_header_t Head; |
| + NDR_record_t NDR; |
| + exception_type_t exception; |
| + mach_msg_type_number_t codeCnt; |
| + int64_t code[2]; |
| + int flavor; |
| + mach_msg_type_number_t old_stateCnt; |
| + natural_t old_state[THREAD_STATE_MAX]; |
| + |
| + void InitializeForTesting() { |
| + memset(this, 0xa5, sizeof(*this)); |
| + Head.msgh_bits = |
| + MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE); |
| + Head.msgh_size = sizeof(*this); |
| + Head.msgh_remote_port = kClientRemotePort; |
| + Head.msgh_local_port = kServerLocalPort; |
| + Head.msgh_id = 2406; |
| + NDR = NDR_record; |
| + exception = kExceptionType; |
| + codeCnt = 2; |
| + code[0] = kMachExceptionCodes[0]; |
| + code[1] = kMachExceptionCodes[1]; |
| + flavor = kThreadStateFlavor; |
| + old_stateCnt = kThreadStateFlavorCount; |
| + |
| + // Adjust the message size for the data that it’s actually carrying, which |
| + // may be smaller than the maximum that it can carry. |
| + Head.msgh_size += sizeof(old_state[0]) * old_stateCnt - sizeof(old_state); |
| + } |
| +}; |
| + |
| +// The reply messages for exception_raise_state and mach_exception_raise_state |
| +// are identical. |
| +typedef ExceptionRaiseStateReply MachExceptionRaiseStateReply; |
| + |
| +struct __attribute__((packed, |
| + aligned(4))) MachExceptionRaiseStateIdentityRequest { |
| + mach_msg_header_t Head; |
| + mach_msg_body_t msgh_body; |
| + mach_msg_port_descriptor_t thread; |
| + mach_msg_port_descriptor_t task; |
| + NDR_record_t NDR; |
| + exception_type_t exception; |
| + mach_msg_type_number_t codeCnt; |
| + int64_t code[2]; |
| + int flavor; |
| + mach_msg_type_number_t old_stateCnt; |
| + natural_t old_state[THREAD_STATE_MAX]; |
| + |
| + void InitializeForTesting() { |
| + memset(this, 0xa5, sizeof(*this)); |
| + Head.msgh_bits = |
| + MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE) | |
| + MACH_MSGH_BITS_COMPLEX; |
| + Head.msgh_size = sizeof(*this); |
| + Head.msgh_remote_port = kClientRemotePort; |
| + Head.msgh_local_port = kServerLocalPort; |
| + Head.msgh_id = 2407; |
| + msgh_body.msgh_descriptor_count = 2; |
| + InitializeMachMsgPortDescriptor(&thread, kExceptionThreadPort); |
| + InitializeMachMsgPortDescriptor(&task, kExceptionTaskPort); |
| + NDR = NDR_record; |
| + exception = kExceptionType; |
| + codeCnt = 2; |
| + code[0] = kMachExceptionCodes[0]; |
| + code[1] = kMachExceptionCodes[1]; |
| + flavor = kThreadStateFlavor; |
| + old_stateCnt = kThreadStateFlavorCount; |
| + |
| + // Adjust the message size for the data that it’s actually carrying, which |
| + // may be smaller than the maximum that it can carry. |
| + Head.msgh_size += sizeof(old_state[0]) * old_stateCnt - sizeof(old_state); |
| + } |
| +}; |
| + |
| +// The reply messages for exception_raise_state_identity and |
| +// mach_exception_raise_state_identity are identical. |
| +typedef ExceptionRaiseStateIdentityReply MachExceptionRaiseStateIdentityReply; |
| + |
| +// InvalidRequest and BadIDErrorReply are used to test that |
| +// UniversalMachExcServer deals appropriately with messages that it does not |
| +// understand: messages with an unknown Head.msgh_id. |
| + |
| +struct __attribute__((packed, aligned(4))) InvalidRequest |
| + : public mach_msg_empty_send_t { |
| + void InitializeForTesting(mach_msg_id_t id) { |
| + memset(this, 0xa5, sizeof(*this)); |
| + header.msgh_bits = |
| + MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE); |
| + header.msgh_size = sizeof(*this); |
| + header.msgh_remote_port = kClientRemotePort; |
| + header.msgh_local_port = kServerLocalPort; |
| + header.msgh_id = id; |
| + } |
| +}; |
| + |
| +struct __attribute__((packed, aligned(4))) BadIDErrorReply |
| + : public mig_reply_error_t { |
| + void InitializeForTesting() { |
| + memset(this, 0x5a, sizeof(*this)); |
| + RetCode = KERN_FAILURE; |
| + } |
| + |
| + void Verify(mach_msg_id_t id) { |
| + EXPECT_EQ(static_cast<mach_msg_bits_t>( |
| + MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0)), |
| + Head.msgh_bits); |
| + EXPECT_EQ(sizeof(*this), Head.msgh_size); |
| + EXPECT_EQ(kClientRemotePort, Head.msgh_remote_port); |
| + EXPECT_EQ(static_cast<mach_port_t>(MACH_PORT_NULL), Head.msgh_local_port); |
| + EXPECT_EQ(id + 100, Head.msgh_id); |
| + EXPECT_EQ(0, memcmp(&NDR, &NDR_record, sizeof(NDR))); |
| + EXPECT_EQ(MIG_BAD_ID, RetCode); |
| + } |
| +}; |
| + |
| +class MockUniversalMachExcServer : public UniversalMachExcServer { |
| + public: |
| + struct ConstExceptionCodes { |
| + const mach_exception_data_type_t* code; |
| + mach_msg_type_number_t code_count; |
| + }; |
| + struct ThreadState { |
| + thread_state_t state; |
| + mach_msg_type_number_t* state_count; |
| + }; |
| + struct ConstThreadState { |
| + const natural_t* state; |
| + mach_msg_type_number_t* state_count; |
| + }; |
| + |
| + // CatchMachException is the method to mock, but it has 13 parameters, and |
| + // gmock can only mock methods with up to 10 parameters. Coalesce some related |
| + // parameters together into structs, and call a mocked method. |
| + virtual kern_return_t CatchMachException( |
| + exception_behavior_t behavior, |
| + exception_handler_t exception_port, |
| + thread_t thread, |
| + task_t task, |
| + exception_type_t exception, |
| + const mach_exception_data_type_t* code, |
| + mach_msg_type_number_t code_count, |
| + thread_state_flavor_t* flavor, |
| + const natural_t* old_state, |
| + mach_msg_type_number_t old_state_count, |
| + thread_state_t new_state, |
| + mach_msg_type_number_t* new_state_count, |
| + bool* destroy_complex_request) override { |
| + *destroy_complex_request = true; |
| + const ConstExceptionCodes exception_codes = {code, code_count}; |
| + const ConstThreadState old_thread_state = {old_state, &old_state_count}; |
| + ThreadState new_thread_state = {new_state, new_state_count}; |
| + return MockCatchMachException(behavior, |
| + exception_port, |
| + thread, |
| + task, |
| + exception, |
| + &exception_codes, |
| + flavor, |
| + &old_thread_state, |
| + &new_thread_state); |
| + } |
| + |
| + MOCK_METHOD9(MockCatchMachException, |
| + kern_return_t(exception_behavior_t behavior, |
| + exception_handler_t exception_port, |
| + thread_t thread, |
| + task_t task, |
| + exception_type_t exception, |
| + const ConstExceptionCodes* exception_codes, |
| + thread_state_flavor_t* flavor, |
| + const ConstThreadState* old_thread_state, |
| + ThreadState* new_thread_state)); |
| +}; |
| + |
| +// Matcher for ConstExceptionCodes, testing that it carries 2 codes matching |
| +// code_0 and code_1. |
| +MATCHER_P2(AreExceptionCodes, code_0, code_1, "") { |
| + if (!arg) { |
| + return false; |
| + } |
| + |
| + if (arg->code_count == 2 && arg->code[0] == code_0 && |
| + arg->code[1] == code_1) { |
| + return true; |
| + } |
| + |
| + *result_listener << "codes ("; |
| + for (size_t index = 0; index < arg->code_count; ++index) { |
| + *result_listener << arg->code[index]; |
| + if (index < arg->code_count - 1) { |
| + *result_listener << ", "; |
| + } |
| + } |
| + *result_listener << ")"; |
| + |
| + return false; |
| +} |
| + |
| +// Matcher for ThreadState and ConstThreadState, testing that *state_count is |
| +// present and matches the specified value. If 0 is specified for the count, |
| +// state must be NULL (not present), otherwise state must be non-NULL (present). |
| +MATCHER_P(IsThreadStateCount, state_count, "") { |
| + if (!arg) { |
| + return false; |
| + } |
| + if (!arg->state_count) { |
| + *result_listener << "state_count NULL"; |
| + return false; |
| + } |
| + if (*(arg->state_count) != state_count) { |
| + *result_listener << "*state_count " << *(arg->state_count); |
| + return false; |
| + } |
| + if (state_count) { |
| + if (!arg->state) { |
| + *result_listener << "*state_count " << state_count << ", state NULL"; |
| + return false; |
| + } |
| + } else { |
| + if (arg->state) { |
| + *result_listener << "*state_count 0, state non-NULL (" << arg->state |
| + << ")"; |
| + return false; |
| + } |
| + } |
| + return true; |
| +} |
| + |
| +template <typename T> |
| +class ScopedDefaultValue { |
| + public: |
| + explicit ScopedDefaultValue(const T& default_value) { |
| + DefaultValue<T>::Set(default_value); |
|
Robert Sesek
2014/09/10 16:44:41
Where's DefaultValue defined?
Mark Mentovai
2014/09/10 20:12:56
rsesek wrote:
|
| + } |
| + |
| + ~ScopedDefaultValue() { DefaultValue<T>::Clear(); } |
| +}; |
| + |
| +TEST(ExcServerVariants, MockExceptionRaise) { |
| + ScopedDefaultValue<kern_return_t> default_kern_return_t(KERN_FAILURE); |
|
Robert Sesek
2014/09/10 16:44:41
… I don't understand how this is used.
|
| + |
| + MockUniversalMachExcServer server; |
| + |
| + ExceptionRaiseRequest request; |
| + EXPECT_LE(sizeof(request), server.MachMessageServerRequestSize()); |
| + request.InitializeForTesting(); |
| + |
| + ExceptionRaiseReply reply; |
| + EXPECT_LE(sizeof(reply), server.MachMessageServerReplySize()); |
| + reply.InitializeForTesting(); |
| + |
| + const exception_behavior_t kExceptionBehavior = EXCEPTION_DEFAULT; |
| + |
| + EXPECT_CALL(server, |
| + MockCatchMachException( |
| + kExceptionBehavior, |
| + kServerLocalPort, |
| + kExceptionThreadPort, |
| + kExceptionTaskPort, |
| + kExceptionType, |
| + AreExceptionCodes(kExceptionCodes[0], kExceptionCodes[1]), |
| + Pointee(Eq(THREAD_STATE_NONE)), |
| + IsThreadStateCount(0u), |
| + IsThreadStateCount(0u))) |
| + .WillOnce(Return(KERN_SUCCESS)) |
| + .RetiresOnSaturation(); |
| + |
| + bool destroy_complex_request = false; |
| + EXPECT_TRUE(server.MachMessageServerFunction( |
| + reinterpret_cast<mach_msg_header_t*>(&request), |
| + reinterpret_cast<mach_msg_header_t*>(&reply), |
| + &destroy_complex_request)); |
| + EXPECT_TRUE(destroy_complex_request); |
| + |
| + reply.Verify(kExceptionBehavior); |
| +} |
| + |
| +TEST(ExcServerVariants, MockExceptionRaiseState) { |
| + ScopedDefaultValue<kern_return_t> default_kern_return_t(KERN_FAILURE); |
| + |
| + MockUniversalMachExcServer server; |
| + |
| + ExceptionRaiseStateRequest request; |
| + EXPECT_LE(sizeof(request), server.MachMessageServerRequestSize()); |
| + request.InitializeForTesting(); |
| + |
| + ExceptionRaiseStateReply reply; |
| + EXPECT_LE(sizeof(reply), server.MachMessageServerReplySize()); |
| + reply.InitializeForTesting(); |
| + |
| + const exception_behavior_t kExceptionBehavior = EXCEPTION_STATE; |
| + |
| + EXPECT_CALL(server, |
| + MockCatchMachException( |
| + kExceptionBehavior, |
| + kServerLocalPort, |
| + MACH_PORT_NULL, |
| + MACH_PORT_NULL, |
| + kExceptionType, |
| + AreExceptionCodes(kExceptionCodes[0], kExceptionCodes[1]), |
| + Pointee(Eq(kThreadStateFlavor)), |
| + IsThreadStateCount(kThreadStateFlavorCount), |
| + IsThreadStateCount(arraysize(reply.new_state)))) |
| + .WillOnce(Return(KERN_SUCCESS)) |
| + .RetiresOnSaturation(); |
| + |
| + bool destroy_complex_request = false; |
| + EXPECT_TRUE(server.MachMessageServerFunction( |
| + reinterpret_cast<mach_msg_header_t*>(&request), |
| + reinterpret_cast<mach_msg_header_t*>(&reply), |
| + &destroy_complex_request)); |
| + |
| + // The request wasn’t complex, so nothing got a chance to change the value of |
| + // this variable. |
| + EXPECT_FALSE(destroy_complex_request); |
| + |
| + reply.Verify(kExceptionBehavior); |
| +} |
| + |
| +TEST(ExcServerVariants, MockExceptionRaiseStateIdentity) { |
| + ScopedDefaultValue<kern_return_t> default_kern_return_t(KERN_FAILURE); |
| + |
| + MockUniversalMachExcServer server; |
| + |
| + ExceptionRaiseStateIdentityRequest request; |
| + EXPECT_LE(sizeof(request), server.MachMessageServerRequestSize()); |
| + request.InitializeForTesting(); |
| + |
| + ExceptionRaiseStateIdentityReply reply; |
| + EXPECT_LE(sizeof(reply), server.MachMessageServerReplySize()); |
| + reply.InitializeForTesting(); |
| + |
| + const exception_behavior_t kExceptionBehavior = EXCEPTION_STATE_IDENTITY; |
| + |
| + EXPECT_CALL(server, |
| + MockCatchMachException( |
| + kExceptionBehavior, |
| + kServerLocalPort, |
| + kExceptionThreadPort, |
| + kExceptionTaskPort, |
| + kExceptionType, |
| + AreExceptionCodes(kExceptionCodes[0], kExceptionCodes[1]), |
| + Pointee(Eq(kThreadStateFlavor)), |
| + IsThreadStateCount(kThreadStateFlavorCount), |
| + IsThreadStateCount(arraysize(reply.new_state)))) |
| + .WillOnce(Return(KERN_SUCCESS)) |
| + .RetiresOnSaturation(); |
| + |
| + bool destroy_complex_request = false; |
| + EXPECT_TRUE(server.MachMessageServerFunction( |
| + reinterpret_cast<mach_msg_header_t*>(&request), |
| + reinterpret_cast<mach_msg_header_t*>(&reply), |
| + &destroy_complex_request)); |
| + EXPECT_TRUE(destroy_complex_request); |
| + |
| + reply.Verify(kExceptionBehavior); |
| +} |
| + |
| +TEST(ExcServerVariants, MockMachExceptionRaise) { |
| + ScopedDefaultValue<kern_return_t> default_kern_return_t(KERN_FAILURE); |
| + |
| + MockUniversalMachExcServer server; |
| + |
| + MachExceptionRaiseRequest request; |
| + EXPECT_LE(sizeof(request), server.MachMessageServerRequestSize()); |
| + request.InitializeForTesting(); |
| + |
| + MachExceptionRaiseReply reply; |
| + EXPECT_LE(sizeof(reply), server.MachMessageServerReplySize()); |
| + reply.InitializeForTesting(); |
| + |
| + const exception_behavior_t kExceptionBehavior = |
| + EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES; |
| + |
| + EXPECT_CALL( |
| + server, |
| + MockCatchMachException( |
| + kExceptionBehavior, |
| + kServerLocalPort, |
| + kExceptionThreadPort, |
| + kExceptionTaskPort, |
| + kExceptionType, |
| + AreExceptionCodes(kMachExceptionCodes[0], kMachExceptionCodes[1]), |
| + Pointee(Eq(THREAD_STATE_NONE)), |
| + IsThreadStateCount(0u), |
| + IsThreadStateCount(0u))) |
| + .WillOnce(Return(KERN_SUCCESS)) |
| + .RetiresOnSaturation(); |
| + |
| + bool destroy_complex_request = false; |
| + EXPECT_TRUE(server.MachMessageServerFunction( |
| + reinterpret_cast<mach_msg_header_t*>(&request), |
| + reinterpret_cast<mach_msg_header_t*>(&reply), |
| + &destroy_complex_request)); |
| + EXPECT_TRUE(destroy_complex_request); |
| + |
| + reply.Verify(kExceptionBehavior); |
| +} |
| + |
| +TEST(ExcServerVariants, MockMachExceptionRaiseState) { |
| + ScopedDefaultValue<kern_return_t> default_kern_return_t(KERN_FAILURE); |
| + |
| + MockUniversalMachExcServer server; |
| + |
| + MachExceptionRaiseStateRequest request; |
| + EXPECT_LE(sizeof(request), server.MachMessageServerRequestSize()); |
| + request.InitializeForTesting(); |
| + |
| + MachExceptionRaiseStateReply reply; |
| + EXPECT_LE(sizeof(reply), server.MachMessageServerReplySize()); |
| + reply.InitializeForTesting(); |
| + |
| + const exception_behavior_t kExceptionBehavior = |
| + EXCEPTION_STATE | MACH_EXCEPTION_CODES; |
| + |
| + EXPECT_CALL( |
| + server, |
| + MockCatchMachException( |
| + kExceptionBehavior, |
| + kServerLocalPort, |
| + MACH_PORT_NULL, |
| + MACH_PORT_NULL, |
| + kExceptionType, |
| + AreExceptionCodes(kMachExceptionCodes[0], kMachExceptionCodes[1]), |
| + Pointee(Eq(kThreadStateFlavor)), |
| + IsThreadStateCount(kThreadStateFlavorCount), |
| + IsThreadStateCount(arraysize(reply.new_state)))) |
| + .WillOnce(Return(KERN_SUCCESS)) |
| + .RetiresOnSaturation(); |
| + |
| + bool destroy_complex_request = false; |
| + EXPECT_TRUE(server.MachMessageServerFunction( |
| + reinterpret_cast<mach_msg_header_t*>(&request), |
| + reinterpret_cast<mach_msg_header_t*>(&reply), |
| + &destroy_complex_request)); |
| + |
| + // The request wasn’t complex, so nothing got a chance to change the value of |
| + // this variable. |
| + EXPECT_FALSE(destroy_complex_request); |
| + |
| + reply.Verify(kExceptionBehavior); |
| +} |
| + |
| +TEST(ExcServerVariants, MockMachExceptionRaiseStateIdentity) { |
| + ScopedDefaultValue<kern_return_t> default_kern_return_t(KERN_FAILURE); |
| + |
| + MockUniversalMachExcServer server; |
| + |
| + MachExceptionRaiseStateIdentityRequest request; |
| + EXPECT_LE(sizeof(request), server.MachMessageServerRequestSize()); |
| + request.InitializeForTesting(); |
| + |
| + MachExceptionRaiseStateIdentityReply reply; |
| + EXPECT_LE(sizeof(reply), server.MachMessageServerReplySize()); |
| + reply.InitializeForTesting(); |
| + |
| + const exception_behavior_t kExceptionBehavior = |
| + EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES; |
| + |
| + EXPECT_CALL( |
| + server, |
| + MockCatchMachException( |
| + kExceptionBehavior, |
| + kServerLocalPort, |
| + kExceptionThreadPort, |
| + kExceptionTaskPort, |
| + kExceptionType, |
| + AreExceptionCodes(kMachExceptionCodes[0], kMachExceptionCodes[1]), |
| + Pointee(Eq(kThreadStateFlavor)), |
| + IsThreadStateCount(kThreadStateFlavorCount), |
| + IsThreadStateCount(arraysize(reply.new_state)))) |
| + .WillOnce(Return(KERN_SUCCESS)) |
| + .RetiresOnSaturation(); |
| + |
| + bool destroy_complex_request = false; |
| + EXPECT_TRUE(server.MachMessageServerFunction( |
| + reinterpret_cast<mach_msg_header_t*>(&request), |
| + reinterpret_cast<mach_msg_header_t*>(&reply), |
| + &destroy_complex_request)); |
| + EXPECT_TRUE(destroy_complex_request); |
| + |
| + reply.Verify(kExceptionBehavior); |
| +} |
| + |
| +TEST(ExcServerVariants, MockUnknownID) { |
| + ScopedDefaultValue<kern_return_t> default_kern_return_t(KERN_FAILURE); |
| + |
| + MockUniversalMachExcServer server; |
| + |
| + // Make sure that a message with an unknown ID is handled appropriately. |
| + // UniversalMachExcServer should not dispatch the message to |
| + // MachMessageServerFunction, but should generate a MIG_BAD_ID error reply. |
| + |
| + const mach_msg_id_t unknown_ids[] = { |
| + // Reasonable things to check. |
| + -101, |
| + -100, |
| + -99, |
| + -1, |
| + 0, |
| + 1, |
| + 99, |
| + 100, |
| + 101, |
| + |
| + // Invalid IDs right around valid ones. |
| + 2400, |
| + 2404, |
| + 2408, |
| + |
| + // Valid and invalid IDs in the range used for replies, not requests. |
| + 2500, |
| + 2501, |
| + 2502, |
| + 2503, |
| + 2504, |
| + 2505, |
| + 2506, |
| + 2507, |
| + 2508, |
| + }; |
| + |
| + for (size_t index = 0; index < arraysize(unknown_ids); ++index) { |
| + mach_msg_id_t id = unknown_ids[index]; |
| + |
| + SCOPED_TRACE(base::StringPrintf("unknown id %d", id)); |
| + |
| + InvalidRequest request; |
| + EXPECT_LE(sizeof(request), server.MachMessageServerRequestSize()); |
| + request.InitializeForTesting(id); |
| + |
| + BadIDErrorReply reply; |
| + EXPECT_LE(sizeof(reply), server.MachMessageServerReplySize()); |
| + reply.InitializeForTesting(); |
| + |
| + bool destroy_complex_request = false; |
| + EXPECT_FALSE(server.MachMessageServerFunction( |
| + reinterpret_cast<mach_msg_header_t*>(&request), |
| + reinterpret_cast<mach_msg_header_t*>(&reply), |
| + &destroy_complex_request)); |
| + |
| + // The request wasn’t handled, nothing got a chance to change the value of |
| + // this variable. MachMessageServer would destroy the request if it was |
| + // complex, regardless of what was done to this variable, because the |
| + // return code was not KERN_SUCCESS or MIG_NO_REPLY. |
| + EXPECT_FALSE(destroy_complex_request); |
| + |
| + reply.Verify(id); |
| + } |
| +} |
| + |
| +class TestExcServerVariants : public UniversalMachExcServer, |
| + public MachMultiprocess { |
| + public: |
| + TestExcServerVariants(exception_behavior_t behavior, |
| + thread_state_flavor_t flavor, |
| + mach_msg_type_number_t state_count) |
| + : UniversalMachExcServer(), |
| + MachMultiprocess(), |
| + behavior_(behavior), |
| + flavor_(flavor), |
| + state_count_(state_count), |
| + handled_(false) { |
| + } |
| + |
| + // UniversalMachExcServer: |
| + |
| + virtual kern_return_t CatchMachException( |
| + exception_behavior_t behavior, |
| + exception_handler_t exception_port, |
| + thread_t thread, |
| + task_t task, |
| + exception_type_t exception, |
| + const mach_exception_data_type_t* code, |
| + mach_msg_type_number_t code_count, |
| + thread_state_flavor_t* flavor, |
| + const natural_t* old_state, |
| + mach_msg_type_number_t old_state_count, |
| + thread_state_t new_state, |
| + mach_msg_type_number_t* new_state_count, |
| + bool* destroy_complex_request) override { |
| + *destroy_complex_request = true; |
| + |
| + EXPECT_FALSE(handled_); |
| + handled_ = true; |
| + |
| + EXPECT_EQ(behavior_, behavior); |
| + exception_behavior_t basic_behavior = behavior & ~MACH_EXCEPTION_CODES; |
| + const bool has_identity = basic_behavior == EXCEPTION_DEFAULT || |
| + basic_behavior == EXCEPTION_STATE_IDENTITY; |
| + const bool has_state = basic_behavior == EXCEPTION_STATE || |
| + basic_behavior == EXCEPTION_STATE_IDENTITY; |
| + |
| + EXPECT_EQ(LocalPort(), exception_port); |
| + |
| + if (has_identity) { |
| + EXPECT_NE(static_cast<mach_port_t>(MACH_PORT_NULL), thread); |
|
Robert Sesek
2014/09/10 16:44:41
You write this cast enough that it could be a hand
Mark Mentovai
2014/09/10 20:12:56
rsesek wrote:
|
| + EXPECT_EQ(ChildTask(), task); |
| + } else { |
| + EXPECT_EQ(static_cast<mach_port_t>(MACH_PORT_NULL), thread); |
| + EXPECT_EQ(static_cast<mach_port_t>(MACH_PORT_NULL), task); |
| + } |
| + |
| + EXPECT_EQ(EXC_CRASH, exception); |
| + EXPECT_EQ(2u, code_count); |
| + |
| + if (code_count > 1) { |
| + // The signal that terminated the process is stored in code[0] along with |
| + // some other data. See 10.9.4 xnu-2422.110.17/bsd/kern/kern_exit.c |
| + // proc_prepareexit(). |
| + int sig = (code[0] >> 24) & 0xff; |
| + SetExpectedChildTermination(kTerminationSignal, sig); |
| + } |
| + |
| + if (has_state) { |
| + EXPECT_EQ(flavor_, *flavor); |
| + EXPECT_EQ(state_count_, old_state_count); |
| + EXPECT_NE(static_cast<const natural_t*>(NULL), old_state); |
| + EXPECT_EQ(static_cast<mach_msg_type_number_t>(THREAD_STATE_MAX), |
| + *new_state_count); |
| + EXPECT_NE(static_cast<const natural_t*>(NULL), new_state); |
| + } else { |
| + EXPECT_EQ(THREAD_STATE_NONE, *flavor); |
| + EXPECT_EQ(0u, old_state_count); |
| + EXPECT_EQ(NULL, old_state); |
| + EXPECT_EQ(0u, *new_state_count); |
| + EXPECT_EQ(NULL, new_state); |
| + } |
| + |
| + // Even for an EXC_CRASH handler, returning KERN_SUCCESS with a |
| + // state-carrying reply will cause the kernel to try to set a new thread |
| + // state, leading to a perceptible waste of time. Returning |
| + // MACH_RCV_PORT_DIED is the only way to suppress this behavior while also |
| + // preventing the kernel from looking for another (host-level) EXC_CRASH |
| + // handler. See 10.9.4 xnu-2422.110.17/osfmk/kern/exception.c |
| + // exception_triage(). |
| + return has_state ? MACH_RCV_PORT_DIED : KERN_SUCCESS; |
| + } |
| + |
| + |
|
Robert Sesek
2014/09/10 16:44:41
nit: double blank line
|
| + private: |
| + // MachMultiprocess: |
| + |
| + virtual void MachMultiprocessParent() override { |
| + kern_return_t kr = MachMessageServer::Run(this, |
| + LocalPort(), |
| + MACH_MSG_OPTION_NONE, |
| + MachMessageServer::kOneShot, |
| + MachMessageServer::kBlocking, |
| + 0); |
| + EXPECT_EQ(KERN_SUCCESS, kr) |
| + << MachErrorMessage(kr, "MachMessageServer::Run"); |
| + |
| + EXPECT_TRUE(handled_); |
| + } |
| + |
| + virtual void MachMultiprocessChild() override { |
| + // Set the parent as the exception handler for EXC_CRASH. |
| + kern_return_t kr = task_set_exception_ports( |
| + mach_task_self(), EXC_MASK_CRASH, RemotePort(), behavior_, flavor_); |
| + ASSERT_EQ(KERN_SUCCESS, kr) |
| + << MachErrorMessage(kr, "task_set_exception_ports"); |
| + |
| + // Now crash. |
| + __builtin_trap(); |
| + } |
| + |
| + exception_behavior_t behavior_; |
| + thread_state_flavor_t flavor_; |
| + mach_msg_type_number_t state_count_; |
| + bool handled_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(TestExcServerVariants); |
| +}; |
| + |
| +TEST(ExcServerVariants, ExceptionRaise) { |
| + TestExcServerVariants test_exc_server_variants( |
| + EXCEPTION_DEFAULT, THREAD_STATE_NONE, 0); |
| + test_exc_server_variants.Run(); |
| +} |
| + |
| +TEST(ExcServerVariants, ExceptionRaiseState) { |
| + TestExcServerVariants test_exc_server_variants( |
| + EXCEPTION_STATE, MACHINE_THREAD_STATE, MACHINE_THREAD_STATE_COUNT); |
| + test_exc_server_variants.Run(); |
| +} |
| + |
| +TEST(ExcServerVariants, ExceptionRaiseStateIdentity) { |
| + TestExcServerVariants test_exc_server_variants(EXCEPTION_STATE_IDENTITY, |
| + MACHINE_THREAD_STATE, |
| + MACHINE_THREAD_STATE_COUNT); |
| + test_exc_server_variants.Run(); |
| +} |
| + |
| +TEST(ExcServerVariants, MachExceptionRaise) { |
| + TestExcServerVariants test_exc_server_variants( |
| + MACH_EXCEPTION_CODES | EXCEPTION_DEFAULT, THREAD_STATE_NONE, 0); |
| + test_exc_server_variants.Run(); |
| +} |
| + |
| +TEST(ExcServerVariants, MachExceptionRaiseState) { |
| + TestExcServerVariants test_exc_server_variants( |
| + MACH_EXCEPTION_CODES | EXCEPTION_STATE, |
| + MACHINE_THREAD_STATE, |
| + MACHINE_THREAD_STATE_COUNT); |
| + test_exc_server_variants.Run(); |
| +} |
| + |
| +TEST(ExcServerVariants, MachExceptionRaiseStateIdentity) { |
| + TestExcServerVariants test_exc_server_variants( |
| + MACH_EXCEPTION_CODES | EXCEPTION_STATE_IDENTITY, |
| + MACHINE_THREAD_STATE, |
| + MACHINE_THREAD_STATE_COUNT); |
| + test_exc_server_variants.Run(); |
| +} |
| + |
| +TEST(ExcServerVariants, ThreadStates) { |
| + // So far, all of the tests worked with MACHINE_THREAD_STATE. Now try all of |
| + // the other thread state flavors that are expected to work. |
| + |
| + struct TestData { |
| + thread_state_flavor_t flavor; |
| + mach_msg_type_number_t count; |
| + }; |
| + const TestData test_data[] = { |
| +#if defined(ARCH_CPU_X86_FAMILY) |
| +#if defined(ARCH_CPU_X86) |
| + { x86_THREAD_STATE32, x86_THREAD_STATE32_COUNT }, |
| + { x86_FLOAT_STATE32, x86_FLOAT_STATE32_COUNT }, |
| + { x86_EXCEPTION_STATE32, x86_EXCEPTION_STATE32_COUNT }, |
| + { x86_DEBUG_STATE32, x86_DEBUG_STATE32_COUNT }, |
| + // Don’t test x86_AVX_STATE32 because it’s not available on all CPUs and |
| + // OS versionns. |
| +#endif |
| +#if defined(ARCH_CPU_X86_64) |
| + { x86_THREAD_STATE64, x86_THREAD_STATE64_COUNT }, |
| + { x86_FLOAT_STATE64, x86_FLOAT_STATE64_COUNT }, |
| + { x86_EXCEPTION_STATE64, x86_EXCEPTION_STATE64_COUNT }, |
| + { x86_DEBUG_STATE64, x86_DEBUG_STATE64_COUNT }, |
| + // Don’t test x86_AVX_STATE64 because it’s not available on all CPUs and |
| + // OS versions. |
| +#endif |
| + { x86_THREAD_STATE, x86_THREAD_STATE_COUNT }, |
| + { x86_FLOAT_STATE, x86_FLOAT_STATE_COUNT }, |
| + { x86_EXCEPTION_STATE, x86_EXCEPTION_STATE_COUNT }, |
| + { x86_DEBUG_STATE, x86_DEBUG_STATE_COUNT }, |
| + // Don’t test x86_AVX_STATE because it’s not available on all CPUs and OS |
| + // versions. |
| +#else |
| +#error Port this test to your CPU architecture. |
| +#endif |
| + }; |
| + |
| + for (size_t index = 0; index < arraysize(test_data); ++index) { |
| + const TestData& test = test_data[index]; |
| + SCOPED_TRACE(base::StringPrintf( |
| + "index %zu, flavor %d", index, test.flavor)); |
| + |
| + TestExcServerVariants test_exc_server_variants( |
| + MACH_EXCEPTION_CODES | EXCEPTION_STATE_IDENTITY, |
| + test.flavor, |
| + test.count); |
| + test_exc_server_variants.Run(); |
| + } |
| +} |
| + |
| +} // namespace |