| // Copyright 2014 The Crashpad Authors |
| // |
| // 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 <stdint.h> |
| #include <string.h> |
| #include <sys/types.h> |
| |
| #include <iterator> |
| |
| #include "base/strings/stringprintf.h" |
| #include "build/build_config.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include "test/mac/mach_errors.h" |
| #include "util/mac/mac_util.h" |
| #include "util/mach/exception_behaviors.h" |
| #include "util/mach/exception_types.h" |
| #include "util/mach/mach_message.h" |
| #include "util/misc/implicit_cast.h" |
| |
| #if BUILDFLAG(IS_MAC) |
| #include "test/mac/mach_multiprocess.h" |
| #endif // BUILDFLAG(IS_MAC) |
| |
| namespace crashpad { |
| namespace test { |
| namespace { |
| |
| using testing::DefaultValue; |
| using testing::Eq; |
| using testing::Pointee; |
| using testing::Return; |
| |
| // 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. |
| constexpr mach_port_t kClientRemotePort = 0x01010101; |
| constexpr mach_port_t kServerLocalPort = 0x02020202; |
| constexpr thread_t kExceptionThreadPort = 0x03030303; |
| constexpr task_t kExceptionTaskPort = 0x04040404; |
| |
| // Other fake exception values. |
| constexpr 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. |
| constexpr exception_data_type_t kTestExceptonCodes[] = { |
| KERN_PROTECTION_FAILURE, |
| implicit_cast<exception_data_type_t>(0xfedcba98), |
| }; |
| |
| constexpr mach_exception_data_type_t kTestMachExceptionCodes[] = { |
| KERN_PROTECTION_FAILURE, |
| implicit_cast<mach_exception_data_type_t>(0xfedcba9876543210), |
| }; |
| |
| constexpr thread_state_flavor_t kThreadStateFlavor = MACHINE_THREAD_STATE; |
| constexpr 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_PORT_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 { |
| ExceptionRaiseRequest() { |
| memset(this, 0xa5, sizeof(*this)); |
| Head.msgh_bits = |
| MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND_ONCE, MACH_MSG_TYPE_PORT_SEND) | |
| MACH_MSGH_BITS_COMPLEX; |
| Head.msgh_size = sizeof(*this) - sizeof(trailer); |
| 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] = kTestExceptonCodes[0]; |
| code[1] = kTestExceptonCodes[1]; |
| } |
| |
| 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]; |
| mach_msg_trailer_t trailer; |
| }; |
| |
| struct __attribute__((packed, aligned(4))) ExceptionRaiseReply { |
| ExceptionRaiseReply() { |
| 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(Head.msgh_bits, |
| implicit_cast<mach_msg_bits_t>( |
| MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND_ONCE, 0))); |
| EXPECT_EQ(Head.msgh_size, sizeof(*this)); |
| EXPECT_EQ(Head.msgh_remote_port, kClientRemotePort); |
| EXPECT_EQ(Head.msgh_local_port, kMachPortNull); |
| switch (behavior) { |
| case EXCEPTION_DEFAULT: |
| EXPECT_EQ(Head.msgh_id, 2501); |
| break; |
| case EXCEPTION_DEFAULT | kMachExceptionCodes: |
| EXPECT_EQ(Head.msgh_id, 2505); |
| break; |
| default: |
| ADD_FAILURE() << "behavior " << behavior << ", Head.msgh_id " |
| << Head.msgh_id; |
| break; |
| } |
| EXPECT_EQ(memcmp(&NDR, &NDR_record, sizeof(NDR)), 0); |
| EXPECT_EQ(RetCode, KERN_SUCCESS); |
| } |
| |
| mach_msg_header_t Head; |
| NDR_record_t NDR; |
| kern_return_t RetCode; |
| }; |
| |
| struct __attribute__((packed, aligned(4))) ExceptionRaiseStateRequest { |
| ExceptionRaiseStateRequest() { |
| memset(this, 0xa5, sizeof(*this)); |
| Head.msgh_bits = |
| MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND_ONCE, MACH_MSG_TYPE_PORT_SEND); |
| Head.msgh_size = sizeof(*this) - sizeof(trailer); |
| Head.msgh_remote_port = kClientRemotePort; |
| Head.msgh_local_port = kServerLocalPort; |
| Head.msgh_id = 2402; |
| NDR = NDR_record; |
| exception = kExceptionType; |
| codeCnt = 2; |
| code[0] = kTestExceptonCodes[0]; |
| code[1] = kTestExceptonCodes[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); |
| } |
| |
| // Because the message size has been adjusted, the trailer may not appear in |
| // its home member variable. This computes the actual address of the trailer. |
| const mach_msg_trailer_t* Trailer() const { |
| return MachMessageTrailerFromHeader(&Head); |
| } |
| |
| 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]; |
| mach_msg_trailer_t trailer; |
| }; |
| |
| struct __attribute__((packed, aligned(4))) ExceptionRaiseStateReply { |
| ExceptionRaiseStateReply() { |
| 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(Head.msgh_bits, |
| implicit_cast<mach_msg_bits_t>( |
| MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND_ONCE, 0))); |
| EXPECT_EQ(Head.msgh_size, sizeof(*this)); |
| EXPECT_EQ(Head.msgh_remote_port, kClientRemotePort); |
| EXPECT_EQ(Head.msgh_local_port, kMachPortNull); |
| switch (behavior) { |
| case EXCEPTION_STATE: |
| EXPECT_EQ(Head.msgh_id, 2502); |
| break; |
| case EXCEPTION_STATE_IDENTITY: |
| EXPECT_EQ(Head.msgh_id, 2503); |
| break; |
| case EXCEPTION_STATE | kMachExceptionCodes: |
| EXPECT_EQ(Head.msgh_id, 2506); |
| break; |
| case EXCEPTION_STATE_IDENTITY | kMachExceptionCodes: |
| EXPECT_EQ(Head.msgh_id, 2507); |
| break; |
| default: |
| ADD_FAILURE() << "behavior " << behavior << ", Head.msgh_id " |
| << Head.msgh_id; |
| break; |
| } |
| EXPECT_EQ(memcmp(&NDR, &NDR_record, sizeof(NDR)), 0); |
| EXPECT_EQ(RetCode, KERN_SUCCESS); |
| EXPECT_EQ(flavor, kThreadStateFlavor); |
| EXPECT_EQ(new_stateCnt, std::size(new_state)); |
| } |
| |
| 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]; |
| }; |
| |
| struct __attribute__((packed, aligned(4))) ExceptionRaiseStateIdentityRequest { |
| ExceptionRaiseStateIdentityRequest() { |
| memset(this, 0xa5, sizeof(*this)); |
| Head.msgh_bits = |
| MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND_ONCE, MACH_MSG_TYPE_PORT_SEND) | |
| MACH_MSGH_BITS_COMPLEX; |
| Head.msgh_size = sizeof(*this) - sizeof(trailer); |
| 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] = kTestExceptonCodes[0]; |
| code[1] = kTestExceptonCodes[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); |
| } |
| |
| // Because the message size has been adjusted, the trailer may not appear in |
| // its home member variable. This computes the actual address of the trailer. |
| const mach_msg_trailer_t* Trailer() const { |
| return MachMessageTrailerFromHeader(&Head); |
| } |
| |
| 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]; |
| mach_msg_trailer_t trailer; |
| }; |
| |
| // The reply messages for exception_raise_state and |
| // exception_raise_state_identity are identical. |
| using ExceptionRaiseStateIdentityReply = ExceptionRaiseStateReply; |
| |
| struct __attribute__((packed, aligned(4))) MachExceptionRaiseRequest { |
| MachExceptionRaiseRequest() { |
| memset(this, 0xa5, sizeof(*this)); |
| Head.msgh_bits = |
| MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND_ONCE, MACH_MSG_TYPE_PORT_SEND) | |
| MACH_MSGH_BITS_COMPLEX; |
| Head.msgh_size = sizeof(*this) - sizeof(trailer); |
| 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] = kTestMachExceptionCodes[0]; |
| code[1] = kTestMachExceptionCodes[1]; |
| } |
| |
| 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]; |
| mach_msg_trailer_t trailer; |
| }; |
| |
| // The reply messages for exception_raise and mach_exception_raise are |
| // identical. |
| using MachExceptionRaiseReply = ExceptionRaiseReply; |
| |
| struct __attribute__((packed, aligned(4))) MachExceptionRaiseStateRequest { |
| MachExceptionRaiseStateRequest() { |
| memset(this, 0xa5, sizeof(*this)); |
| Head.msgh_bits = |
| MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND_ONCE, MACH_MSG_TYPE_PORT_SEND); |
| Head.msgh_size = sizeof(*this) - sizeof(trailer); |
| Head.msgh_remote_port = kClientRemotePort; |
| Head.msgh_local_port = kServerLocalPort; |
| Head.msgh_id = 2406; |
| NDR = NDR_record; |
| exception = kExceptionType; |
| codeCnt = 2; |
| code[0] = kTestMachExceptionCodes[0]; |
| code[1] = kTestMachExceptionCodes[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); |
| } |
| |
| // Because the message size has been adjusted, the trailer may not appear in |
| // its home member variable. This computes the actual address of the trailer. |
| const mach_msg_trailer_t* Trailer() const { |
| return MachMessageTrailerFromHeader(&Head); |
| } |
| |
| 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]; |
| mach_msg_trailer_t trailer; |
| }; |
| |
| // The reply messages for exception_raise_state and mach_exception_raise_state |
| // are identical. |
| using MachExceptionRaiseStateReply = ExceptionRaiseStateReply; |
| |
| struct __attribute__((packed, aligned(4))) |
| MachExceptionRaiseStateIdentityRequest { |
| MachExceptionRaiseStateIdentityRequest() { |
| memset(this, 0xa5, sizeof(*this)); |
| Head.msgh_bits = |
| MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND_ONCE, MACH_MSG_TYPE_PORT_SEND) | |
| MACH_MSGH_BITS_COMPLEX; |
| Head.msgh_size = sizeof(*this) - sizeof(trailer); |
| 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] = kTestMachExceptionCodes[0]; |
| code[1] = kTestMachExceptionCodes[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); |
| } |
| |
| // Because the message size has been adjusted, the trailer may not appear in |
| // its home member variable. This computes the actual address of the trailer. |
| const mach_msg_trailer_t* Trailer() const { |
| return MachMessageTrailerFromHeader(&Head); |
| } |
| |
| 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]; |
| mach_msg_trailer_t trailer; |
| }; |
| |
| // The reply messages for exception_raise_state_identity and |
| // mach_exception_raise_state_identity are identical. |
| using MachExceptionRaiseStateIdentityReply = ExceptionRaiseStateIdentityReply; |
| |
| // 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 InvalidRequest : public mach_msg_empty_send_t { |
| explicit InvalidRequest(mach_msg_id_t id) { |
| memset(this, 0xa5, sizeof(*this)); |
| header.msgh_bits = |
| MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND_ONCE, MACH_MSG_TYPE_PORT_SEND); |
| header.msgh_size = sizeof(*this); |
| header.msgh_remote_port = kClientRemotePort; |
| header.msgh_local_port = kServerLocalPort; |
| header.msgh_id = id; |
| } |
| }; |
| |
| struct BadIDErrorReply : public mig_reply_error_t { |
| BadIDErrorReply() { |
| memset(this, 0x5a, sizeof(*this)); |
| RetCode = KERN_FAILURE; |
| } |
| |
| void Verify(mach_msg_id_t id) { |
| EXPECT_EQ(Head.msgh_bits, |
| implicit_cast<mach_msg_bits_t>( |
| MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND_ONCE, 0))); |
| EXPECT_EQ(Head.msgh_size, sizeof(*this)); |
| EXPECT_EQ(Head.msgh_remote_port, kClientRemotePort); |
| EXPECT_EQ(Head.msgh_local_port, kMachPortNull); |
| EXPECT_EQ(Head.msgh_id, id + 100); |
| EXPECT_EQ(memcmp(&NDR, &NDR_record, sizeof(NDR)), 0); |
| EXPECT_EQ(RetCode, MIG_BAD_ID); |
| } |
| }; |
| |
| class MockUniversalMachExcServer : public UniversalMachExcServer::Interface { |
| public: |
| struct ConstExceptionCodes { |
| const mach_exception_data_type_t* code; |
| mach_msg_type_number_t code_count; |
| }; |
| struct ThreadStateAndCount { |
| thread_state_t state; |
| mach_msg_type_number_t* state_count; |
| }; |
| struct ConstThreadStateAndCount { |
| ConstThreadState state; |
| mach_msg_type_number_t* state_count; |
| }; |
| |
| // UniversalMachExcServer::Interface: |
| |
| // CatchMachException is the method to mock, but it has 13 parameters, and |
| // Google Mock 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, |
| ConstThreadState old_state, |
| mach_msg_type_number_t old_state_count, |
| thread_state_t new_state, |
| mach_msg_type_number_t* new_state_count, |
| const mach_msg_trailer_t* trailer, |
| bool* destroy_complex_request) override { |
| *destroy_complex_request = true; |
| const ConstExceptionCodes exception_codes = {code, code_count}; |
| const ConstThreadStateAndCount old_thread_state = {old_state, |
| &old_state_count}; |
| ThreadStateAndCount 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, |
| trailer); |
| } |
| |
| MOCK_METHOD(kern_return_t, |
| MockCatchMachException, |
| (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 ConstThreadStateAndCount* old_thread_state, |
| ThreadStateAndCount* new_thread_state, |
| const mach_msg_trailer_t* trailer)); |
| }; |
| |
| // 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 ThreadStateAndCount and ConstThreadStateAndCount, testing that |
| // *state_count is present and matches the specified value. If 0 is specified |
| // for the count, |state| must be nullptr (not present), otherwise |state| must |
| // not be nullptr (present). |
| MATCHER_P(IsThreadStateAndCount, state_count, "") { |
| if (!arg) { |
| return false; |
| } |
| if (!arg->state_count) { |
| *result_listener << "state_count nullptr"; |
| 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 nullptr"; |
| return false; |
| } |
| } else { |
| if (arg->state) { |
| *result_listener << "*state_count 0, state non-nullptr (" << arg->state |
| << ")"; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| template <typename T> |
| class ScopedDefaultValue { |
| public: |
| explicit ScopedDefaultValue(const T& default_value) { |
| DefaultValue<T>::Set(default_value); |
| } |
| |
| ~ScopedDefaultValue() { DefaultValue<T>::Clear(); } |
| }; |
| |
| TEST(ExcServerVariants, MockExceptionRaise) { |
| ScopedDefaultValue<kern_return_t> default_kern_return_t(KERN_FAILURE); |
| |
| MockUniversalMachExcServer server; |
| UniversalMachExcServer universal_mach_exc_server(&server); |
| |
| std::set<mach_msg_id_t> ids = |
| universal_mach_exc_server.MachMessageServerRequestIDs(); |
| EXPECT_NE(ids.find(2401), ids.end()); // There is no constant for this. |
| |
| ExceptionRaiseRequest request; |
| EXPECT_LE(request.Head.msgh_size, |
| universal_mach_exc_server.MachMessageServerRequestSize()); |
| |
| ExceptionRaiseReply reply; |
| EXPECT_LE(sizeof(reply), |
| universal_mach_exc_server.MachMessageServerReplySize()); |
| |
| constexpr exception_behavior_t kExceptionBehavior = EXCEPTION_DEFAULT; |
| |
| EXPECT_CALL(server, |
| MockCatchMachException(kExceptionBehavior, |
| kServerLocalPort, |
| kExceptionThreadPort, |
| kExceptionTaskPort, |
| kExceptionType, |
| AreExceptionCodes(kTestExceptonCodes[0], |
| kTestExceptonCodes[1]), |
| Pointee(Eq(THREAD_STATE_NONE)), |
| IsThreadStateAndCount(0u), |
| IsThreadStateAndCount(0u), |
| Eq(&request.trailer))) |
| .WillOnce(Return(KERN_SUCCESS)) |
| .RetiresOnSaturation(); |
| |
| bool destroy_complex_request = false; |
| EXPECT_TRUE(universal_mach_exc_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; |
| UniversalMachExcServer universal_mach_exc_server(&server); |
| |
| std::set<mach_msg_id_t> ids = |
| universal_mach_exc_server.MachMessageServerRequestIDs(); |
| EXPECT_NE(ids.find(2402), ids.end()); // There is no constant for this. |
| |
| ExceptionRaiseStateRequest request; |
| EXPECT_LE(request.Head.msgh_size, |
| universal_mach_exc_server.MachMessageServerRequestSize()); |
| |
| ExceptionRaiseStateReply reply; |
| EXPECT_LE(sizeof(reply), |
| universal_mach_exc_server.MachMessageServerReplySize()); |
| |
| constexpr exception_behavior_t kExceptionBehavior = EXCEPTION_STATE; |
| |
| EXPECT_CALL( |
| server, |
| MockCatchMachException( |
| kExceptionBehavior, |
| kServerLocalPort, |
| THREAD_NULL, |
| TASK_NULL, |
| kExceptionType, |
| AreExceptionCodes(kTestExceptonCodes[0], kTestExceptonCodes[1]), |
| Pointee(Eq(kThreadStateFlavor)), |
| IsThreadStateAndCount(kThreadStateFlavorCount), |
| IsThreadStateAndCount(std::size(reply.new_state)), |
| Eq(request.Trailer()))) |
| .WillOnce(Return(KERN_SUCCESS)) |
| .RetiresOnSaturation(); |
| |
| bool destroy_complex_request = false; |
| EXPECT_TRUE(universal_mach_exc_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; |
| UniversalMachExcServer universal_mach_exc_server(&server); |
| |
| std::set<mach_msg_id_t> ids = |
| universal_mach_exc_server.MachMessageServerRequestIDs(); |
| EXPECT_NE(ids.find(2403), ids.end()); // There is no constant for this. |
| |
| ExceptionRaiseStateIdentityRequest request; |
| EXPECT_LE(request.Head.msgh_size, |
| universal_mach_exc_server.MachMessageServerRequestSize()); |
| |
| ExceptionRaiseStateIdentityReply reply; |
| EXPECT_LE(sizeof(reply), |
| universal_mach_exc_server.MachMessageServerReplySize()); |
| |
| constexpr exception_behavior_t kExceptionBehavior = EXCEPTION_STATE_IDENTITY; |
| |
| EXPECT_CALL( |
| server, |
| MockCatchMachException( |
| kExceptionBehavior, |
| kServerLocalPort, |
| kExceptionThreadPort, |
| kExceptionTaskPort, |
| kExceptionType, |
| AreExceptionCodes(kTestExceptonCodes[0], kTestExceptonCodes[1]), |
| Pointee(Eq(kThreadStateFlavor)), |
| IsThreadStateAndCount(kThreadStateFlavorCount), |
| IsThreadStateAndCount(std::size(reply.new_state)), |
| Eq(request.Trailer()))) |
| .WillOnce(Return(KERN_SUCCESS)) |
| .RetiresOnSaturation(); |
| |
| bool destroy_complex_request = false; |
| EXPECT_TRUE(universal_mach_exc_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; |
| UniversalMachExcServer universal_mach_exc_server(&server); |
| |
| std::set<mach_msg_id_t> ids = |
| universal_mach_exc_server.MachMessageServerRequestIDs(); |
| EXPECT_NE(ids.find(2405), ids.end()); // There is no constant for this. |
| |
| MachExceptionRaiseRequest request; |
| EXPECT_LE(request.Head.msgh_size, |
| universal_mach_exc_server.MachMessageServerRequestSize()); |
| |
| MachExceptionRaiseReply reply; |
| EXPECT_LE(sizeof(reply), |
| universal_mach_exc_server.MachMessageServerReplySize()); |
| |
| constexpr exception_behavior_t kExceptionBehavior = |
| EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES; |
| |
| EXPECT_CALL( |
| server, |
| MockCatchMachException(kExceptionBehavior, |
| kServerLocalPort, |
| kExceptionThreadPort, |
| kExceptionTaskPort, |
| kExceptionType, |
| AreExceptionCodes(kTestMachExceptionCodes[0], |
| kTestMachExceptionCodes[1]), |
| Pointee(Eq(THREAD_STATE_NONE)), |
| IsThreadStateAndCount(0u), |
| IsThreadStateAndCount(0u), |
| Eq(&request.trailer))) |
| .WillOnce(Return(KERN_SUCCESS)) |
| .RetiresOnSaturation(); |
| |
| bool destroy_complex_request = false; |
| EXPECT_TRUE(universal_mach_exc_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; |
| UniversalMachExcServer universal_mach_exc_server(&server); |
| |
| std::set<mach_msg_id_t> ids = |
| universal_mach_exc_server.MachMessageServerRequestIDs(); |
| EXPECT_NE(ids.find(2406), ids.end()); // There is no constant for this. |
| |
| MachExceptionRaiseStateRequest request; |
| EXPECT_LE(request.Head.msgh_size, |
| universal_mach_exc_server.MachMessageServerRequestSize()); |
| |
| MachExceptionRaiseStateReply reply; |
| EXPECT_LE(sizeof(reply), |
| universal_mach_exc_server.MachMessageServerReplySize()); |
| |
| constexpr exception_behavior_t kExceptionBehavior = |
| EXCEPTION_STATE | MACH_EXCEPTION_CODES; |
| |
| EXPECT_CALL( |
| server, |
| MockCatchMachException(kExceptionBehavior, |
| kServerLocalPort, |
| THREAD_NULL, |
| TASK_NULL, |
| kExceptionType, |
| AreExceptionCodes(kTestMachExceptionCodes[0], |
| kTestMachExceptionCodes[1]), |
| Pointee(Eq(kThreadStateFlavor)), |
| IsThreadStateAndCount(kThreadStateFlavorCount), |
| IsThreadStateAndCount(std::size(reply.new_state)), |
| Eq(request.Trailer()))) |
| .WillOnce(Return(KERN_SUCCESS)) |
| .RetiresOnSaturation(); |
| |
| bool destroy_complex_request = false; |
| EXPECT_TRUE(universal_mach_exc_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; |
| UniversalMachExcServer universal_mach_exc_server(&server); |
| |
| std::set<mach_msg_id_t> ids = |
| universal_mach_exc_server.MachMessageServerRequestIDs(); |
| EXPECT_NE(ids.find(2407), ids.end()); // There is no constant for this. |
| |
| MachExceptionRaiseStateIdentityRequest request; |
| EXPECT_LE(request.Head.msgh_size, |
| universal_mach_exc_server.MachMessageServerRequestSize()); |
| |
| MachExceptionRaiseStateIdentityReply reply; |
| EXPECT_LE(sizeof(reply), |
| universal_mach_exc_server.MachMessageServerReplySize()); |
| |
| constexpr exception_behavior_t kExceptionBehavior = |
| EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES; |
| |
| EXPECT_CALL( |
| server, |
| MockCatchMachException(kExceptionBehavior, |
| kServerLocalPort, |
| kExceptionThreadPort, |
| kExceptionTaskPort, |
| kExceptionType, |
| AreExceptionCodes(kTestMachExceptionCodes[0], |
| kTestMachExceptionCodes[1]), |
| Pointee(Eq(kThreadStateFlavor)), |
| IsThreadStateAndCount(kThreadStateFlavorCount), |
| IsThreadStateAndCount(std::size(reply.new_state)), |
| Eq(request.Trailer()))) |
| .WillOnce(Return(KERN_SUCCESS)) |
| .RetiresOnSaturation(); |
| |
| bool destroy_complex_request = false; |
| EXPECT_TRUE(universal_mach_exc_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; |
| UniversalMachExcServer universal_mach_exc_server(&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. |
| |
| static constexpr 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 < std::size(unknown_ids); ++index) { |
| mach_msg_id_t id = unknown_ids[index]; |
| |
| SCOPED_TRACE(base::StringPrintf("unknown id %d", id)); |
| |
| std::set<mach_msg_id_t> ids = |
| universal_mach_exc_server.MachMessageServerRequestIDs(); |
| EXPECT_EQ(ids.find(id), ids.end()); |
| |
| InvalidRequest request(id); |
| EXPECT_LE(sizeof(request), |
| universal_mach_exc_server.MachMessageServerRequestSize()); |
| |
| BadIDErrorReply reply; |
| EXPECT_LE(sizeof(reply), |
| universal_mach_exc_server.MachMessageServerReplySize()); |
| |
| bool destroy_complex_request = false; |
| EXPECT_FALSE(universal_mach_exc_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); |
| } |
| } |
| |
| TEST(ExcServerVariants, MachMessageServerRequestIDs) { |
| std::set<mach_msg_id_t> expect_request_ids; |
| |
| // There are no constants for these. |
| expect_request_ids.insert(2401); |
| expect_request_ids.insert(2402); |
| expect_request_ids.insert(2403); |
| expect_request_ids.insert(2405); |
| expect_request_ids.insert(2406); |
| expect_request_ids.insert(2407); |
| |
| MockUniversalMachExcServer server; |
| UniversalMachExcServer universal_mach_exc_server(&server); |
| |
| EXPECT_EQ(universal_mach_exc_server.MachMessageServerRequestIDs(), |
| expect_request_ids); |
| } |
| |
| #if BUILDFLAG(IS_MAC) |
| |
| class TestExcServerVariants : public MachMultiprocess, |
| public UniversalMachExcServer::Interface { |
| public: |
| TestExcServerVariants(exception_behavior_t behavior, |
| thread_state_flavor_t flavor, |
| mach_msg_type_number_t state_count) |
| : MachMultiprocess(), |
| UniversalMachExcServer::Interface(), |
| behavior_(behavior), |
| flavor_(flavor), |
| state_count_(state_count), |
| handled_(false) { |
| SetExpectedChildTerminationBuiltinTrap(); |
| } |
| |
| TestExcServerVariants(const TestExcServerVariants&) = delete; |
| TestExcServerVariants& operator=(const TestExcServerVariants&) = delete; |
| |
| // UniversalMachExcServer::Interface: |
| |
| 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, |
| ConstThreadState old_state, |
| mach_msg_type_number_t old_state_count, |
| thread_state_t new_state, |
| mach_msg_type_number_t* new_state_count, |
| const mach_msg_trailer_t* trailer, |
| bool* destroy_complex_request) override { |
| *destroy_complex_request = true; |
| |
| EXPECT_FALSE(handled_); |
| handled_ = true; |
| |
| EXPECT_EQ(behavior, behavior_); |
| |
| EXPECT_EQ(exception_port, LocalPort()); |
| |
| if (ExceptionBehaviorHasIdentity(behavior)) { |
| EXPECT_NE(thread, THREAD_NULL); |
| EXPECT_EQ(task, ChildTask()); |
| } else { |
| EXPECT_EQ(thread, THREAD_NULL); |
| EXPECT_EQ(task, TASK_NULL); |
| } |
| |
| EXPECT_EQ(exception, EXC_CRASH); |
| EXPECT_EQ(code_count, 2u); |
| |
| // The exception and code_count checks above would ideally use ASSERT_EQ so |
| // that the next conditional would not be necessary, but ASSERT_* requires a |
| // function returning type void, and the interface dictates otherwise here. |
| if (exception == EXC_CRASH && code_count >= 1) { |
| int signal; |
| ExcCrashRecoverOriginalException(code[0], nullptr, &signal); |
| } |
| |
| const bool has_state = ExceptionBehaviorHasState(behavior); |
| if (has_state) { |
| EXPECT_EQ(*flavor, flavor_); |
| EXPECT_EQ(old_state_count, state_count_); |
| EXPECT_NE(old_state, nullptr); |
| EXPECT_EQ(*new_state_count, |
| implicit_cast<mach_msg_type_number_t>(THREAD_STATE_MAX)); |
| EXPECT_NE(new_state, nullptr); |
| } else { |
| EXPECT_EQ(*flavor, THREAD_STATE_NONE); |
| EXPECT_EQ(old_state_count, 0u); |
| EXPECT_EQ(old_state, nullptr); |
| EXPECT_EQ(*new_state_count, 0u); |
| EXPECT_EQ(new_state, nullptr); |
| } |
| |
| EXPECT_EQ( |
| trailer->msgh_trailer_type, |
| implicit_cast<mach_msg_trailer_type_t>(MACH_MSG_TRAILER_FORMAT_0)); |
| EXPECT_EQ(trailer->msgh_trailer_size, |
| REQUESTED_TRAILER_SIZE(kMachMessageOptions)); |
| |
| ExcServerCopyState( |
| behavior, old_state, old_state_count, new_state, new_state_count); |
| |
| return ExcServerSuccessfulReturnValue(exception, behavior, false); |
| } |
| |
| private: |
| // MachMultiprocess: |
| |
| void MachMultiprocessParent() override { |
| UniversalMachExcServer universal_mach_exc_server(this); |
| |
| kern_return_t kr = |
| MachMessageServer::Run(&universal_mach_exc_server, |
| LocalPort(), |
| kMachMessageOptions, |
| MachMessageServer::kOneShot, |
| MachMessageServer::kReceiveLargeError, |
| kMachMessageTimeoutWaitIndefinitely); |
| EXPECT_EQ(kr, KERN_SUCCESS) |
| << MachErrorMessage(kr, "MachMessageServer::Run"); |
| |
| EXPECT_TRUE(handled_); |
| } |
| |
| 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(kr, KERN_SUCCESS) |
| << 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_; |
| |
| static const mach_msg_option_t kMachMessageOptions = |
| MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0); |
| }; |
| |
| 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. |
| |
| static constexpr struct { |
| thread_state_flavor_t flavor; |
| mach_msg_type_number_t count; |
| } test_data[] = { |
| #if defined(ARCH_CPU_X86_FAMILY) |
| // For the x86 family, exception handlers can only properly receive the |
| // thread, float, and exception state flavors. There’s a bug in the kernel |
| // that causes it to call thread_getstatus() (a wrapper for the more |
| // familiar thread_get_state()) with an incorrect state buffer size |
| // parameter when delivering an exception. 10.9.4 |
| // xnu-2422.110.17/osfmk/kern/exception.c exception_deliver() uses the |
| // _MachineStateCount[] array indexed by the flavor number to obtain the |
| // buffer size. 10.9.4 xnu-2422.110.17/osfmk/i386/pcb.c contains the |
| // definition of this array for the x86 family. The slots corresponding to |
| // thread, float, and exception state flavors in both native-width (32- and |
| // 64-bit) and universal are correct, but the remaining elements in the |
| // array are not. This includes elements that would correspond to debug and |
| // AVX state flavors, so these cannot be tested here. |
| // |
| // When machine_thread_get_state() (the machine-specific implementation of |
| // thread_get_state()) encounters an undersized buffer as reported by the |
| // buffer size parameter, it returns KERN_INVALID_ARGUMENT, which causes |
| // exception_deliver() to not actually deliver the exception and instead |
| // return that error code to exception_triage() as well. |
| // |
| // This bug is filed as radar 18312067. |
| // |
| // Additionaly, the AVX state flavors are also not tested because they’re |
| // not available on all CPUs and OS versions. |
| {x86_THREAD_STATE, x86_THREAD_STATE_COUNT}, |
| {x86_FLOAT_STATE, x86_FLOAT_STATE_COUNT}, |
| {x86_EXCEPTION_STATE, x86_EXCEPTION_STATE_COUNT}, |
| #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}, |
| #elif 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}, |
| #endif |
| #elif defined(ARCH_CPU_ARM64) |
| {ARM_UNIFIED_THREAD_STATE, ARM_UNIFIED_THREAD_STATE_COUNT}, |
| {ARM_THREAD_STATE64, ARM_THREAD_STATE64_COUNT}, |
| {ARM_NEON_STATE64, ARM_NEON_STATE64_COUNT}, |
| {ARM_EXCEPTION_STATE64, ARM_EXCEPTION_STATE64_COUNT}, |
| #else |
| #error Port this test to your CPU architecture. |
| #endif |
| }; |
| |
| for (size_t index = 0; index < std::size(test_data); ++index) { |
| const auto& 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(); |
| } |
| } |
| |
| #endif // BUILDFLAG(IS_MAC) |
| |
| TEST(ExcServerVariants, ExcServerSuccessfulReturnValue) { |
| #if BUILDFLAG(IS_IOS) |
| // iOS 9 ≅ OS X 10.11. |
| const kern_return_t prefer_not_set_thread_state = KERN_SUCCESS; |
| #else |
| const kern_return_t prefer_not_set_thread_state = |
| MacOSVersionNumber() < 10'11'00 ? MACH_RCV_PORT_DIED : KERN_SUCCESS; |
| #endif |
| |
| const struct { |
| exception_type_t exception; |
| exception_behavior_t behavior; |
| bool set_thread_state; |
| kern_return_t kr; |
| } kTestData[] = { |
| {EXC_CRASH, EXCEPTION_DEFAULT, false, KERN_SUCCESS}, |
| {EXC_CRASH, EXCEPTION_STATE, false, prefer_not_set_thread_state}, |
| {EXC_CRASH, EXCEPTION_STATE_IDENTITY, false, prefer_not_set_thread_state}, |
| {EXC_CRASH, kMachExceptionCodes | EXCEPTION_DEFAULT, false, KERN_SUCCESS}, |
| {EXC_CRASH, |
| kMachExceptionCodes | EXCEPTION_STATE, |
| false, |
| prefer_not_set_thread_state}, |
| {EXC_CRASH, |
| kMachExceptionCodes | EXCEPTION_STATE_IDENTITY, |
| false, |
| prefer_not_set_thread_state}, |
| {EXC_CRASH, EXCEPTION_DEFAULT, true, KERN_SUCCESS}, |
| {EXC_CRASH, EXCEPTION_STATE, true, KERN_SUCCESS}, |
| {EXC_CRASH, EXCEPTION_STATE_IDENTITY, true, KERN_SUCCESS}, |
| {EXC_CRASH, kMachExceptionCodes | EXCEPTION_DEFAULT, true, KERN_SUCCESS}, |
| {EXC_CRASH, kMachExceptionCodes | EXCEPTION_STATE, true, KERN_SUCCESS}, |
| {EXC_CRASH, |
| kMachExceptionCodes | EXCEPTION_STATE_IDENTITY, |
| true, |
| KERN_SUCCESS}, |
| {EXC_BAD_ACCESS, EXCEPTION_DEFAULT, false, KERN_SUCCESS}, |
| {EXC_BAD_INSTRUCTION, EXCEPTION_STATE, false, MACH_RCV_PORT_DIED}, |
| {EXC_ARITHMETIC, EXCEPTION_STATE_IDENTITY, false, MACH_RCV_PORT_DIED}, |
| {EXC_EMULATION, |
| kMachExceptionCodes | EXCEPTION_DEFAULT, |
| false, |
| KERN_SUCCESS}, |
| {EXC_SOFTWARE, |
| kMachExceptionCodes | EXCEPTION_STATE, |
| false, |
| MACH_RCV_PORT_DIED}, |
| {EXC_BREAKPOINT, |
| kMachExceptionCodes | EXCEPTION_STATE_IDENTITY, |
| false, |
| MACH_RCV_PORT_DIED}, |
| {EXC_SYSCALL, EXCEPTION_DEFAULT, true, KERN_SUCCESS}, |
| {EXC_MACH_SYSCALL, EXCEPTION_STATE, true, KERN_SUCCESS}, |
| {EXC_RPC_ALERT, EXCEPTION_STATE_IDENTITY, true, KERN_SUCCESS}, |
| {EXC_RESOURCE, |
| kMachExceptionCodes | EXCEPTION_DEFAULT, |
| true, |
| KERN_SUCCESS}, |
| {EXC_GUARD, kMachExceptionCodes | EXCEPTION_STATE, true, KERN_SUCCESS}, |
| {EXC_CORPSE_NOTIFY, |
| kMachExceptionCodes | EXCEPTION_STATE_IDENTITY, |
| true, |
| KERN_SUCCESS}, |
| }; |
| |
| for (size_t index = 0; index < std::size(kTestData); ++index) { |
| const auto& test_data = kTestData[index]; |
| SCOPED_TRACE( |
| base::StringPrintf("index %zu, behavior %d, set_thread_state %s", |
| index, |
| test_data.behavior, |
| test_data.set_thread_state ? "true" : "false")); |
| |
| EXPECT_EQ(ExcServerSuccessfulReturnValue(test_data.exception, |
| test_data.behavior, |
| test_data.set_thread_state), |
| test_data.kr); |
| } |
| } |
| |
| TEST(ExcServerVariants, ExcServerCopyState) { |
| static constexpr natural_t old_state[] = {1, 2, 3, 4, 5}; |
| natural_t new_state[10] = {}; |
| |
| constexpr mach_msg_type_number_t old_state_count = std::size(old_state); |
| mach_msg_type_number_t new_state_count = std::size(new_state); |
| |
| // EXCEPTION_DEFAULT (with or without MACH_EXCEPTION_CODES) is not |
| // state-carrying. new_state and new_state_count should be untouched. |
| ExcServerCopyState(EXCEPTION_DEFAULT, |
| old_state, |
| old_state_count, |
| new_state, |
| &new_state_count); |
| EXPECT_EQ(new_state_count, std::size(new_state)); |
| for (size_t i = 0; i < std::size(new_state); ++i) { |
| EXPECT_EQ(new_state[i], 0u) << "i " << i; |
| } |
| |
| ExcServerCopyState(MACH_EXCEPTION_CODES | EXCEPTION_DEFAULT, |
| old_state, |
| old_state_count, |
| new_state, |
| &new_state_count); |
| EXPECT_EQ(new_state_count, std::size(new_state)); |
| for (size_t i = 0; i < std::size(new_state); ++i) { |
| EXPECT_EQ(new_state[i], 0u) << "i " << i; |
| } |
| |
| // This is a state-carrying exception where old_state_count is small. |
| mach_msg_type_number_t copy_limit = 2; |
| ExcServerCopyState( |
| EXCEPTION_STATE, old_state, copy_limit, new_state, &new_state_count); |
| EXPECT_EQ(new_state_count, copy_limit); |
| for (size_t i = 0; i < copy_limit; ++i) { |
| EXPECT_EQ(new_state[i], old_state[i]) << "i " << i; |
| } |
| for (size_t i = copy_limit; i < std::size(new_state); ++i) { |
| EXPECT_EQ(new_state[i], 0u) << "i " << i; |
| } |
| |
| // This is a state-carrying exception where new_state_count is small. |
| copy_limit = 3; |
| new_state_count = copy_limit; |
| ExcServerCopyState(EXCEPTION_STATE_IDENTITY, |
| old_state, |
| old_state_count, |
| new_state, |
| &new_state_count); |
| EXPECT_EQ(new_state_count, copy_limit); |
| for (size_t i = 0; i < copy_limit; ++i) { |
| EXPECT_EQ(new_state[i], old_state[i]) << "i " << i; |
| } |
| for (size_t i = copy_limit; i < std::size(new_state); ++i) { |
| EXPECT_EQ(new_state[i], 0u) << "i " << i; |
| } |
| |
| // This is a state-carrying exception where all of old_state is copied to |
| // new_state, which is large enough to receive it and then some. |
| new_state_count = std::size(new_state); |
| ExcServerCopyState(MACH_EXCEPTION_CODES | EXCEPTION_STATE_IDENTITY, |
| old_state, |
| old_state_count, |
| new_state, |
| &new_state_count); |
| EXPECT_EQ(new_state_count, old_state_count); |
| for (size_t i = 0; i < std::size(old_state); ++i) { |
| EXPECT_EQ(new_state[i], old_state[i]) << "i " << i; |
| } |
| for (size_t i = std::size(old_state); i < std::size(new_state); ++i) { |
| EXPECT_EQ(new_state[i], 0u) << "i " << i; |
| } |
| } |
| |
| } // namespace |
| } // namespace test |
| } // namespace crashpad |