From 177f5dcddce4a38b0769b3d843f3d4d5c4dfd240 Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Wed, 10 Sep 2014 17:29:07 -0400 Subject: [PATCH] Add exc_server_variants including UniversalMachExcServer and its test. TEST=util_test ExcServerVariants.* R=rsesek@chromium.org Review URL: https://codereview.chromium.org/545053003 --- util/mach/bootstrap_test.cc | 7 +- util/mach/exc_server_variants.cc | 674 ++++++++++++++++ util/mach/exc_server_variants.h | 436 ++++++++++ util/mach/exc_server_variants_test.cc | 1072 +++++++++++++++++++++++++ util/mach/mach_extensions.h | 44 + util/mach/mach_message_server_test.cc | 13 +- util/test/mac/mach_multiprocess.cc | 7 +- util/util.gyp | 5 + 8 files changed, 2244 insertions(+), 14 deletions(-) create mode 100644 util/mach/exc_server_variants.cc create mode 100644 util/mach/exc_server_variants.h create mode 100644 util/mach/exc_server_variants_test.cc create mode 100644 util/mach/mach_extensions.h diff --git a/util/mach/bootstrap_test.cc b/util/mach/bootstrap_test.cc index fbf69cad..2b8afe0f 100644 --- a/util/mach/bootstrap_test.cc +++ b/util/mach/bootstrap_test.cc @@ -25,6 +25,7 @@ #include "base/mac/scoped_mach_port.h" #include "base/rand_util.h" #include "gtest/gtest.h" +#include "util/mach/mach_extensions.h" #include "util/test/mac/mach_errors.h" namespace { @@ -51,21 +52,21 @@ TEST(Bootstrap, BootstrapCheckIn) { kr = BootstrapCheckIn(bootstrap_port, service_name.c_str(), &server_port); ASSERT_EQ(BOOTSTRAP_SUCCESS, kr) << BootstrapErrorMessage(kr, "bootstrap_check_in"); - ASSERT_NE(static_cast(MACH_PORT_NULL), server_port); + ASSERT_NE(kMachPortNull, server_port); base::mac::ScopedMachReceiveRight server_port_owner(server_port); // A subsequent checkin attempt should fail. mach_port_t fail_port = MACH_PORT_NULL; kr = BootstrapCheckIn(bootstrap_port, service_name.c_str(), &fail_port); EXPECT_EQ(BOOTSTRAP_SERVICE_ACTIVE, kr); - EXPECT_EQ(static_cast(MACH_PORT_NULL), fail_port); + EXPECT_EQ(kMachPortNull, fail_port); // Look up the service, getting a send right. kr = bootstrap_look_up(bootstrap_port, service_name.c_str(), &client_port); ASSERT_EQ(BOOTSTRAP_SUCCESS, kr) << BootstrapErrorMessage(kr, "bootstrap_look_up"); base::mac::ScopedMachSendRight client_port_owner(client_port); - EXPECT_NE(static_cast(MACH_PORT_NULL), client_port); + EXPECT_NE(kMachPortNull, client_port); // Have the “client” send a message to the “server”. struct SendMessage { diff --git a/util/mach/exc_server_variants.cc b/util/mach/exc_server_variants.cc new file mode 100644 index 00000000..5ecd05d5 --- /dev/null +++ b/util/mach/exc_server_variants.cc @@ -0,0 +1,674 @@ +// 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 +#include + +#include "base/basictypes.h" +#include "base/logging.h" +#include "util/mach/exc.h" +#include "util/mach/excServer.h" +#include "util/mach/mach_exc.h" +#include "util/mach/mach_excServer.h" + +extern "C" { + +// These six functions are not used, and are in fact obsoleted by the other +// functionality implemented in this file. The standard MIG-generated exc_server +// (in excServer.c) and mach_exc_server (in mach_excServer.c) server dispatch +// routines usable with the standard mach_msg_server() function call out to +// these functions. exc_server() and mach_exc_server() are unused and are +// replaced by the more flexible ExcServer and MachExcServer, but the linker +// still needs to see these six function definitions. + +kern_return_t catch_exception_raise(exception_handler_t exception_port, + thread_t thread, + task_t task, + exception_type_t exception, + exception_data_t code, + mach_msg_type_number_t code_count) { + NOTREACHED(); + return KERN_FAILURE; +} + +kern_return_t catch_exception_raise_state( + exception_handler_t exception_port, + exception_type_t exception, + exception_data_t code, + mach_msg_type_number_t code_count, + thread_state_flavor_t* flavor, + thread_state_t old_state, + mach_msg_type_number_t old_state_count, + thread_state_t new_state, + mach_msg_type_number_t* new_state_count) { + NOTREACHED(); + return KERN_FAILURE; +} + +kern_return_t catch_exception_raise_state_identity( + exception_handler_t exception_port, + thread_t thread, + task_t task, + exception_type_t exception, + exception_data_t code, + mach_msg_type_number_t code_count, + thread_state_flavor_t* flavor, + thread_state_t old_state, + mach_msg_type_number_t old_state_count, + thread_state_t new_state, + mach_msg_type_number_t* new_state_count) { + NOTREACHED(); + return KERN_FAILURE; +} + +kern_return_t catch_mach_exception_raise(exception_handler_t exception_port, + thread_t thread, + task_t task, + exception_type_t exception, + mach_exception_data_t code, + mach_msg_type_number_t code_count) { + NOTREACHED(); + return KERN_FAILURE; +} + +kern_return_t catch_mach_exception_raise_state( + exception_handler_t exception_port, + exception_type_t exception, + mach_exception_data_t code, + mach_msg_type_number_t code_count, + thread_state_flavor_t* flavor, + thread_state_t old_state, + mach_msg_type_number_t old_state_count, + thread_state_t new_state, + mach_msg_type_number_t* new_state_count) { + NOTREACHED(); + return KERN_FAILURE; +} + +kern_return_t catch_mach_exception_raise_state_identity( + exception_handler_t exception_port, + thread_t thread, + task_t task, + exception_type_t exception, + mach_exception_data_t code, + mach_msg_type_number_t code_count, + thread_state_flavor_t* flavor, + thread_state_t old_state, + mach_msg_type_number_t old_state_count, + thread_state_t new_state, + mach_msg_type_number_t* new_state_count) { + NOTREACHED(); + return KERN_FAILURE; +} + +} // extern "C" + +namespace { + +void PrepareReplyFromRequest(const mach_msg_header_t* in_header, + mach_msg_header_t* out_header) { + out_header->msgh_bits = + MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(in_header->msgh_bits), 0); + out_header->msgh_remote_port = in_header->msgh_remote_port; + out_header->msgh_size = sizeof(mig_reply_error_t); + out_header->msgh_local_port = MACH_PORT_NULL; + out_header->msgh_id = in_header->msgh_id + 100; + reinterpret_cast(out_header)->NDR = NDR_record; +} + +void SetReplyError(mach_msg_header_t* out_header, kern_return_t error) { + reinterpret_cast(out_header)->RetCode = error; +} + +// There are no predefined constants for these. +enum MachMessageID : mach_msg_id_t { + kMachMessageIDExceptionRaise = 2401, + kMachMessageIDExceptionRaiseState = 2402, + kMachMessageIDExceptionRaiseStateIdentity = 2403, + kMachMessageIDMachExceptionRaise = 2405, + kMachMessageIDMachExceptionRaiseState = 2406, + kMachMessageIDMachExceptionRaiseStateIdentity = 2407, +}; + +} // namespace + +namespace crashpad { +namespace internal { + +ExcServer::ExcServer(ExcServer::Interface* interface) + : MachMessageServer::Interface(), + interface_(interface) { +} + +bool ExcServer::MachMessageServerFunction(mach_msg_header_t* in_header, + mach_msg_header_t* out_header, + bool* destroy_complex_request) { + PrepareReplyFromRequest(in_header, out_header); + + switch (in_header->msgh_id) { + case kMachMessageIDExceptionRaise: { + // exception_raise(), catch_exception_raise(). + typedef __Request__exception_raise_t Request; + Request* in_request = reinterpret_cast(in_header); + kern_return_t kr = __MIG_check__Request__exception_raise_t(in_request); + if (kr != MACH_MSG_SUCCESS) { + SetReplyError(out_header, kr); + return true; + } + + typedef __Reply__exception_raise_t Reply; + Reply* out_reply = reinterpret_cast(out_header); + out_reply->RetCode = + interface_->CatchExceptionRaise(in_header->msgh_local_port, + in_request->thread.name, + in_request->task.name, + in_request->exception, + in_request->code, + in_request->codeCnt, + destroy_complex_request); + if (out_reply->RetCode != KERN_SUCCESS) { + return true; + } + + out_header->msgh_size = sizeof(*out_reply); + return true; + } + + case kMachMessageIDExceptionRaiseState: { + // exception_raise_state(), catch_exception_raise_state(). + typedef __Request__exception_raise_state_t Request; + Request* in_request = reinterpret_cast(in_header); + + // in_request_1 is used for the portion of the request after the codes, + // which in theory can be variable-length. The check function will set it. + Request* in_request_1; + kern_return_t kr = __MIG_check__Request__exception_raise_state_t( + in_request, &in_request_1); + if (kr != MACH_MSG_SUCCESS) { + SetReplyError(out_header, kr); + return true; + } + + typedef __Reply__exception_raise_state_t Reply; + Reply* out_reply = reinterpret_cast(out_header); + out_reply->new_stateCnt = arraysize(out_reply->new_state); + out_reply->RetCode = + interface_->CatchExceptionRaiseState(in_header->msgh_local_port, + in_request->exception, + in_request->code, + in_request->codeCnt, + &in_request_1->flavor, + in_request_1->old_state, + in_request_1->old_stateCnt, + out_reply->new_state, + &out_reply->new_stateCnt); + if (out_reply->RetCode != KERN_SUCCESS) { + return true; + } + + out_reply->flavor = in_request_1->flavor; + out_header->msgh_size = + sizeof(*out_reply) - sizeof(out_reply->new_state) + + sizeof(out_reply->new_state[0]) * out_reply->new_stateCnt; + return true; + } + + case kMachMessageIDExceptionRaiseStateIdentity: { + // exception_raise_state_identity(), + // catch_exception_raise_state_identity(). + typedef __Request__exception_raise_state_identity_t Request; + Request* in_request = reinterpret_cast(in_header); + + // in_request_1 is used for the portion of the request after the codes, + // which in theory can be variable-length. The check function will set it. + Request* in_request_1; + kern_return_t kr = __MIG_check__Request__exception_raise_state_identity_t( + in_request, &in_request_1); + if (kr != MACH_MSG_SUCCESS) { + SetReplyError(out_header, kr); + return true; + } + + typedef __Reply__exception_raise_state_identity_t Reply; + Reply* out_reply = reinterpret_cast(out_header); + out_reply->new_stateCnt = arraysize(out_reply->new_state); + out_reply->RetCode = interface_->CatchExceptionRaiseStateIdentity( + in_header->msgh_local_port, + in_request->thread.name, + in_request->task.name, + in_request->exception, + in_request->code, + in_request->codeCnt, + &in_request_1->flavor, + in_request_1->old_state, + in_request_1->old_stateCnt, + out_reply->new_state, + &out_reply->new_stateCnt, + destroy_complex_request); + if (out_reply->RetCode != KERN_SUCCESS) { + return true; + } + + out_reply->flavor = in_request_1->flavor; + out_header->msgh_size = + sizeof(*out_reply) - sizeof(out_reply->new_state) + + sizeof(out_reply->new_state[0]) * out_reply->new_stateCnt; + return true; + } + } + + SetReplyError(out_header, MIG_BAD_ID); + return false; +} + +mach_msg_size_t ExcServer::MachMessageServerRequestSize() { + return sizeof(__RequestUnion__exc_subsystem); +} + +mach_msg_size_t ExcServer::MachMessageServerReplySize() { + return sizeof(__ReplyUnion__exc_subsystem); +} + +MachExcServer::MachExcServer(MachExcServer::Interface* interface) + : MachMessageServer::Interface(), + interface_(interface) { +} + +bool MachExcServer::MachMessageServerFunction(mach_msg_header_t* in_header, + mach_msg_header_t* out_header, + bool* destroy_complex_request) { + PrepareReplyFromRequest(in_header, out_header); + + switch (in_header->msgh_id) { + case kMachMessageIDMachExceptionRaise: { + // mach_exception_raise(), catch_mach_exception_raise(). + typedef __Request__mach_exception_raise_t Request; + Request* in_request = reinterpret_cast(in_header); + kern_return_t kr = + __MIG_check__Request__mach_exception_raise_t(in_request); + if (kr != MACH_MSG_SUCCESS) { + SetReplyError(out_header, kr); + return true; + } + + typedef __Reply__mach_exception_raise_t Reply; + Reply* out_reply = reinterpret_cast(out_header); + out_reply->RetCode = + interface_->CatchMachExceptionRaise(in_header->msgh_local_port, + in_request->thread.name, + in_request->task.name, + in_request->exception, + in_request->code, + in_request->codeCnt, + destroy_complex_request); + if (out_reply->RetCode != KERN_SUCCESS) { + return true; + } + + out_header->msgh_size = sizeof(*out_reply); + return true; + } + + case kMachMessageIDMachExceptionRaiseState: { + // mach_exception_raise_state(), catch_mach_exception_raise_state(). + typedef __Request__mach_exception_raise_state_t Request; + Request* in_request = reinterpret_cast(in_header); + + // in_request_1 is used for the portion of the request after the codes, + // which in theory can be variable-length. The check function will set it. + Request* in_request_1; + kern_return_t kr = __MIG_check__Request__mach_exception_raise_state_t( + in_request, &in_request_1); + if (kr != MACH_MSG_SUCCESS) { + SetReplyError(out_header, kr); + return true; + } + + typedef __Reply__mach_exception_raise_state_t Reply; + Reply* out_reply = reinterpret_cast(out_header); + out_reply->new_stateCnt = arraysize(out_reply->new_state); + out_reply->RetCode = + interface_->CatchMachExceptionRaiseState(in_header->msgh_local_port, + in_request->exception, + in_request->code, + in_request->codeCnt, + &in_request_1->flavor, + in_request_1->old_state, + in_request_1->old_stateCnt, + out_reply->new_state, + &out_reply->new_stateCnt); + if (out_reply->RetCode != KERN_SUCCESS) { + return true; + } + + out_reply->flavor = in_request_1->flavor; + out_header->msgh_size = + sizeof(*out_reply) - sizeof(out_reply->new_state) + + sizeof(out_reply->new_state[0]) * out_reply->new_stateCnt; + return true; + } + + case kMachMessageIDMachExceptionRaiseStateIdentity: { + // mach_exception_raise_state_identity(), + // catch_mach_exception_raise_state_identity(). + typedef __Request__mach_exception_raise_state_identity_t Request; + Request* in_request = reinterpret_cast(in_header); + + // in_request_1 is used for the portion of the request after the codes, + // which in theory can be variable-length. The check function will set it. + Request* in_request_1; + kern_return_t kr = + __MIG_check__Request__mach_exception_raise_state_identity_t( + in_request, &in_request_1); + if (kr != MACH_MSG_SUCCESS) { + SetReplyError(out_header, kr); + return true; + } + + typedef __Reply__mach_exception_raise_state_identity_t Reply; + Reply* out_reply = reinterpret_cast(out_header); + out_reply->new_stateCnt = arraysize(out_reply->new_state); + out_reply->RetCode = interface_->CatchMachExceptionRaiseStateIdentity( + in_header->msgh_local_port, + in_request->thread.name, + in_request->task.name, + in_request->exception, + in_request->code, + in_request->codeCnt, + &in_request_1->flavor, + in_request_1->old_state, + in_request_1->old_stateCnt, + out_reply->new_state, + &out_reply->new_stateCnt, + destroy_complex_request); + if (out_reply->RetCode != KERN_SUCCESS) { + return true; + } + + out_reply->flavor = in_request_1->flavor; + out_header->msgh_size = + sizeof(*out_reply) - sizeof(out_reply->new_state) + + sizeof(out_reply->new_state[0]) * out_reply->new_stateCnt; + return true; + } + } + + SetReplyError(out_header, MIG_BAD_ID); + return false; +} + +mach_msg_size_t MachExcServer::MachMessageServerRequestSize() { + return sizeof(__RequestUnion__mach_exc_subsystem); +} + +mach_msg_size_t MachExcServer::MachMessageServerReplySize() { + return sizeof(__ReplyUnion__mach_exc_subsystem); +} + +SimplifiedExcServer::SimplifiedExcServer( + SimplifiedExcServer::Interface* interface) + : ExcServer(this), + ExcServer::Interface(), + interface_(interface) { +} + +kern_return_t SimplifiedExcServer::CatchExceptionRaise( + exception_handler_t exception_port, + thread_t thread, + task_t task, + exception_type_t exception, + const exception_data_type_t* code, + mach_msg_type_number_t code_count, + bool* destroy_request) { + thread_state_flavor_t flavor = THREAD_STATE_NONE; + mach_msg_type_number_t new_state_count = 0; + return interface_->CatchException(EXCEPTION_DEFAULT, + exception_port, + thread, + task, + exception, + code, + code_count, + &flavor, + NULL, + 0, + NULL, + &new_state_count, + destroy_request); +} + +kern_return_t SimplifiedExcServer::CatchExceptionRaiseState( + exception_handler_t exception_port, + exception_type_t exception, + const 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 = false; + return interface_->CatchException(EXCEPTION_STATE, + exception_port, + MACH_PORT_NULL, + MACH_PORT_NULL, + exception, + code, + code_count, + flavor, + old_state, + old_state_count, + new_state, + new_state_count, + &destroy_complex_request); +} + +kern_return_t SimplifiedExcServer::CatchExceptionRaiseStateIdentity( + exception_handler_t exception_port, + thread_t thread, + task_t task, + exception_type_t exception, + const 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_request) { + return interface_->CatchException(EXCEPTION_STATE_IDENTITY, + exception_port, + thread, + task, + exception, + code, + code_count, + flavor, + old_state, + old_state_count, + new_state, + new_state_count, + destroy_request); +} + +SimplifiedMachExcServer::SimplifiedMachExcServer( + SimplifiedMachExcServer::Interface* interface) + : MachExcServer(this), + MachExcServer::Interface(), + interface_(interface) { +} + +kern_return_t SimplifiedMachExcServer::CatchMachExceptionRaise( + 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, + bool* destroy_request) { + thread_state_flavor_t flavor = THREAD_STATE_NONE; + mach_msg_type_number_t new_state_count = 0; + return interface_->CatchMachException( + EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, + exception_port, + thread, + task, + exception, + code, + code_count, + &flavor, + NULL, + 0, + NULL, + &new_state_count, + destroy_request); +} + +kern_return_t SimplifiedMachExcServer::CatchMachExceptionRaiseState( + exception_handler_t exception_port, + 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 = false; + return interface_->CatchMachException(EXCEPTION_STATE | MACH_EXCEPTION_CODES, + exception_port, + MACH_PORT_NULL, + MACH_PORT_NULL, + exception, + code, + code_count, + flavor, + old_state, + old_state_count, + new_state, + new_state_count, + &destroy_complex_request); +} + +kern_return_t SimplifiedMachExcServer::CatchMachExceptionRaiseStateIdentity( + 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_request) { + return interface_->CatchMachException( + EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES, + exception_port, + thread, + task, + exception, + code, + code_count, + flavor, + old_state, + old_state_count, + new_state, + new_state_count, + destroy_request); +} + +} // namespace internal + +UniversalMachExcServer::UniversalMachExcServer() + : MachMessageServer::Interface(), + internal::SimplifiedExcServer::Interface(), + internal::SimplifiedMachExcServer::Interface(), + exc_server_(this), + mach_exc_server_(this) { +} + +bool UniversalMachExcServer::MachMessageServerFunction( + mach_msg_header_t* in_header, + mach_msg_header_t* out_header, + bool* destroy_complex_request) { + switch (in_header->msgh_id) { + case kMachMessageIDMachExceptionRaise: + case kMachMessageIDMachExceptionRaiseState: + case kMachMessageIDMachExceptionRaiseStateIdentity: + return mach_exc_server_.MachMessageServerFunction( + in_header, out_header, destroy_complex_request); + case kMachMessageIDExceptionRaise: + case kMachMessageIDExceptionRaiseState: + case kMachMessageIDExceptionRaiseStateIdentity: + return exc_server_.MachMessageServerFunction( + in_header, out_header, destroy_complex_request); + } + + // Do what the MIG-generated server routines do when they can’t dispatch a + // message. + PrepareReplyFromRequest(in_header, out_header); + SetReplyError(out_header, MIG_BAD_ID); + return false; +} + +mach_msg_size_t UniversalMachExcServer::MachMessageServerRequestSize() { + return std::max(mach_exc_server_.MachMessageServerRequestSize(), + exc_server_.MachMessageServerRequestSize()); +} + +mach_msg_size_t UniversalMachExcServer::MachMessageServerReplySize() { + return std::max(mach_exc_server_.MachMessageServerReplySize(), + exc_server_.MachMessageServerReplySize()); +} + +kern_return_t UniversalMachExcServer::CatchException( + exception_behavior_t behavior, + exception_handler_t exception_port, + thread_t thread, + task_t task, + exception_type_t exception, + const 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) { + std::vector mach_codes; + mach_codes.reserve(code_count); + for (size_t index = 0; index < code_count; ++index) { + mach_codes.push_back(code[index]); + } + + return CatchMachException(behavior, + exception_port, + thread, + task, + exception, + code_count ? &mach_codes[0] : NULL, + code_count, + flavor, + old_state, + old_state_count, + new_state, + new_state_count, + destroy_complex_request); +} + +} // namespace crashpad diff --git a/util/mach/exc_server_variants.h b/util/mach/exc_server_variants.h new file mode 100644 index 00000000..08e770aa --- /dev/null +++ b/util/mach/exc_server_variants.h @@ -0,0 +1,436 @@ +// 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. + +#ifndef CRASHPAD_UTIL_MACH_EXC_SERVER_VARIANTS_H_ +#define CRASHPAD_UTIL_MACH_EXC_SERVER_VARIANTS_H_ + +#include + +#include "util/mach/mach_message_server.h" + +namespace crashpad { + +// Routines to provide a single unified front-end to the interfaces in +// and . The two interfaces are identical, +// except that the latter allows for 64-bit exception codes, and is requested by +// setting the MACH_EXCEPTION_CODES behavior bit associated with an exception +// port. + +namespace internal { + +//! \brief A server interface for the `exc` Mach subsystem. +class ExcServer : public MachMessageServer::Interface { + public: + //! \brief An interface that the different request messages that are a part of + //! the `exc` Mach subsystem can be dispatched to. + class Interface { + public: + //! \brief Handles exceptions raised by `exception_raise()`. + //! + //! This behaves equivalently to a `catch_exception_raise()` function used + //! with `exc_server()`. + //! + //! \param[out] destroy_request `true` if the request message is to be + //! destroyed even when this method returns success. See + //! MachMessageServer::Interface. + virtual kern_return_t CatchExceptionRaise( + exception_handler_t exception_port, + thread_t thread, + task_t task, + exception_type_t exception, + const exception_data_type_t* code, + mach_msg_type_number_t code_count, + bool* destroy_request) = 0; + + //! \brief Handles exceptions raised by `exception_raise_state()`. + //! + //! This behaves equivalently to a `catch_exception_raise_state()` function + //! used with `exc_server()`. + //! + //! There is no \a destroy_request parameter because, unlike + //! CatchExceptionRaise() and CatchExceptionRaiseStateIdentity(), the + //! request message is not complex (it does not carry the \a thread or \a + //! task port rights) and thus there is nothing to destroy. + virtual kern_return_t CatchExceptionRaiseState( + exception_handler_t exception_port, + exception_type_t exception, + const 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) = 0; + + //! \brief Handles exceptions raised by `exception_raise_state_identity()`. + //! + //! This behaves equivalently to a `catch_exception_raise_state_identity()` + //! function used with `exc_server()`. + //! + //! \param[out] destroy_request `true` if the request message is to be + //! destroyed even when this method returns success. See + //! MachMessageServer::Interface. + virtual kern_return_t CatchExceptionRaiseStateIdentity( + exception_handler_t exception_port, + thread_t thread, + task_t task, + exception_type_t exception, + const 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_request) = 0; + + protected: + ~Interface() {} + }; + + explicit ExcServer(Interface* interface); + + // MachMessageServer::Interface: + + virtual bool MachMessageServerFunction( + mach_msg_header_t* in_header, + mach_msg_header_t* out_header, + bool* destroy_complex_request) override; + + virtual mach_msg_size_t MachMessageServerRequestSize() override; + virtual mach_msg_size_t MachMessageServerReplySize() override; + + private: + Interface* interface_; // weak +}; + +//! \brief A server interface for the `mach_exc` Mach subsystem. +class MachExcServer : public MachMessageServer::Interface { + public: + //! \brief An interface that the different request messages that are a part of + //! the `mach_exc` Mach subsystem can be dispatched to. + class Interface { + public: + //! \brief Handles exceptions raised by `mach_exception_raise()`. + //! + //! This behaves equivalently to a `catch_mach_exception_raise()` function + //! used with `mach_exc_server()`. + //! + //! \param[out] destroy_request `true` if the request message is to be + //! destroyed even when this method returns success. See + //! MachMessageServer::Interface. + virtual kern_return_t CatchMachExceptionRaise( + 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, + bool* destroy_request) = 0; + + //! \brief Handles exceptions raised by `mach_exception_raise_state()`. + //! + //! This behaves equivalently to a `catch_mach_exception_raise_state()` + //! function used with `mach_exc_server()`. + //! + //! There is no \a destroy_request parameter because, unlike + //! CatchMachExceptionRaise() and CatchMachExceptionRaiseStateIdentity(), + //! the request message is not complex (it does not carry the \a thread or + //! \a task port rights) and thus there is nothing to destroy. + virtual kern_return_t CatchMachExceptionRaiseState( + exception_handler_t exception_port, + 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) = 0; + + //! \brief Handles exceptions raised by + //! `mach_exception_raise_state_identity()`. + //! + //! This behaves equivalently to a + //! `catch_mach_exception_raise_state_identity()` function used with + //! `mach_exc_server()`. + //! + //! \param[out] destroy_request `true` if the request message is to be + //! destroyed even when this method returns success. See + //! MachMessageServer::Interface. + virtual kern_return_t CatchMachExceptionRaiseStateIdentity( + 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_request) = 0; + + protected: + ~Interface() {} + }; + + explicit MachExcServer(Interface* interface); + + // MachMessageServer::Interface: + + virtual bool MachMessageServerFunction( + mach_msg_header_t* in_header, + mach_msg_header_t* out_header, + bool* destroy_complex_request) override; + + virtual mach_msg_size_t MachMessageServerRequestSize() override; + virtual mach_msg_size_t MachMessageServerReplySize() override; + + private: + Interface* interface_; // weak +}; + +//! \brief A server interface for the `exc` Mach subsystem, simplified to have +//! only a single interface method needing implementation. +class SimplifiedExcServer : public ExcServer, public ExcServer::Interface { + public: + //! \brief An interface that the different request messages that are a part of + //! the `exc` Mach subsystem can be dispatched to. + class Interface { + public: + //! \brief Handles exceptions raised by `exception_raise()`, + //! `exception_raise_state()`, and `exception_raise_state_identity()`. + //! + //! For convenience in implementation, these different “behaviors” of + //! exception messages are all mapped to a single interface method. The + //! exception’s original “behavior” is specified in the \a behavior + //! parameter. Only parameters that were supplied in the request message + //! are populated, other parameters are set to reasonable default values. + //! + //! The meanings of most parameters are identical to that of + //! ExcServer::Interface::CatchExceptionRaiseStateIdentity(). + //! + //! \param[in] behavior `EXCEPTION_DEFAULT`, `EXCEPTION_STATE`, or + //! `EXCEPTION_STATE_IDENTITY`, identifying which exception request + //! message was processed and thus which other parameters are valid. + virtual kern_return_t CatchException( + exception_behavior_t behavior, + exception_handler_t exception_port, + thread_t thread, + task_t task, + exception_type_t exception, + const 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) = 0; + + protected: + ~Interface() {} + }; + + explicit SimplifiedExcServer(Interface* interface); + + // ExcServer::Interface: + + virtual kern_return_t CatchExceptionRaise( + exception_handler_t exception_port, + thread_t thread, + task_t task, + exception_type_t exception, + const exception_data_type_t* code, + mach_msg_type_number_t code_count, + bool* destroy_request) override; + virtual kern_return_t CatchExceptionRaiseState( + exception_handler_t exception_port, + exception_type_t exception, + const 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) override; + virtual kern_return_t CatchExceptionRaiseStateIdentity( + exception_handler_t exception_port, + thread_t thread, + task_t task, + exception_type_t exception, + const 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_request) override; + + private: + Interface* interface_; // weak +}; + +//! \brief A server interface for the `mach_exc` Mach subsystem, simplified to +//! have only a single interface method needing implementation. +class SimplifiedMachExcServer : public MachExcServer, + public MachExcServer::Interface { + public: + //! \brief An interface that the different request messages that are a part of + //! the `mach_exc` Mach subsystem can be dispatched to. + class Interface { + public: + //! \brief Handles exceptions raised by `mach_exception_raise()`, + //! `mach_exception_raise_state()`, and + //! `mach_exception_raise_state_identity()`. + //! + //! When used with UniversalMachExcServer, this also handles exceptions + //! raised by `exception_raise()`, `exception_raise_state()`, and + //! `exception_raise_state_identity()`. + //! + //! For convenience in implementation, these different “behaviors” of + //! exception messages are all mapped to a single interface method. The + //! exception’s original “behavior” is specified in the \a behavior + //! parameter. Only parameters that were supplied in the request message + //! are populated, other parameters are set to reasonable default values. + //! + //! The meanings of most parameters are identical to that of + //! MachExcServer::Interface::CatchMachExceptionRaiseStateIdentity(). + //! + //! \param[in] behavior `MACH_EXCEPTION_CODES | EXCEPTION_DEFAULT`, + //! `MACH_EXCEPTION_CODES | EXCEPTION_STATE`, or + //! `MACH_EXCEPTION_CODES | EXCEPTION_STATE_IDENTITY`, identifying which + //! exception request message was processed and thus which other + //! parameters are valid. When used with UniversalMachExcServer, \a + //! behavior can also be `EXCEPTION_DEFAULT`, `EXCEPTION_STATE`, or + //! `EXCEPTION_STATE_IDENTITY`. + 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) = 0; + + protected: + ~Interface() {} + }; + + explicit SimplifiedMachExcServer(Interface* interface); + + // MachExcServer::Interface: + + virtual kern_return_t CatchMachExceptionRaise( + 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, + bool* destroy_request) override; + virtual kern_return_t CatchMachExceptionRaiseState( + exception_handler_t exception_port, + 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) override; + virtual kern_return_t CatchMachExceptionRaiseStateIdentity( + 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_request) override; + + private: + Interface* interface_; // weak +}; + +} // namespace internal + +//! \brief A server interface for the `exc` and `mach_exc` Mach subsystems, +//! unified to handle exceptions delivered to either subsystem, and +//! simplified to have only a single interface method needing +//! implementation. +//! +//! UniversalMachExcServer operates by translating messages received in the +//! `exc` subsystem to a variant that is compatible with the `mach_exc` +//! subsystem. This involves changing the format of \a code, the exception code +//! field, from `exception_data_type_t` to `mach_exception_data_type_t`. +//! This is achieved by implementing SimplifiedExcServer::Interface and having +//! it forward translated messages to SimplifiedMachExcServer::Interface, which +//! is left unimplemented here so that users of this class can provide their own +//! implementations. +class UniversalMachExcServer + : public MachMessageServer::Interface, + public internal::SimplifiedExcServer::Interface, + public internal::SimplifiedMachExcServer::Interface { + public: + UniversalMachExcServer(); + + // MachMessageServer::Interface: + + virtual bool MachMessageServerFunction( + mach_msg_header_t* in_header, + mach_msg_header_t* out_header, + bool* destroy_complex_request) override; + + virtual mach_msg_size_t MachMessageServerRequestSize() override; + virtual mach_msg_size_t MachMessageServerReplySize() override; + + // internal::SimplifiedExcServer::Interface: + + virtual kern_return_t CatchException( + exception_behavior_t behavior, + exception_handler_t exception_port, + thread_t thread, + task_t task, + exception_type_t exception, + const 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; + + private: + internal::SimplifiedExcServer exc_server_; + internal::SimplifiedMachExcServer mach_exc_server_; +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_MACH_EXC_SERVER_VARIANTS_H_ diff --git a/util/mach/exc_server_variants_test.cc b/util/mach/exc_server_variants_test.cc new file mode 100644 index 00000000..a942bb8a --- /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 +#include + +#include "base/basictypes.h" +#include "base/strings/stringprintf.h" +#include "gmock/gmock.h" +#include "gtest/gtest.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; +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(0xfedcba98), +}; + +const exception_data_type_t kMachExceptionCodes[] = { + KERN_PROTECTION_FAILURE, + static_cast(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_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(kMachPortNull, Head.msgh_local_port); + switch (behavior) { + case EXCEPTION_DEFAULT: + EXPECT_EQ(2501, Head.msgh_id); + break; + case static_cast(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_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(kMachPortNull, 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_STATE | + MACH_EXCEPTION_CODES): + EXPECT_EQ(2506, Head.msgh_id); + break; + case static_cast(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_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(kMachPortNull, 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 +class ScopedDefaultValue { + public: + explicit ScopedDefaultValue(const T& default_value) { + DefaultValue::Set(default_value); + } + + ~ScopedDefaultValue() { DefaultValue::Clear(); } +}; + +TEST(ExcServerVariants, MockExceptionRaise) { + ScopedDefaultValue default_kern_return_t(KERN_FAILURE); + + 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(&request), + reinterpret_cast(&reply), + &destroy_complex_request)); + EXPECT_TRUE(destroy_complex_request); + + reply.Verify(kExceptionBehavior); +} + +TEST(ExcServerVariants, MockExceptionRaiseState) { + ScopedDefaultValue 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(&request), + reinterpret_cast(&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 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(&request), + reinterpret_cast(&reply), + &destroy_complex_request)); + EXPECT_TRUE(destroy_complex_request); + + reply.Verify(kExceptionBehavior); +} + +TEST(ExcServerVariants, MockMachExceptionRaise) { + ScopedDefaultValue 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(&request), + reinterpret_cast(&reply), + &destroy_complex_request)); + EXPECT_TRUE(destroy_complex_request); + + reply.Verify(kExceptionBehavior); +} + +TEST(ExcServerVariants, MockMachExceptionRaiseState) { + ScopedDefaultValue 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(&request), + reinterpret_cast(&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 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(&request), + reinterpret_cast(&reply), + &destroy_complex_request)); + EXPECT_TRUE(destroy_complex_request); + + reply.Verify(kExceptionBehavior); +} + +TEST(ExcServerVariants, MockUnknownID) { + ScopedDefaultValue 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(&request), + reinterpret_cast(&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(kMachPortNull, thread); + EXPECT_EQ(ChildTask(), task); + } else { + EXPECT_EQ(kMachPortNull, thread); + EXPECT_EQ(kMachPortNull, 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(NULL), old_state); + EXPECT_EQ(static_cast(THREAD_STATE_MAX), + *new_state_count); + EXPECT_NE(static_cast(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; + } + + 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 diff --git a/util/mach/mach_extensions.h b/util/mach/mach_extensions.h new file mode 100644 index 00000000..81e9a86a --- /dev/null +++ b/util/mach/mach_extensions.h @@ -0,0 +1,44 @@ +// 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. + +#ifndef CRASHPAD_UTIL_MACH_MACH_EXTENSIONS_H_ +#define CRASHPAD_UTIL_MACH_MACH_EXTENSIONS_H_ + +#include + +namespace crashpad { + +//! \brief `MACH_PORT_NULL` with the correct type for a Mach port, +//! `mach_port_t`. +//! +//! For situations where implicit conversions between signed and unsigned types +//! are not performed, use kMachPortNull instead of an explicit `static_cast` of +//! `MACH_PORT_NULL` to `mach_port_t`. This is useful for logging and testing +//! assertions. +const mach_port_t kMachPortNull = MACH_PORT_NULL; + +// Because exception_mask_t is an int and has one bit for each defined +// exception_type_t, it’s reasonable to assume that there cannot be any +// officially-defined exception_type_t values higher than 31. +// kMachExceptionSimulated uses a value well outside this range because it does +// not require a corresponding mask value. Simulated exceptions are delivered to +// the exception handler registered for EXC_CRASH exceptions using +// EXC_MASK_CRASH. + +//! \brief An exception type to use for simulated exceptions. +const exception_type_t kMachExceptionSimulated = 'CPsx'; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_MACH_MACH_EXTENSIONS_H_ diff --git a/util/mach/mach_message_server_test.cc b/util/mach/mach_message_server_test.cc index 08c781d1..311a546a 100644 --- a/util/mach/mach_message_server_test.cc +++ b/util/mach/mach_message_server_test.cc @@ -21,6 +21,7 @@ #include "base/mac/scoped_mach_port.h" #include "gtest/gtest.h" #include "util/file/fd_io.h" +#include "util/mach/mach_extensions.h" #include "util/test/errors.h" #include "util/test/mac/mach_errors.h" #include "util/test/mac/mach_multiprocess.h" @@ -214,8 +215,7 @@ class TestMachMessageServer : public MachMessageServer::Interface, EXPECT_EQ(kRequestMessageId, request->header.msgh_id); if (options_.client_send_complex) { EXPECT_EQ(1u, request->body.msgh_descriptor_count); - EXPECT_NE(static_cast(MACH_PORT_NULL), - request->port_descriptor.name); + EXPECT_NE(kMachPortNull, request->port_descriptor.name); parent_complex_message_port_ = request->port_descriptor.name; EXPECT_EQ(static_cast(MACH_MSG_TYPE_MOVE_SEND), request->port_descriptor.disposition); @@ -224,8 +224,7 @@ class TestMachMessageServer : public MachMessageServer::Interface, request->port_descriptor.type); } else { EXPECT_EQ(0u, request->body.msgh_descriptor_count); - EXPECT_EQ(static_cast(MACH_PORT_NULL), - request->port_descriptor.name); + EXPECT_EQ(kMachPortNull, request->port_descriptor.name); EXPECT_EQ(0u, request->port_descriptor.disposition); EXPECT_EQ(0u, request->port_descriptor.type); } @@ -318,8 +317,7 @@ class TestMachMessageServer : public MachMessageServer::Interface, << MachErrorMessage(kr, "MachMessageServer"); if (options_.client_send_complex) { - EXPECT_NE(static_cast(MACH_PORT_NULL), - parent_complex_message_port_); + EXPECT_NE(kMachPortNull, parent_complex_message_port_); mach_port_type_t type; if (!options_.expect_server_destroyed_complex) { @@ -505,8 +503,7 @@ class TestMachMessageServer : public MachMessageServer::Interface, ASSERT_EQ(static_cast( MACH_MSGH_BITS(0, MACH_MSG_TYPE_MOVE_SEND)), reply.Head.msgh_bits); ASSERT_EQ(sizeof(ReplyMessage), reply.Head.msgh_size); - ASSERT_EQ(static_cast(MACH_PORT_NULL), - reply.Head.msgh_remote_port); + ASSERT_EQ(kMachPortNull, reply.Head.msgh_remote_port); ASSERT_EQ(LocalPort(), reply.Head.msgh_local_port); ASSERT_EQ(kReplyMessageId, reply.Head.msgh_id); ASSERT_EQ(0, memcmp(&reply.NDR, &NDR_record, sizeof(NDR_record))); diff --git a/util/test/mac/mach_multiprocess.cc b/util/test/mac/mach_multiprocess.cc index d78b8600..569f2c27 100644 --- a/util/test/mac/mach_multiprocess.cc +++ b/util/test/mac/mach_multiprocess.cc @@ -27,6 +27,7 @@ #include "base/rand_util.h" #include "gtest/gtest.h" #include "util/mach/bootstrap.h" +#include "util/mach/mach_extensions.h" #include "util/misc/scoped_forbid_return.h" #include "util/test/errors.h" #include "util/test/mac/mach_errors.h" @@ -105,17 +106,17 @@ void MachMultiprocess::PreFork() { } mach_port_t MachMultiprocess::LocalPort() const { - EXPECT_NE(static_cast(MACH_PORT_NULL), info_->local_port); + EXPECT_NE(kMachPortNull, info_->local_port); return info_->local_port; } mach_port_t MachMultiprocess::RemotePort() const { - EXPECT_NE(static_cast(MACH_PORT_NULL), info_->remote_port); + EXPECT_NE(kMachPortNull, info_->remote_port); return info_->remote_port; } mach_port_t MachMultiprocess::ChildTask() const { - EXPECT_NE(static_cast(MACH_PORT_NULL), info_->child_task); + EXPECT_NE(kMachPortNull, info_->child_task); return info_->child_task; } diff --git a/util/util.gyp b/util/util.gyp index 0176647d..7e242edb 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -61,6 +61,9 @@ 'mac/process_types/traits.h', 'mach/bootstrap.cc', 'mach/bootstrap.h', + 'mach/exc_server_variants.cc', + 'mach/exc_server_variants.h', + 'mach/mach_extensions.h', 'mach/mach_message_server.cc', 'mach/mach_message_server.h', 'mach/task_memory.cc', @@ -167,6 +170,7 @@ 'util_test_lib', 'util_test_multiprocess_exec_test_child', '../compat/compat.gyp:compat', + '../third_party/gmock/gmock.gyp:gmock', '../third_party/gtest/gtest.gyp:gtest', '../third_party/gtest/gtest.gyp:gtest_main', '../third_party/mini_chromium/mini_chromium/base/base.gyp:base', @@ -185,6 +189,7 @@ 'mac/process_types_test.cc', 'mac/service_management_test.mm', 'mach/bootstrap_test.cc', + 'mach/exc_server_variants_test.cc', 'mach/mach_message_server_test.cc', 'mach/task_memory_test.cc', 'misc/initialization_state_dcheck_test.cc',