Index: util/mach/exc_client_variants_test.cc |
diff --git a/util/mach/exc_client_variants_test.cc b/util/mach/exc_client_variants_test.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..9948f4d8840b0008baf1eb13ec58a095887ee485 |
--- /dev/null |
+++ b/util/mach/exc_client_variants_test.cc |
@@ -0,0 +1,288 @@ |
+// 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_client_variants.h" |
+ |
+#include <mach/mach.h> |
+#include <pthread.h> |
+#include <string.h> |
+ |
+#include "base/basictypes.h" |
+#include "base/strings/stringprintf.h" |
+#include "gtest/gtest.h" |
+#include "util/mach/exc_server_variants.h" |
+#include "util/mach/mach_extensions.h" |
+#include "util/test/mac/mach_errors.h" |
+#include "util/test/mac/mach_multiprocess.h" |
+ |
+namespace { |
+ |
+using namespace crashpad; |
+using namespace crashpad::test; |
+ |
+class TestExcClientVariants : public UniversalMachExcServer, |
+ public MachMultiprocess { |
+ public: |
+ TestExcClientVariants(exception_behavior_t behavior, bool all_fields) |
+ : UniversalMachExcServer(), |
+ MachMultiprocess(), |
+ behavior_(behavior), |
+ all_fields_(all_fields), |
+ handled_(false) { |
+ ++exception_; |
+ ++exception_code_; |
+ ++exception_subcode_; |
+ } |
+ |
+ // 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); |
+ EXPECT_EQ(LocalPort(), exception_port); |
+ |
+ if (HasIdentity()) { |
+ EXPECT_NE(kMachPortNull, thread); |
+ EXPECT_EQ(ChildTask(), task); |
+ } else { |
+ EXPECT_EQ(kMachPortNull, thread); |
+ EXPECT_EQ(kMachPortNull, task); |
+ } |
+ |
+ mach_exception_code_t expect_code = exception_code_; |
+ mach_exception_subcode_t expect_subcode = exception_subcode_; |
+ if ((behavior & MACH_EXCEPTION_CODES) == 0) { |
+ expect_code = static_cast<exception_data_type_t>(expect_code); |
+ expect_subcode = static_cast<exception_data_type_t>(expect_subcode); |
+ } |
+ |
+ EXPECT_EQ(exception_, exception); |
+ EXPECT_EQ(2u, code_count); |
+ EXPECT_EQ(expect_code, code[0]); |
+ EXPECT_EQ(expect_subcode, code[1]); |
+ |
+ if (HasState()) { |
+ EXPECT_EQ(exception_ + 10, *flavor); |
+ EXPECT_EQ(MACHINE_THREAD_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<natural_t*>(NULL), new_state); |
+ |
+ for (size_t index = 0; index < old_state_count; ++index) { |
+ EXPECT_EQ(index, old_state[index]); |
+ } |
+ |
+ // Use a flavor known to be different from the incoming flavor, for a test |
+ // of the “out” side of the inout 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) { |
+ EXPECT_EQ(0u, new_state[index]); |
+ new_state[index] = MACHINE_THREAD_STATE_COUNT - index; |
+ } |
+ } 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); |
+ } |
+ |
+ return KERN_SUCCESS; |
+ } |
+ |
+ 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 { |
+ const exception_type_t exception = exception_; |
+ const mach_exception_data_type_t code[] = { |
+ exception_code_, |
+ exception_subcode_ |
+ }; |
+ |
+ thread_t thread = MACH_PORT_NULL; |
+ task_t task = MACH_PORT_NULL; |
+ if (all_fields_ || HasIdentity()) { |
+ thread = MachThreadSelf(); |
+ task = mach_task_self(); |
+ } |
+ |
+ thread_state_flavor_t flavor; |
+ thread_state_flavor_t* flavor_p = NULL; |
+ natural_t old_state[MACHINE_THREAD_STATE_COUNT]; |
+ thread_state_t old_state_p = NULL; |
+ mach_msg_type_number_t old_state_count = 0; |
+ natural_t new_state[THREAD_STATE_MAX]; |
+ thread_state_t new_state_p = NULL; |
+ mach_msg_type_number_t new_state_count; |
+ mach_msg_type_number_t* new_state_count_p = NULL; |
+ 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 < arraysize(old_state); ++index) { |
+ old_state[index] = index; |
+ } |
+ old_state_p = reinterpret_cast<thread_state_t>(&old_state); |
+ old_state_count = arraysize(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(KERN_SUCCESS, UniversalExceptionRaise(behavior_, |
+ RemotePort(), |
+ thread, |
+ task, |
+ exception, |
+ code, |
+ arraysize(code), |
+ flavor_p, |
+ old_state_p, |
+ old_state_count, |
+ new_state_p, |
+ new_state_count_p)); |
+ |
+ if (HasState()) { |
+ // Verify the out parameters. |
+ |
+ EXPECT_EQ(exception_ + 20, flavor); |
+ EXPECT_EQ(MACHINE_THREAD_STATE_COUNT, new_state_count); |
+ |
+ for (size_t index = 0; index < new_state_count; ++index) { |
+ EXPECT_EQ(MACHINE_THREAD_STATE_COUNT - index, new_state[index]); |
+ } |
+ } |
+ } |
+ |
+ exception_behavior_t BasicBehavior() const { |
+ return behavior_ & ~MACH_EXCEPTION_CODES; |
+ } |
+ |
+ bool HasIdentity() const { |
+ exception_behavior_t basic_behavior = BasicBehavior(); |
+ return basic_behavior == EXCEPTION_DEFAULT || |
+ basic_behavior == EXCEPTION_STATE_IDENTITY; |
+ } |
+ |
+ bool HasState() const { |
+ exception_behavior_t basic_behavior = BasicBehavior(); |
+ return basic_behavior == EXCEPTION_STATE || |
+ basic_behavior == EXCEPTION_STATE_IDENTITY; |
+ } |
+ |
+ // 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_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(TestExcClientVariants); |
+}; |
+ |
+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) { |
+ const 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 < arraysize(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 |