| // 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_client_variants.h" |
| |
| #include <mach/mach.h> |
| #include <pthread.h> |
| #include <string.h> |
| #include <sys/types.h> |
| |
| #include <iterator> |
| |
| #include "base/strings/stringprintf.h" |
| #include "gtest/gtest.h" |
| #include "test/mac/mach_errors.h" |
| #include "test/mac/mach_multiprocess.h" |
| #include "util/mach/exc_server_variants.h" |
| #include "util/mach/exception_behaviors.h" |
| #include "util/mach/mach_extensions.h" |
| #include "util/mach/mach_message.h" |
| #include "util/mach/mach_message_server.h" |
| #include "util/misc/implicit_cast.h" |
| |
| namespace crashpad { |
| namespace test { |
| namespace { |
| |
| class TestExcClientVariants : public MachMultiprocess, |
| public UniversalMachExcServer::Interface { |
| public: |
| TestExcClientVariants(exception_behavior_t behavior, bool all_fields) |
| : MachMultiprocess(), |
| UniversalMachExcServer::Interface(), |
| behavior_(behavior), |
| all_fields_(all_fields), |
| handled_(false) { |
| ++exception_; |
| ++exception_code_; |
| ++exception_subcode_; |
| } |
| |
| TestExcClientVariants(const TestExcClientVariants&) = delete; |
| TestExcClientVariants& operator=(const TestExcClientVariants&) = 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 (HasIdentity()) { |
| EXPECT_NE(thread, THREAD_NULL); |
| EXPECT_EQ(task, ChildTask()); |
| } else { |
| EXPECT_EQ(thread, THREAD_NULL); |
| EXPECT_EQ(task, TASK_NULL); |
| } |
| |
| mach_exception_code_t expect_code = exception_code_; |
| mach_exception_subcode_t expect_subcode = exception_subcode_; |
| if ((behavior & MACH_EXCEPTION_CODES) == 0) { |
| expect_code = implicit_cast<exception_data_type_t>(expect_code); |
| expect_subcode = implicit_cast<exception_data_type_t>(expect_subcode); |
| } |
| |
| EXPECT_EQ(exception, exception_); |
| EXPECT_EQ(code_count, 2u); |
| |
| // The code_count check above would ideally use ASSERT_EQ so that the next |
| // conditionals would not be necessary, but ASSERT_* requires a function |
| // returning type void, and the interface dictates otherwise here. |
| if (code_count >= 1) { |
| EXPECT_EQ(code[0], expect_code); |
| } |
| if (code_count >= 2) { |
| EXPECT_EQ(code[1], expect_subcode); |
| } |
| |
| if (HasState()) { |
| EXPECT_EQ(*flavor, exception_ + 10); |
| EXPECT_EQ(old_state_count, MACHINE_THREAD_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); |
| |
| for (size_t index = 0; index < old_state_count; ++index) { |
| EXPECT_EQ(old_state[index], index); |
| } |
| |
| // Use a flavor known to be different from the incoming flavor, for a test |
| // of the “out” side of the in-out flavor parameter. |
| *flavor = exception_ + 20; |
| *new_state_count = MACHINE_THREAD_STATE_COUNT; |
| |
| // Send a new state back to the client. |
| for (size_t index = 0; index < *new_state_count; ++index) { |
| new_state[index] = MACHINE_THREAD_STATE_COUNT - index; |
| } |
| } 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); |
| } |
| |
| return KERN_SUCCESS; |
| } |
| |
| private: |
| // MachMultiprocess: |
| |
| void MachMultiprocessParent() override { |
| UniversalMachExcServer universal_mach_exc_server(this); |
| |
| kern_return_t kr = |
| MachMessageServer::Run(&universal_mach_exc_server, |
| LocalPort(), |
| MACH_MSG_OPTION_NONE, |
| MachMessageServer::kOneShot, |
| MachMessageServer::kReceiveLargeError, |
| kMachMessageTimeoutWaitIndefinitely); |
| EXPECT_EQ(kr, KERN_SUCCESS) |
| << MachErrorMessage(kr, "MachMessageServer::Run"); |
| |
| EXPECT_TRUE(handled_); |
| } |
| |
| void MachMultiprocessChild() override { |
| const exception_type_t exception = exception_; |
| const mach_exception_data_type_t code[] = { |
| exception_code_, |
| exception_subcode_ |
| }; |
| |
| thread_t thread = THREAD_NULL; |
| task_t task = TASK_NULL; |
| if (all_fields_ || HasIdentity()) { |
| thread = MachThreadSelf(); |
| task = mach_task_self(); |
| } |
| |
| thread_state_flavor_t flavor; |
| thread_state_flavor_t* flavor_p = nullptr; |
| natural_t old_state[MACHINE_THREAD_STATE_COUNT]; |
| thread_state_t old_state_p = nullptr; |
| mach_msg_type_number_t old_state_count = 0; |
| natural_t new_state[THREAD_STATE_MAX]; |
| thread_state_t new_state_p = nullptr; |
| mach_msg_type_number_t new_state_count; |
| mach_msg_type_number_t* new_state_count_p = nullptr; |
| if (all_fields_ || HasState()) { |
| // Pick a different flavor each time based on the value of exception_. |
| // These aren’t real flavors, it’s just for testing. |
| flavor = exception_ + 10; |
| flavor_p = &flavor; |
| for (size_t index = 0; index < std::size(old_state); ++index) { |
| old_state[index] = index; |
| } |
| old_state_p = reinterpret_cast<thread_state_t>(&old_state); |
| old_state_count = std::size(old_state); |
| |
| // new_state and new_state_count are out parameters that the server should |
| // never see or use, so set them to bogus values. The call to the server |
| // should overwrite these. |
| memset(new_state, 0xa5, sizeof(new_state)); |
| new_state_p = reinterpret_cast<thread_state_t>(&new_state); |
| new_state_count = 0x5a; |
| new_state_count_p = &new_state_count; |
| } |
| |
| EXPECT_EQ(UniversalExceptionRaise(behavior_, |
| RemotePort(), |
| thread, |
| task, |
| exception, |
| code, |
| std::size(code), |
| flavor_p, |
| old_state_p, |
| old_state_count, |
| new_state_p, |
| new_state_count_p), |
| KERN_SUCCESS); |
| |
| if (HasState()) { |
| // Verify the out parameters. |
| |
| EXPECT_EQ(flavor, exception_ + 20); |
| EXPECT_EQ(new_state_count, MACHINE_THREAD_STATE_COUNT); |
| |
| for (size_t index = 0; index < new_state_count; ++index) { |
| EXPECT_EQ(new_state[index], MACHINE_THREAD_STATE_COUNT - index); |
| } |
| } |
| } |
| |
| bool HasIdentity() const { |
| return ExceptionBehaviorHasIdentity(behavior_); |
| } |
| |
| bool HasState() const { |
| return ExceptionBehaviorHasState(behavior_); |
| } |
| |
| // The behavior to test. |
| exception_behavior_t behavior_; |
| |
| // If false, only fields required for the current value of behavior_ are set |
| // in a call to UniversalExceptionRaise(). The thread and task fields are only |
| // set for identity-carrying behaviors, and the flavor and state fields are |
| // only set for state-carrying behaviors. If true, all fields are set |
| // regardless of the behavior. Testing in both ways verifies that |
| // UniversalExceptionRaise() can tolerate the null arguments documented as |
| // usable when the behavior allows it, and that it ignores these arguments |
| // even when set when the behavior does not make use of them. |
| bool all_fields_; |
| |
| // true if an exception message was handled. |
| bool handled_; |
| |
| // These fields will increment for each instantiation of the test class. |
| static exception_type_t exception_; |
| static mach_exception_code_t exception_code_; |
| static mach_exception_subcode_t exception_subcode_; |
| }; |
| |
| exception_type_t TestExcClientVariants::exception_ = 0; |
| |
| // exception_code_ and exception_subcode_ are always large enough to require |
| // 64 bits, so that when the 32-bit-only variants not using MACH_EXCEPITON_CODES |
| // are tested, the code and subcode fields can be checked for proper truncation. |
| mach_exception_code_t TestExcClientVariants::exception_code_ = 0x100000000; |
| mach_exception_subcode_t TestExcClientVariants::exception_subcode_ = |
| 0xffffffff00000000; |
| |
| TEST(ExcClientVariants, UniversalExceptionRaise) { |
| static constexpr exception_behavior_t kBehaviors[] = { |
| EXCEPTION_DEFAULT, |
| EXCEPTION_STATE, |
| EXCEPTION_STATE_IDENTITY, |
| kMachExceptionCodes | EXCEPTION_DEFAULT, |
| kMachExceptionCodes | EXCEPTION_STATE, |
| kMachExceptionCodes | EXCEPTION_STATE_IDENTITY, |
| }; |
| |
| for (size_t index = 0; index < std::size(kBehaviors); ++index) { |
| exception_behavior_t behavior = kBehaviors[index]; |
| SCOPED_TRACE(base::StringPrintf("index %zu, behavior %d", index, behavior)); |
| |
| { |
| SCOPED_TRACE("all_fields = false"); |
| |
| TestExcClientVariants test_exc_client_variants(behavior, false); |
| test_exc_client_variants.Run(); |
| } |
| |
| { |
| SCOPED_TRACE("all_fields = true"); |
| |
| TestExcClientVariants test_exc_client_variants(behavior, true); |
| test_exc_client_variants.Run(); |
| } |
| } |
| } |
| |
| } // namespace |
| } // namespace test |
| } // namespace crashpad |