mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-27 15:32:10 +08:00
6278690abe
sed -i '' -E -e 's/Copyright (.+) The Crashpad Authors\. All rights reserved\.$/Copyright \1 The Crashpad Authors/' $(git grep -El 'Copyright (.+) The Crashpad Authors\. All rights reserved\.$') Bug: chromium:1098010 Change-Id: I8d6138469ddbe3d281a5d83f64cf918ec2491611 Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/3878262 Reviewed-by: Joshua Peraza <jperaza@chromium.org> Commit-Queue: Mark Mentovai <mark@chromium.org>
1366 lines
48 KiB
C++
1366 lines
48 KiB
C++
// Copyright 2014 The Crashpad Authors
|
||
//
|
||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
// you may not use this file except in compliance with the License.
|
||
// You may obtain a copy of the License at
|
||
//
|
||
// http://www.apache.org/licenses/LICENSE-2.0
|
||
//
|
||
// Unless required by applicable law or agreed to in writing, software
|
||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
// See the License for the specific language governing permissions and
|
||
// limitations under the License.
|
||
|
||
#include "util/mach/exc_server_variants.h"
|
||
|
||
#include <mach/mach.h>
|
||
#include <stdint.h>
|
||
#include <string.h>
|
||
#include <sys/types.h>
|
||
|
||
#include <iterator>
|
||
|
||
#include "base/strings/stringprintf.h"
|
||
#include "build/build_config.h"
|
||
#include "gmock/gmock.h"
|
||
#include "gtest/gtest.h"
|
||
#include "test/mac/mach_errors.h"
|
||
#include "util/mac/mac_util.h"
|
||
#include "util/mach/exception_behaviors.h"
|
||
#include "util/mach/exception_types.h"
|
||
#include "util/mach/mach_message.h"
|
||
#include "util/misc/implicit_cast.h"
|
||
|
||
#if BUILDFLAG(IS_MAC)
|
||
#include "test/mac/mach_multiprocess.h"
|
||
#endif // BUILDFLAG(IS_MAC)
|
||
|
||
namespace crashpad {
|
||
namespace test {
|
||
namespace {
|
||
|
||
using testing::DefaultValue;
|
||
using testing::Eq;
|
||
using testing::Pointee;
|
||
using testing::Return;
|
||
|
||
// Fake Mach ports. These aren’t used as ports in these tests, they’re just used
|
||
// as cookies to make sure that the correct values get passed to the correct
|
||
// places.
|
||
constexpr mach_port_t kClientRemotePort = 0x01010101;
|
||
constexpr mach_port_t kServerLocalPort = 0x02020202;
|
||
constexpr thread_t kExceptionThreadPort = 0x03030303;
|
||
constexpr task_t kExceptionTaskPort = 0x04040404;
|
||
|
||
// Other fake exception values.
|
||
constexpr exception_type_t kExceptionType = EXC_BAD_ACCESS;
|
||
|
||
// Test using an exception code with the high bit set to ensure that it gets
|
||
// promoted to the wider mach_exception_data_type_t type as a signed quantity.
|
||
constexpr exception_data_type_t kTestExceptonCodes[] = {
|
||
KERN_PROTECTION_FAILURE,
|
||
implicit_cast<exception_data_type_t>(0xfedcba98),
|
||
};
|
||
|
||
constexpr mach_exception_data_type_t kTestMachExceptionCodes[] = {
|
||
KERN_PROTECTION_FAILURE,
|
||
implicit_cast<mach_exception_data_type_t>(0xfedcba9876543210),
|
||
};
|
||
|
||
constexpr thread_state_flavor_t kThreadStateFlavor = MACHINE_THREAD_STATE;
|
||
constexpr mach_msg_type_number_t kThreadStateFlavorCount =
|
||
MACHINE_THREAD_STATE_COUNT;
|
||
|
||
void InitializeMachMsgPortDescriptor(mach_msg_port_descriptor_t* descriptor,
|
||
mach_port_t port) {
|
||
descriptor->name = port;
|
||
descriptor->disposition = MACH_MSG_TYPE_PORT_SEND;
|
||
descriptor->type = MACH_MSG_PORT_DESCRIPTOR;
|
||
}
|
||
|
||
// The definitions of the request and reply structures from mach_exc.h aren’t
|
||
// available here. They need custom initialization code, and the reply
|
||
// structures need verification code too, so duplicate the expected definitions
|
||
// of the structures from both exc.h and mach_exc.h here in this file, and
|
||
// provide the initialization and verification code as methods in true
|
||
// object-oriented fashion.
|
||
|
||
struct __attribute__((packed, aligned(4))) ExceptionRaiseRequest {
|
||
ExceptionRaiseRequest() {
|
||
memset(this, 0xa5, sizeof(*this));
|
||
Head.msgh_bits =
|
||
MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND_ONCE, MACH_MSG_TYPE_PORT_SEND) |
|
||
MACH_MSGH_BITS_COMPLEX;
|
||
Head.msgh_size = sizeof(*this) - sizeof(trailer);
|
||
Head.msgh_remote_port = kClientRemotePort;
|
||
Head.msgh_local_port = kServerLocalPort;
|
||
Head.msgh_id = 2401;
|
||
msgh_body.msgh_descriptor_count = 2;
|
||
InitializeMachMsgPortDescriptor(&thread, kExceptionThreadPort);
|
||
InitializeMachMsgPortDescriptor(&task, kExceptionTaskPort);
|
||
NDR = NDR_record;
|
||
exception = kExceptionType;
|
||
codeCnt = 2;
|
||
code[0] = kTestExceptonCodes[0];
|
||
code[1] = kTestExceptonCodes[1];
|
||
}
|
||
|
||
mach_msg_header_t Head;
|
||
mach_msg_body_t msgh_body;
|
||
mach_msg_port_descriptor_t thread;
|
||
mach_msg_port_descriptor_t task;
|
||
NDR_record_t NDR;
|
||
exception_type_t exception;
|
||
mach_msg_type_number_t codeCnt;
|
||
integer_t code[2];
|
||
mach_msg_trailer_t trailer;
|
||
};
|
||
|
||
struct __attribute__((packed, aligned(4))) ExceptionRaiseReply {
|
||
ExceptionRaiseReply() {
|
||
memset(this, 0x5a, sizeof(*this));
|
||
RetCode = KERN_FAILURE;
|
||
}
|
||
|
||
// Verify accepts a |behavior| parameter because the same message format and
|
||
// verification function is used for ExceptionRaiseReply and
|
||
// MachExceptionRaiseReply. Knowing which behavior is expected allows the
|
||
// message ID to be checked.
|
||
void Verify(exception_behavior_t behavior) {
|
||
EXPECT_EQ(Head.msgh_bits,
|
||
implicit_cast<mach_msg_bits_t>(
|
||
MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND_ONCE, 0)));
|
||
EXPECT_EQ(Head.msgh_size, sizeof(*this));
|
||
EXPECT_EQ(Head.msgh_remote_port, kClientRemotePort);
|
||
EXPECT_EQ(Head.msgh_local_port, kMachPortNull);
|
||
switch (behavior) {
|
||
case EXCEPTION_DEFAULT:
|
||
EXPECT_EQ(Head.msgh_id, 2501);
|
||
break;
|
||
case EXCEPTION_DEFAULT | kMachExceptionCodes:
|
||
EXPECT_EQ(Head.msgh_id, 2505);
|
||
break;
|
||
default:
|
||
ADD_FAILURE() << "behavior " << behavior << ", Head.msgh_id "
|
||
<< Head.msgh_id;
|
||
break;
|
||
}
|
||
EXPECT_EQ(memcmp(&NDR, &NDR_record, sizeof(NDR)), 0);
|
||
EXPECT_EQ(RetCode, KERN_SUCCESS);
|
||
}
|
||
|
||
mach_msg_header_t Head;
|
||
NDR_record_t NDR;
|
||
kern_return_t RetCode;
|
||
};
|
||
|
||
struct __attribute__((packed, aligned(4))) ExceptionRaiseStateRequest {
|
||
ExceptionRaiseStateRequest() {
|
||
memset(this, 0xa5, sizeof(*this));
|
||
Head.msgh_bits =
|
||
MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND_ONCE, MACH_MSG_TYPE_PORT_SEND);
|
||
Head.msgh_size = sizeof(*this) - sizeof(trailer);
|
||
Head.msgh_remote_port = kClientRemotePort;
|
||
Head.msgh_local_port = kServerLocalPort;
|
||
Head.msgh_id = 2402;
|
||
NDR = NDR_record;
|
||
exception = kExceptionType;
|
||
codeCnt = 2;
|
||
code[0] = kTestExceptonCodes[0];
|
||
code[1] = kTestExceptonCodes[1];
|
||
flavor = kThreadStateFlavor;
|
||
old_stateCnt = kThreadStateFlavorCount;
|
||
|
||
// Adjust the message size for the data that it’s actually carrying, which
|
||
// may be smaller than the maximum that it can carry.
|
||
Head.msgh_size += sizeof(old_state[0]) * old_stateCnt - sizeof(old_state);
|
||
}
|
||
|
||
// Because the message size has been adjusted, the trailer may not appear in
|
||
// its home member variable. This computes the actual address of the trailer.
|
||
const mach_msg_trailer_t* Trailer() const {
|
||
return MachMessageTrailerFromHeader(&Head);
|
||
}
|
||
|
||
mach_msg_header_t Head;
|
||
NDR_record_t NDR;
|
||
exception_type_t exception;
|
||
mach_msg_type_number_t codeCnt;
|
||
integer_t code[2];
|
||
int flavor;
|
||
mach_msg_type_number_t old_stateCnt;
|
||
natural_t old_state[THREAD_STATE_MAX];
|
||
mach_msg_trailer_t trailer;
|
||
};
|
||
|
||
struct __attribute__((packed, aligned(4))) ExceptionRaiseStateReply {
|
||
ExceptionRaiseStateReply() {
|
||
memset(this, 0x5a, sizeof(*this));
|
||
RetCode = KERN_FAILURE;
|
||
}
|
||
|
||
// Verify accepts a |behavior| parameter because the same message format and
|
||
// verification function is used for ExceptionRaiseStateReply,
|
||
// ExceptionRaiseStateIdentityReply, MachExceptionRaiseStateReply, and
|
||
// MachExceptionRaiseStateIdentityReply. Knowing which behavior is expected
|
||
// allows the message ID to be checked.
|
||
void Verify(exception_behavior_t behavior) {
|
||
EXPECT_EQ(Head.msgh_bits,
|
||
implicit_cast<mach_msg_bits_t>(
|
||
MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND_ONCE, 0)));
|
||
EXPECT_EQ(Head.msgh_size, sizeof(*this));
|
||
EXPECT_EQ(Head.msgh_remote_port, kClientRemotePort);
|
||
EXPECT_EQ(Head.msgh_local_port, kMachPortNull);
|
||
switch (behavior) {
|
||
case EXCEPTION_STATE:
|
||
EXPECT_EQ(Head.msgh_id, 2502);
|
||
break;
|
||
case EXCEPTION_STATE_IDENTITY:
|
||
EXPECT_EQ(Head.msgh_id, 2503);
|
||
break;
|
||
case EXCEPTION_STATE | kMachExceptionCodes:
|
||
EXPECT_EQ(Head.msgh_id, 2506);
|
||
break;
|
||
case EXCEPTION_STATE_IDENTITY | kMachExceptionCodes:
|
||
EXPECT_EQ(Head.msgh_id, 2507);
|
||
break;
|
||
default:
|
||
ADD_FAILURE() << "behavior " << behavior << ", Head.msgh_id "
|
||
<< Head.msgh_id;
|
||
break;
|
||
}
|
||
EXPECT_EQ(memcmp(&NDR, &NDR_record, sizeof(NDR)), 0);
|
||
EXPECT_EQ(RetCode, KERN_SUCCESS);
|
||
EXPECT_EQ(flavor, kThreadStateFlavor);
|
||
EXPECT_EQ(new_stateCnt, std::size(new_state));
|
||
}
|
||
|
||
mach_msg_header_t Head;
|
||
NDR_record_t NDR;
|
||
kern_return_t RetCode;
|
||
int flavor;
|
||
mach_msg_type_number_t new_stateCnt;
|
||
natural_t new_state[THREAD_STATE_MAX];
|
||
};
|
||
|
||
struct __attribute__((packed, aligned(4))) ExceptionRaiseStateIdentityRequest {
|
||
ExceptionRaiseStateIdentityRequest() {
|
||
memset(this, 0xa5, sizeof(*this));
|
||
Head.msgh_bits =
|
||
MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND_ONCE, MACH_MSG_TYPE_PORT_SEND) |
|
||
MACH_MSGH_BITS_COMPLEX;
|
||
Head.msgh_size = sizeof(*this) - sizeof(trailer);
|
||
Head.msgh_remote_port = kClientRemotePort;
|
||
Head.msgh_local_port = kServerLocalPort;
|
||
Head.msgh_id = 2403;
|
||
msgh_body.msgh_descriptor_count = 2;
|
||
InitializeMachMsgPortDescriptor(&thread, kExceptionThreadPort);
|
||
InitializeMachMsgPortDescriptor(&task, kExceptionTaskPort);
|
||
NDR = NDR_record;
|
||
exception = kExceptionType;
|
||
codeCnt = 2;
|
||
code[0] = kTestExceptonCodes[0];
|
||
code[1] = kTestExceptonCodes[1];
|
||
flavor = kThreadStateFlavor;
|
||
old_stateCnt = kThreadStateFlavorCount;
|
||
|
||
// Adjust the message size for the data that it’s actually carrying, which
|
||
// may be smaller than the maximum that it can carry.
|
||
Head.msgh_size += sizeof(old_state[0]) * old_stateCnt - sizeof(old_state);
|
||
}
|
||
|
||
// Because the message size has been adjusted, the trailer may not appear in
|
||
// its home member variable. This computes the actual address of the trailer.
|
||
const mach_msg_trailer_t* Trailer() const {
|
||
return MachMessageTrailerFromHeader(&Head);
|
||
}
|
||
|
||
mach_msg_header_t Head;
|
||
mach_msg_body_t msgh_body;
|
||
mach_msg_port_descriptor_t thread;
|
||
mach_msg_port_descriptor_t task;
|
||
NDR_record_t NDR;
|
||
exception_type_t exception;
|
||
mach_msg_type_number_t codeCnt;
|
||
integer_t code[2];
|
||
int flavor;
|
||
mach_msg_type_number_t old_stateCnt;
|
||
natural_t old_state[THREAD_STATE_MAX];
|
||
mach_msg_trailer_t trailer;
|
||
};
|
||
|
||
// The reply messages for exception_raise_state and
|
||
// exception_raise_state_identity are identical.
|
||
using ExceptionRaiseStateIdentityReply = ExceptionRaiseStateReply;
|
||
|
||
struct __attribute__((packed, aligned(4))) MachExceptionRaiseRequest {
|
||
MachExceptionRaiseRequest() {
|
||
memset(this, 0xa5, sizeof(*this));
|
||
Head.msgh_bits =
|
||
MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND_ONCE, MACH_MSG_TYPE_PORT_SEND) |
|
||
MACH_MSGH_BITS_COMPLEX;
|
||
Head.msgh_size = sizeof(*this) - sizeof(trailer);
|
||
Head.msgh_remote_port = kClientRemotePort;
|
||
Head.msgh_local_port = kServerLocalPort;
|
||
Head.msgh_id = 2405;
|
||
msgh_body.msgh_descriptor_count = 2;
|
||
InitializeMachMsgPortDescriptor(&thread, kExceptionThreadPort);
|
||
InitializeMachMsgPortDescriptor(&task, kExceptionTaskPort);
|
||
NDR = NDR_record;
|
||
exception = kExceptionType;
|
||
codeCnt = 2;
|
||
code[0] = kTestMachExceptionCodes[0];
|
||
code[1] = kTestMachExceptionCodes[1];
|
||
}
|
||
|
||
mach_msg_header_t Head;
|
||
mach_msg_body_t msgh_body;
|
||
mach_msg_port_descriptor_t thread;
|
||
mach_msg_port_descriptor_t task;
|
||
NDR_record_t NDR;
|
||
exception_type_t exception;
|
||
mach_msg_type_number_t codeCnt;
|
||
int64_t code[2];
|
||
mach_msg_trailer_t trailer;
|
||
};
|
||
|
||
// The reply messages for exception_raise and mach_exception_raise are
|
||
// identical.
|
||
using MachExceptionRaiseReply = ExceptionRaiseReply;
|
||
|
||
struct __attribute__((packed, aligned(4))) MachExceptionRaiseStateRequest {
|
||
MachExceptionRaiseStateRequest() {
|
||
memset(this, 0xa5, sizeof(*this));
|
||
Head.msgh_bits =
|
||
MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND_ONCE, MACH_MSG_TYPE_PORT_SEND);
|
||
Head.msgh_size = sizeof(*this) - sizeof(trailer);
|
||
Head.msgh_remote_port = kClientRemotePort;
|
||
Head.msgh_local_port = kServerLocalPort;
|
||
Head.msgh_id = 2406;
|
||
NDR = NDR_record;
|
||
exception = kExceptionType;
|
||
codeCnt = 2;
|
||
code[0] = kTestMachExceptionCodes[0];
|
||
code[1] = kTestMachExceptionCodes[1];
|
||
flavor = kThreadStateFlavor;
|
||
old_stateCnt = kThreadStateFlavorCount;
|
||
|
||
// Adjust the message size for the data that it’s actually carrying, which
|
||
// may be smaller than the maximum that it can carry.
|
||
Head.msgh_size += sizeof(old_state[0]) * old_stateCnt - sizeof(old_state);
|
||
}
|
||
|
||
// Because the message size has been adjusted, the trailer may not appear in
|
||
// its home member variable. This computes the actual address of the trailer.
|
||
const mach_msg_trailer_t* Trailer() const {
|
||
return MachMessageTrailerFromHeader(&Head);
|
||
}
|
||
|
||
mach_msg_header_t Head;
|
||
NDR_record_t NDR;
|
||
exception_type_t exception;
|
||
mach_msg_type_number_t codeCnt;
|
||
int64_t code[2];
|
||
int flavor;
|
||
mach_msg_type_number_t old_stateCnt;
|
||
natural_t old_state[THREAD_STATE_MAX];
|
||
mach_msg_trailer_t trailer;
|
||
};
|
||
|
||
// The reply messages for exception_raise_state and mach_exception_raise_state
|
||
// are identical.
|
||
using MachExceptionRaiseStateReply = ExceptionRaiseStateReply;
|
||
|
||
struct __attribute__((packed, aligned(4)))
|
||
MachExceptionRaiseStateIdentityRequest {
|
||
MachExceptionRaiseStateIdentityRequest() {
|
||
memset(this, 0xa5, sizeof(*this));
|
||
Head.msgh_bits =
|
||
MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND_ONCE, MACH_MSG_TYPE_PORT_SEND) |
|
||
MACH_MSGH_BITS_COMPLEX;
|
||
Head.msgh_size = sizeof(*this) - sizeof(trailer);
|
||
Head.msgh_remote_port = kClientRemotePort;
|
||
Head.msgh_local_port = kServerLocalPort;
|
||
Head.msgh_id = 2407;
|
||
msgh_body.msgh_descriptor_count = 2;
|
||
InitializeMachMsgPortDescriptor(&thread, kExceptionThreadPort);
|
||
InitializeMachMsgPortDescriptor(&task, kExceptionTaskPort);
|
||
NDR = NDR_record;
|
||
exception = kExceptionType;
|
||
codeCnt = 2;
|
||
code[0] = kTestMachExceptionCodes[0];
|
||
code[1] = kTestMachExceptionCodes[1];
|
||
flavor = kThreadStateFlavor;
|
||
old_stateCnt = kThreadStateFlavorCount;
|
||
|
||
// Adjust the message size for the data that it’s actually carrying, which
|
||
// may be smaller than the maximum that it can carry.
|
||
Head.msgh_size += sizeof(old_state[0]) * old_stateCnt - sizeof(old_state);
|
||
}
|
||
|
||
// Because the message size has been adjusted, the trailer may not appear in
|
||
// its home member variable. This computes the actual address of the trailer.
|
||
const mach_msg_trailer_t* Trailer() const {
|
||
return MachMessageTrailerFromHeader(&Head);
|
||
}
|
||
|
||
mach_msg_header_t Head;
|
||
mach_msg_body_t msgh_body;
|
||
mach_msg_port_descriptor_t thread;
|
||
mach_msg_port_descriptor_t task;
|
||
NDR_record_t NDR;
|
||
exception_type_t exception;
|
||
mach_msg_type_number_t codeCnt;
|
||
int64_t code[2];
|
||
int flavor;
|
||
mach_msg_type_number_t old_stateCnt;
|
||
natural_t old_state[THREAD_STATE_MAX];
|
||
mach_msg_trailer_t trailer;
|
||
};
|
||
|
||
// The reply messages for exception_raise_state_identity and
|
||
// mach_exception_raise_state_identity are identical.
|
||
using MachExceptionRaiseStateIdentityReply = ExceptionRaiseStateIdentityReply;
|
||
|
||
// InvalidRequest and BadIDErrorReply are used to test that
|
||
// UniversalMachExcServer deals appropriately with messages that it does not
|
||
// understand: messages with an unknown Head.msgh_id.
|
||
|
||
struct InvalidRequest : public mach_msg_empty_send_t {
|
||
explicit InvalidRequest(mach_msg_id_t id) {
|
||
memset(this, 0xa5, sizeof(*this));
|
||
header.msgh_bits =
|
||
MACH_MSGH_BITS(MACH_MSG_TYPE_PORT_SEND_ONCE, MACH_MSG_TYPE_PORT_SEND);
|
||
header.msgh_size = sizeof(*this);
|
||
header.msgh_remote_port = kClientRemotePort;
|
||
header.msgh_local_port = kServerLocalPort;
|
||
header.msgh_id = id;
|
||
}
|
||
};
|
||
|
||
struct BadIDErrorReply : public mig_reply_error_t {
|
||
BadIDErrorReply() {
|
||
memset(this, 0x5a, sizeof(*this));
|
||
RetCode = KERN_FAILURE;
|
||
}
|
||
|
||
void Verify(mach_msg_id_t id) {
|
||
EXPECT_EQ(Head.msgh_bits,
|
||
implicit_cast<mach_msg_bits_t>(
|
||
MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND_ONCE, 0)));
|
||
EXPECT_EQ(Head.msgh_size, sizeof(*this));
|
||
EXPECT_EQ(Head.msgh_remote_port, kClientRemotePort);
|
||
EXPECT_EQ(Head.msgh_local_port, kMachPortNull);
|
||
EXPECT_EQ(Head.msgh_id, id + 100);
|
||
EXPECT_EQ(memcmp(&NDR, &NDR_record, sizeof(NDR)), 0);
|
||
EXPECT_EQ(RetCode, MIG_BAD_ID);
|
||
}
|
||
};
|
||
|
||
class MockUniversalMachExcServer : public UniversalMachExcServer::Interface {
|
||
public:
|
||
struct ConstExceptionCodes {
|
||
const mach_exception_data_type_t* code;
|
||
mach_msg_type_number_t code_count;
|
||
};
|
||
struct ThreadStateAndCount {
|
||
thread_state_t state;
|
||
mach_msg_type_number_t* state_count;
|
||
};
|
||
struct ConstThreadStateAndCount {
|
||
ConstThreadState state;
|
||
mach_msg_type_number_t* state_count;
|
||
};
|
||
|
||
// UniversalMachExcServer::Interface:
|
||
|
||
// CatchMachException is the method to mock, but it has 13 parameters, and
|
||
// Google Mock can only mock methods with up to 10 parameters. Coalesce some
|
||
// related parameters together into structs, and call a mocked method.
|
||
virtual kern_return_t CatchMachException(
|
||
exception_behavior_t behavior,
|
||
exception_handler_t exception_port,
|
||
thread_t thread,
|
||
task_t task,
|
||
exception_type_t exception,
|
||
const mach_exception_data_type_t* code,
|
||
mach_msg_type_number_t code_count,
|
||
thread_state_flavor_t* flavor,
|
||
ConstThreadState old_state,
|
||
mach_msg_type_number_t old_state_count,
|
||
thread_state_t new_state,
|
||
mach_msg_type_number_t* new_state_count,
|
||
const mach_msg_trailer_t* trailer,
|
||
bool* destroy_complex_request) override {
|
||
*destroy_complex_request = true;
|
||
const ConstExceptionCodes exception_codes = {code, code_count};
|
||
const ConstThreadStateAndCount old_thread_state = {old_state,
|
||
&old_state_count};
|
||
ThreadStateAndCount new_thread_state = {new_state, new_state_count};
|
||
return MockCatchMachException(behavior,
|
||
exception_port,
|
||
thread,
|
||
task,
|
||
exception,
|
||
&exception_codes,
|
||
flavor,
|
||
&old_thread_state,
|
||
&new_thread_state,
|
||
trailer);
|
||
}
|
||
|
||
MOCK_METHOD(kern_return_t,
|
||
MockCatchMachException,
|
||
(exception_behavior_t behavior,
|
||
exception_handler_t exception_port,
|
||
thread_t thread,
|
||
task_t task,
|
||
exception_type_t exception,
|
||
const ConstExceptionCodes* exception_codes,
|
||
thread_state_flavor_t* flavor,
|
||
const ConstThreadStateAndCount* old_thread_state,
|
||
ThreadStateAndCount* new_thread_state,
|
||
const mach_msg_trailer_t* trailer));
|
||
};
|
||
|
||
// Matcher for ConstExceptionCodes, testing that it carries 2 codes matching
|
||
// code_0 and code_1.
|
||
MATCHER_P2(AreExceptionCodes, code_0, code_1, "") {
|
||
if (!arg) {
|
||
return false;
|
||
}
|
||
|
||
if (arg->code_count == 2 && arg->code[0] == code_0 &&
|
||
arg->code[1] == code_1) {
|
||
return true;
|
||
}
|
||
|
||
*result_listener << "codes (";
|
||
for (size_t index = 0; index < arg->code_count; ++index) {
|
||
*result_listener << arg->code[index];
|
||
if (index < arg->code_count - 1) {
|
||
*result_listener << ", ";
|
||
}
|
||
}
|
||
*result_listener << ")";
|
||
|
||
return false;
|
||
}
|
||
|
||
// Matcher for ThreadStateAndCount and ConstThreadStateAndCount, testing that
|
||
// *state_count is present and matches the specified value. If 0 is specified
|
||
// for the count, |state| must be nullptr (not present), otherwise |state| must
|
||
// not be nullptr (present).
|
||
MATCHER_P(IsThreadStateAndCount, state_count, "") {
|
||
if (!arg) {
|
||
return false;
|
||
}
|
||
if (!arg->state_count) {
|
||
*result_listener << "state_count nullptr";
|
||
return false;
|
||
}
|
||
if (*(arg->state_count) != state_count) {
|
||
*result_listener << "*state_count " << *(arg->state_count);
|
||
return false;
|
||
}
|
||
if (state_count) {
|
||
if (!arg->state) {
|
||
*result_listener << "*state_count " << state_count << ", state nullptr";
|
||
return false;
|
||
}
|
||
} else {
|
||
if (arg->state) {
|
||
*result_listener << "*state_count 0, state non-nullptr (" << arg->state
|
||
<< ")";
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
template <typename T>
|
||
class ScopedDefaultValue {
|
||
public:
|
||
explicit ScopedDefaultValue(const T& default_value) {
|
||
DefaultValue<T>::Set(default_value);
|
||
}
|
||
|
||
~ScopedDefaultValue() { DefaultValue<T>::Clear(); }
|
||
};
|
||
|
||
TEST(ExcServerVariants, MockExceptionRaise) {
|
||
ScopedDefaultValue<kern_return_t> default_kern_return_t(KERN_FAILURE);
|
||
|
||
MockUniversalMachExcServer server;
|
||
UniversalMachExcServer universal_mach_exc_server(&server);
|
||
|
||
std::set<mach_msg_id_t> ids =
|
||
universal_mach_exc_server.MachMessageServerRequestIDs();
|
||
EXPECT_NE(ids.find(2401), ids.end()); // There is no constant for this.
|
||
|
||
ExceptionRaiseRequest request;
|
||
EXPECT_LE(request.Head.msgh_size,
|
||
universal_mach_exc_server.MachMessageServerRequestSize());
|
||
|
||
ExceptionRaiseReply reply;
|
||
EXPECT_LE(sizeof(reply),
|
||
universal_mach_exc_server.MachMessageServerReplySize());
|
||
|
||
constexpr exception_behavior_t kExceptionBehavior = EXCEPTION_DEFAULT;
|
||
|
||
EXPECT_CALL(server,
|
||
MockCatchMachException(kExceptionBehavior,
|
||
kServerLocalPort,
|
||
kExceptionThreadPort,
|
||
kExceptionTaskPort,
|
||
kExceptionType,
|
||
AreExceptionCodes(kTestExceptonCodes[0],
|
||
kTestExceptonCodes[1]),
|
||
Pointee(Eq(THREAD_STATE_NONE)),
|
||
IsThreadStateAndCount(0u),
|
||
IsThreadStateAndCount(0u),
|
||
Eq(&request.trailer)))
|
||
.WillOnce(Return(KERN_SUCCESS))
|
||
.RetiresOnSaturation();
|
||
|
||
bool destroy_complex_request = false;
|
||
EXPECT_TRUE(universal_mach_exc_server.MachMessageServerFunction(
|
||
reinterpret_cast<mach_msg_header_t*>(&request),
|
||
reinterpret_cast<mach_msg_header_t*>(&reply),
|
||
&destroy_complex_request));
|
||
EXPECT_TRUE(destroy_complex_request);
|
||
|
||
reply.Verify(kExceptionBehavior);
|
||
}
|
||
|
||
TEST(ExcServerVariants, MockExceptionRaiseState) {
|
||
ScopedDefaultValue<kern_return_t> default_kern_return_t(KERN_FAILURE);
|
||
|
||
MockUniversalMachExcServer server;
|
||
UniversalMachExcServer universal_mach_exc_server(&server);
|
||
|
||
std::set<mach_msg_id_t> ids =
|
||
universal_mach_exc_server.MachMessageServerRequestIDs();
|
||
EXPECT_NE(ids.find(2402), ids.end()); // There is no constant for this.
|
||
|
||
ExceptionRaiseStateRequest request;
|
||
EXPECT_LE(request.Head.msgh_size,
|
||
universal_mach_exc_server.MachMessageServerRequestSize());
|
||
|
||
ExceptionRaiseStateReply reply;
|
||
EXPECT_LE(sizeof(reply),
|
||
universal_mach_exc_server.MachMessageServerReplySize());
|
||
|
||
constexpr exception_behavior_t kExceptionBehavior = EXCEPTION_STATE;
|
||
|
||
EXPECT_CALL(
|
||
server,
|
||
MockCatchMachException(
|
||
kExceptionBehavior,
|
||
kServerLocalPort,
|
||
THREAD_NULL,
|
||
TASK_NULL,
|
||
kExceptionType,
|
||
AreExceptionCodes(kTestExceptonCodes[0], kTestExceptonCodes[1]),
|
||
Pointee(Eq(kThreadStateFlavor)),
|
||
IsThreadStateAndCount(kThreadStateFlavorCount),
|
||
IsThreadStateAndCount(std::size(reply.new_state)),
|
||
Eq(request.Trailer())))
|
||
.WillOnce(Return(KERN_SUCCESS))
|
||
.RetiresOnSaturation();
|
||
|
||
bool destroy_complex_request = false;
|
||
EXPECT_TRUE(universal_mach_exc_server.MachMessageServerFunction(
|
||
reinterpret_cast<mach_msg_header_t*>(&request),
|
||
reinterpret_cast<mach_msg_header_t*>(&reply),
|
||
&destroy_complex_request));
|
||
|
||
// The request wasn’t complex, so nothing got a chance to change the value of
|
||
// this variable.
|
||
EXPECT_FALSE(destroy_complex_request);
|
||
|
||
reply.Verify(kExceptionBehavior);
|
||
}
|
||
|
||
TEST(ExcServerVariants, MockExceptionRaiseStateIdentity) {
|
||
ScopedDefaultValue<kern_return_t> default_kern_return_t(KERN_FAILURE);
|
||
|
||
MockUniversalMachExcServer server;
|
||
UniversalMachExcServer universal_mach_exc_server(&server);
|
||
|
||
std::set<mach_msg_id_t> ids =
|
||
universal_mach_exc_server.MachMessageServerRequestIDs();
|
||
EXPECT_NE(ids.find(2403), ids.end()); // There is no constant for this.
|
||
|
||
ExceptionRaiseStateIdentityRequest request;
|
||
EXPECT_LE(request.Head.msgh_size,
|
||
universal_mach_exc_server.MachMessageServerRequestSize());
|
||
|
||
ExceptionRaiseStateIdentityReply reply;
|
||
EXPECT_LE(sizeof(reply),
|
||
universal_mach_exc_server.MachMessageServerReplySize());
|
||
|
||
constexpr exception_behavior_t kExceptionBehavior = EXCEPTION_STATE_IDENTITY;
|
||
|
||
EXPECT_CALL(
|
||
server,
|
||
MockCatchMachException(
|
||
kExceptionBehavior,
|
||
kServerLocalPort,
|
||
kExceptionThreadPort,
|
||
kExceptionTaskPort,
|
||
kExceptionType,
|
||
AreExceptionCodes(kTestExceptonCodes[0], kTestExceptonCodes[1]),
|
||
Pointee(Eq(kThreadStateFlavor)),
|
||
IsThreadStateAndCount(kThreadStateFlavorCount),
|
||
IsThreadStateAndCount(std::size(reply.new_state)),
|
||
Eq(request.Trailer())))
|
||
.WillOnce(Return(KERN_SUCCESS))
|
||
.RetiresOnSaturation();
|
||
|
||
bool destroy_complex_request = false;
|
||
EXPECT_TRUE(universal_mach_exc_server.MachMessageServerFunction(
|
||
reinterpret_cast<mach_msg_header_t*>(&request),
|
||
reinterpret_cast<mach_msg_header_t*>(&reply),
|
||
&destroy_complex_request));
|
||
EXPECT_TRUE(destroy_complex_request);
|
||
|
||
reply.Verify(kExceptionBehavior);
|
||
}
|
||
|
||
TEST(ExcServerVariants, MockMachExceptionRaise) {
|
||
ScopedDefaultValue<kern_return_t> default_kern_return_t(KERN_FAILURE);
|
||
|
||
MockUniversalMachExcServer server;
|
||
UniversalMachExcServer universal_mach_exc_server(&server);
|
||
|
||
std::set<mach_msg_id_t> ids =
|
||
universal_mach_exc_server.MachMessageServerRequestIDs();
|
||
EXPECT_NE(ids.find(2405), ids.end()); // There is no constant for this.
|
||
|
||
MachExceptionRaiseRequest request;
|
||
EXPECT_LE(request.Head.msgh_size,
|
||
universal_mach_exc_server.MachMessageServerRequestSize());
|
||
|
||
MachExceptionRaiseReply reply;
|
||
EXPECT_LE(sizeof(reply),
|
||
universal_mach_exc_server.MachMessageServerReplySize());
|
||
|
||
constexpr exception_behavior_t kExceptionBehavior =
|
||
EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES;
|
||
|
||
EXPECT_CALL(
|
||
server,
|
||
MockCatchMachException(kExceptionBehavior,
|
||
kServerLocalPort,
|
||
kExceptionThreadPort,
|
||
kExceptionTaskPort,
|
||
kExceptionType,
|
||
AreExceptionCodes(kTestMachExceptionCodes[0],
|
||
kTestMachExceptionCodes[1]),
|
||
Pointee(Eq(THREAD_STATE_NONE)),
|
||
IsThreadStateAndCount(0u),
|
||
IsThreadStateAndCount(0u),
|
||
Eq(&request.trailer)))
|
||
.WillOnce(Return(KERN_SUCCESS))
|
||
.RetiresOnSaturation();
|
||
|
||
bool destroy_complex_request = false;
|
||
EXPECT_TRUE(universal_mach_exc_server.MachMessageServerFunction(
|
||
reinterpret_cast<mach_msg_header_t*>(&request),
|
||
reinterpret_cast<mach_msg_header_t*>(&reply),
|
||
&destroy_complex_request));
|
||
EXPECT_TRUE(destroy_complex_request);
|
||
|
||
reply.Verify(kExceptionBehavior);
|
||
}
|
||
|
||
TEST(ExcServerVariants, MockMachExceptionRaiseState) {
|
||
ScopedDefaultValue<kern_return_t> default_kern_return_t(KERN_FAILURE);
|
||
|
||
MockUniversalMachExcServer server;
|
||
UniversalMachExcServer universal_mach_exc_server(&server);
|
||
|
||
std::set<mach_msg_id_t> ids =
|
||
universal_mach_exc_server.MachMessageServerRequestIDs();
|
||
EXPECT_NE(ids.find(2406), ids.end()); // There is no constant for this.
|
||
|
||
MachExceptionRaiseStateRequest request;
|
||
EXPECT_LE(request.Head.msgh_size,
|
||
universal_mach_exc_server.MachMessageServerRequestSize());
|
||
|
||
MachExceptionRaiseStateReply reply;
|
||
EXPECT_LE(sizeof(reply),
|
||
universal_mach_exc_server.MachMessageServerReplySize());
|
||
|
||
constexpr exception_behavior_t kExceptionBehavior =
|
||
EXCEPTION_STATE | MACH_EXCEPTION_CODES;
|
||
|
||
EXPECT_CALL(
|
||
server,
|
||
MockCatchMachException(kExceptionBehavior,
|
||
kServerLocalPort,
|
||
THREAD_NULL,
|
||
TASK_NULL,
|
||
kExceptionType,
|
||
AreExceptionCodes(kTestMachExceptionCodes[0],
|
||
kTestMachExceptionCodes[1]),
|
||
Pointee(Eq(kThreadStateFlavor)),
|
||
IsThreadStateAndCount(kThreadStateFlavorCount),
|
||
IsThreadStateAndCount(std::size(reply.new_state)),
|
||
Eq(request.Trailer())))
|
||
.WillOnce(Return(KERN_SUCCESS))
|
||
.RetiresOnSaturation();
|
||
|
||
bool destroy_complex_request = false;
|
||
EXPECT_TRUE(universal_mach_exc_server.MachMessageServerFunction(
|
||
reinterpret_cast<mach_msg_header_t*>(&request),
|
||
reinterpret_cast<mach_msg_header_t*>(&reply),
|
||
&destroy_complex_request));
|
||
|
||
// The request wasn’t complex, so nothing got a chance to change the value of
|
||
// this variable.
|
||
EXPECT_FALSE(destroy_complex_request);
|
||
|
||
reply.Verify(kExceptionBehavior);
|
||
}
|
||
|
||
TEST(ExcServerVariants, MockMachExceptionRaiseStateIdentity) {
|
||
ScopedDefaultValue<kern_return_t> default_kern_return_t(KERN_FAILURE);
|
||
|
||
MockUniversalMachExcServer server;
|
||
UniversalMachExcServer universal_mach_exc_server(&server);
|
||
|
||
std::set<mach_msg_id_t> ids =
|
||
universal_mach_exc_server.MachMessageServerRequestIDs();
|
||
EXPECT_NE(ids.find(2407), ids.end()); // There is no constant for this.
|
||
|
||
MachExceptionRaiseStateIdentityRequest request;
|
||
EXPECT_LE(request.Head.msgh_size,
|
||
universal_mach_exc_server.MachMessageServerRequestSize());
|
||
|
||
MachExceptionRaiseStateIdentityReply reply;
|
||
EXPECT_LE(sizeof(reply),
|
||
universal_mach_exc_server.MachMessageServerReplySize());
|
||
|
||
constexpr exception_behavior_t kExceptionBehavior =
|
||
EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES;
|
||
|
||
EXPECT_CALL(
|
||
server,
|
||
MockCatchMachException(kExceptionBehavior,
|
||
kServerLocalPort,
|
||
kExceptionThreadPort,
|
||
kExceptionTaskPort,
|
||
kExceptionType,
|
||
AreExceptionCodes(kTestMachExceptionCodes[0],
|
||
kTestMachExceptionCodes[1]),
|
||
Pointee(Eq(kThreadStateFlavor)),
|
||
IsThreadStateAndCount(kThreadStateFlavorCount),
|
||
IsThreadStateAndCount(std::size(reply.new_state)),
|
||
Eq(request.Trailer())))
|
||
.WillOnce(Return(KERN_SUCCESS))
|
||
.RetiresOnSaturation();
|
||
|
||
bool destroy_complex_request = false;
|
||
EXPECT_TRUE(universal_mach_exc_server.MachMessageServerFunction(
|
||
reinterpret_cast<mach_msg_header_t*>(&request),
|
||
reinterpret_cast<mach_msg_header_t*>(&reply),
|
||
&destroy_complex_request));
|
||
EXPECT_TRUE(destroy_complex_request);
|
||
|
||
reply.Verify(kExceptionBehavior);
|
||
}
|
||
|
||
TEST(ExcServerVariants, MockUnknownID) {
|
||
ScopedDefaultValue<kern_return_t> default_kern_return_t(KERN_FAILURE);
|
||
|
||
MockUniversalMachExcServer server;
|
||
UniversalMachExcServer universal_mach_exc_server(&server);
|
||
|
||
// Make sure that a message with an unknown ID is handled appropriately.
|
||
// UniversalMachExcServer should not dispatch the message to
|
||
// MachMessageServerFunction, but should generate a MIG_BAD_ID error reply.
|
||
|
||
static constexpr mach_msg_id_t unknown_ids[] = {
|
||
// Reasonable things to check.
|
||
-101,
|
||
-100,
|
||
-99,
|
||
-1,
|
||
0,
|
||
1,
|
||
99,
|
||
100,
|
||
101,
|
||
|
||
// Invalid IDs right around valid ones.
|
||
2400,
|
||
2404,
|
||
2408,
|
||
|
||
// Valid and invalid IDs in the range used for replies, not requests.
|
||
2500,
|
||
2501,
|
||
2502,
|
||
2503,
|
||
2504,
|
||
2505,
|
||
2506,
|
||
2507,
|
||
2508,
|
||
};
|
||
|
||
for (size_t index = 0; index < std::size(unknown_ids); ++index) {
|
||
mach_msg_id_t id = unknown_ids[index];
|
||
|
||
SCOPED_TRACE(base::StringPrintf("unknown id %d", id));
|
||
|
||
std::set<mach_msg_id_t> ids =
|
||
universal_mach_exc_server.MachMessageServerRequestIDs();
|
||
EXPECT_EQ(ids.find(id), ids.end());
|
||
|
||
InvalidRequest request(id);
|
||
EXPECT_LE(sizeof(request),
|
||
universal_mach_exc_server.MachMessageServerRequestSize());
|
||
|
||
BadIDErrorReply reply;
|
||
EXPECT_LE(sizeof(reply),
|
||
universal_mach_exc_server.MachMessageServerReplySize());
|
||
|
||
bool destroy_complex_request = false;
|
||
EXPECT_FALSE(universal_mach_exc_server.MachMessageServerFunction(
|
||
reinterpret_cast<mach_msg_header_t*>(&request),
|
||
reinterpret_cast<mach_msg_header_t*>(&reply),
|
||
&destroy_complex_request));
|
||
|
||
// The request wasn’t handled, nothing got a chance to change the value of
|
||
// this variable. MachMessageServer would destroy the request if it was
|
||
// complex, regardless of what was done to this variable, because the
|
||
// return code was not KERN_SUCCESS or MIG_NO_REPLY.
|
||
EXPECT_FALSE(destroy_complex_request);
|
||
|
||
reply.Verify(id);
|
||
}
|
||
}
|
||
|
||
TEST(ExcServerVariants, MachMessageServerRequestIDs) {
|
||
std::set<mach_msg_id_t> expect_request_ids;
|
||
|
||
// There are no constants for these.
|
||
expect_request_ids.insert(2401);
|
||
expect_request_ids.insert(2402);
|
||
expect_request_ids.insert(2403);
|
||
expect_request_ids.insert(2405);
|
||
expect_request_ids.insert(2406);
|
||
expect_request_ids.insert(2407);
|
||
|
||
MockUniversalMachExcServer server;
|
||
UniversalMachExcServer universal_mach_exc_server(&server);
|
||
|
||
EXPECT_EQ(universal_mach_exc_server.MachMessageServerRequestIDs(),
|
||
expect_request_ids);
|
||
}
|
||
|
||
#if BUILDFLAG(IS_MAC)
|
||
|
||
class TestExcServerVariants : public MachMultiprocess,
|
||
public UniversalMachExcServer::Interface {
|
||
public:
|
||
TestExcServerVariants(exception_behavior_t behavior,
|
||
thread_state_flavor_t flavor,
|
||
mach_msg_type_number_t state_count)
|
||
: MachMultiprocess(),
|
||
UniversalMachExcServer::Interface(),
|
||
behavior_(behavior),
|
||
flavor_(flavor),
|
||
state_count_(state_count),
|
||
handled_(false) {
|
||
SetExpectedChildTerminationBuiltinTrap();
|
||
}
|
||
|
||
TestExcServerVariants(const TestExcServerVariants&) = delete;
|
||
TestExcServerVariants& operator=(const TestExcServerVariants&) = delete;
|
||
|
||
// UniversalMachExcServer::Interface:
|
||
|
||
virtual kern_return_t CatchMachException(
|
||
exception_behavior_t behavior,
|
||
exception_handler_t exception_port,
|
||
thread_t thread,
|
||
task_t task,
|
||
exception_type_t exception,
|
||
const mach_exception_data_type_t* code,
|
||
mach_msg_type_number_t code_count,
|
||
thread_state_flavor_t* flavor,
|
||
ConstThreadState old_state,
|
||
mach_msg_type_number_t old_state_count,
|
||
thread_state_t new_state,
|
||
mach_msg_type_number_t* new_state_count,
|
||
const mach_msg_trailer_t* trailer,
|
||
bool* destroy_complex_request) override {
|
||
*destroy_complex_request = true;
|
||
|
||
EXPECT_FALSE(handled_);
|
||
handled_ = true;
|
||
|
||
EXPECT_EQ(behavior, behavior_);
|
||
|
||
EXPECT_EQ(exception_port, LocalPort());
|
||
|
||
if (ExceptionBehaviorHasIdentity(behavior)) {
|
||
EXPECT_NE(thread, THREAD_NULL);
|
||
EXPECT_EQ(task, ChildTask());
|
||
} else {
|
||
EXPECT_EQ(thread, THREAD_NULL);
|
||
EXPECT_EQ(task, TASK_NULL);
|
||
}
|
||
|
||
EXPECT_EQ(exception, EXC_CRASH);
|
||
EXPECT_EQ(code_count, 2u);
|
||
|
||
// The exception and code_count checks above would ideally use ASSERT_EQ so
|
||
// that the next conditional would not be necessary, but ASSERT_* requires a
|
||
// function returning type void, and the interface dictates otherwise here.
|
||
if (exception == EXC_CRASH && code_count >= 1) {
|
||
int signal;
|
||
ExcCrashRecoverOriginalException(code[0], nullptr, &signal);
|
||
}
|
||
|
||
const bool has_state = ExceptionBehaviorHasState(behavior);
|
||
if (has_state) {
|
||
EXPECT_EQ(*flavor, flavor_);
|
||
EXPECT_EQ(old_state_count, state_count_);
|
||
EXPECT_NE(old_state, nullptr);
|
||
EXPECT_EQ(*new_state_count,
|
||
implicit_cast<mach_msg_type_number_t>(THREAD_STATE_MAX));
|
||
EXPECT_NE(new_state, nullptr);
|
||
} else {
|
||
EXPECT_EQ(*flavor, THREAD_STATE_NONE);
|
||
EXPECT_EQ(old_state_count, 0u);
|
||
EXPECT_EQ(old_state, nullptr);
|
||
EXPECT_EQ(*new_state_count, 0u);
|
||
EXPECT_EQ(new_state, nullptr);
|
||
}
|
||
|
||
EXPECT_EQ(
|
||
trailer->msgh_trailer_type,
|
||
implicit_cast<mach_msg_trailer_type_t>(MACH_MSG_TRAILER_FORMAT_0));
|
||
EXPECT_EQ(trailer->msgh_trailer_size,
|
||
REQUESTED_TRAILER_SIZE(kMachMessageOptions));
|
||
|
||
ExcServerCopyState(
|
||
behavior, old_state, old_state_count, new_state, new_state_count);
|
||
|
||
return ExcServerSuccessfulReturnValue(exception, behavior, false);
|
||
}
|
||
|
||
private:
|
||
// MachMultiprocess:
|
||
|
||
void MachMultiprocessParent() override {
|
||
UniversalMachExcServer universal_mach_exc_server(this);
|
||
|
||
kern_return_t kr =
|
||
MachMessageServer::Run(&universal_mach_exc_server,
|
||
LocalPort(),
|
||
kMachMessageOptions,
|
||
MachMessageServer::kOneShot,
|
||
MachMessageServer::kReceiveLargeError,
|
||
kMachMessageTimeoutWaitIndefinitely);
|
||
EXPECT_EQ(kr, KERN_SUCCESS)
|
||
<< MachErrorMessage(kr, "MachMessageServer::Run");
|
||
|
||
EXPECT_TRUE(handled_);
|
||
}
|
||
|
||
void MachMultiprocessChild() override {
|
||
// Set the parent as the exception handler for EXC_CRASH.
|
||
kern_return_t kr = task_set_exception_ports(
|
||
mach_task_self(), EXC_MASK_CRASH, RemotePort(), behavior_, flavor_);
|
||
ASSERT_EQ(kr, KERN_SUCCESS)
|
||
<< MachErrorMessage(kr, "task_set_exception_ports");
|
||
|
||
// Now crash.
|
||
__builtin_trap();
|
||
}
|
||
|
||
exception_behavior_t behavior_;
|
||
thread_state_flavor_t flavor_;
|
||
mach_msg_type_number_t state_count_;
|
||
bool handled_;
|
||
|
||
static const mach_msg_option_t kMachMessageOptions =
|
||
MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0);
|
||
};
|
||
|
||
TEST(ExcServerVariants, ExceptionRaise) {
|
||
TestExcServerVariants test_exc_server_variants(
|
||
EXCEPTION_DEFAULT, THREAD_STATE_NONE, 0);
|
||
test_exc_server_variants.Run();
|
||
}
|
||
|
||
TEST(ExcServerVariants, ExceptionRaiseState) {
|
||
TestExcServerVariants test_exc_server_variants(
|
||
EXCEPTION_STATE, MACHINE_THREAD_STATE, MACHINE_THREAD_STATE_COUNT);
|
||
test_exc_server_variants.Run();
|
||
}
|
||
|
||
TEST(ExcServerVariants, ExceptionRaiseStateIdentity) {
|
||
TestExcServerVariants test_exc_server_variants(EXCEPTION_STATE_IDENTITY,
|
||
MACHINE_THREAD_STATE,
|
||
MACHINE_THREAD_STATE_COUNT);
|
||
test_exc_server_variants.Run();
|
||
}
|
||
|
||
TEST(ExcServerVariants, MachExceptionRaise) {
|
||
TestExcServerVariants test_exc_server_variants(
|
||
MACH_EXCEPTION_CODES | EXCEPTION_DEFAULT, THREAD_STATE_NONE, 0);
|
||
test_exc_server_variants.Run();
|
||
}
|
||
|
||
TEST(ExcServerVariants, MachExceptionRaiseState) {
|
||
TestExcServerVariants test_exc_server_variants(
|
||
MACH_EXCEPTION_CODES | EXCEPTION_STATE,
|
||
MACHINE_THREAD_STATE,
|
||
MACHINE_THREAD_STATE_COUNT);
|
||
test_exc_server_variants.Run();
|
||
}
|
||
|
||
TEST(ExcServerVariants, MachExceptionRaiseStateIdentity) {
|
||
TestExcServerVariants test_exc_server_variants(
|
||
MACH_EXCEPTION_CODES | EXCEPTION_STATE_IDENTITY,
|
||
MACHINE_THREAD_STATE,
|
||
MACHINE_THREAD_STATE_COUNT);
|
||
test_exc_server_variants.Run();
|
||
}
|
||
|
||
TEST(ExcServerVariants, ThreadStates) {
|
||
// So far, all of the tests worked with MACHINE_THREAD_STATE. Now try all of
|
||
// the other thread state flavors that are expected to work.
|
||
|
||
static constexpr struct {
|
||
thread_state_flavor_t flavor;
|
||
mach_msg_type_number_t count;
|
||
} test_data[] = {
|
||
#if defined(ARCH_CPU_X86_FAMILY)
|
||
// For the x86 family, exception handlers can only properly receive the
|
||
// thread, float, and exception state flavors. There’s a bug in the kernel
|
||
// that causes it to call thread_getstatus() (a wrapper for the more
|
||
// familiar thread_get_state()) with an incorrect state buffer size
|
||
// parameter when delivering an exception. 10.9.4
|
||
// xnu-2422.110.17/osfmk/kern/exception.c exception_deliver() uses the
|
||
// _MachineStateCount[] array indexed by the flavor number to obtain the
|
||
// buffer size. 10.9.4 xnu-2422.110.17/osfmk/i386/pcb.c contains the
|
||
// definition of this array for the x86 family. The slots corresponding to
|
||
// thread, float, and exception state flavors in both native-width (32- and
|
||
// 64-bit) and universal are correct, but the remaining elements in the
|
||
// array are not. This includes elements that would correspond to debug and
|
||
// AVX state flavors, so these cannot be tested here.
|
||
//
|
||
// When machine_thread_get_state() (the machine-specific implementation of
|
||
// thread_get_state()) encounters an undersized buffer as reported by the
|
||
// buffer size parameter, it returns KERN_INVALID_ARGUMENT, which causes
|
||
// exception_deliver() to not actually deliver the exception and instead
|
||
// return that error code to exception_triage() as well.
|
||
//
|
||
// This bug is filed as radar 18312067.
|
||
//
|
||
// Additionaly, the AVX state flavors are also not tested because they’re
|
||
// not available on all CPUs and OS versions.
|
||
{x86_THREAD_STATE, x86_THREAD_STATE_COUNT},
|
||
{x86_FLOAT_STATE, x86_FLOAT_STATE_COUNT},
|
||
{x86_EXCEPTION_STATE, x86_EXCEPTION_STATE_COUNT},
|
||
#if defined(ARCH_CPU_X86)
|
||
{x86_THREAD_STATE32, x86_THREAD_STATE32_COUNT},
|
||
{x86_FLOAT_STATE32, x86_FLOAT_STATE32_COUNT},
|
||
{x86_EXCEPTION_STATE32, x86_EXCEPTION_STATE32_COUNT},
|
||
#elif defined(ARCH_CPU_X86_64)
|
||
{x86_THREAD_STATE64, x86_THREAD_STATE64_COUNT},
|
||
{x86_FLOAT_STATE64, x86_FLOAT_STATE64_COUNT},
|
||
{x86_EXCEPTION_STATE64, x86_EXCEPTION_STATE64_COUNT},
|
||
#endif
|
||
#elif defined(ARCH_CPU_ARM64)
|
||
{ARM_UNIFIED_THREAD_STATE, ARM_UNIFIED_THREAD_STATE_COUNT},
|
||
{ARM_THREAD_STATE64, ARM_THREAD_STATE64_COUNT},
|
||
{ARM_NEON_STATE64, ARM_NEON_STATE64_COUNT},
|
||
{ARM_EXCEPTION_STATE64, ARM_EXCEPTION_STATE64_COUNT},
|
||
#else
|
||
#error Port this test to your CPU architecture.
|
||
#endif
|
||
};
|
||
|
||
for (size_t index = 0; index < std::size(test_data); ++index) {
|
||
const auto& test = test_data[index];
|
||
SCOPED_TRACE(
|
||
base::StringPrintf("index %zu, flavor %d", index, test.flavor));
|
||
|
||
TestExcServerVariants test_exc_server_variants(
|
||
MACH_EXCEPTION_CODES | EXCEPTION_STATE_IDENTITY,
|
||
test.flavor,
|
||
test.count);
|
||
test_exc_server_variants.Run();
|
||
}
|
||
}
|
||
|
||
#endif // BUILDFLAG(IS_MAC)
|
||
|
||
TEST(ExcServerVariants, ExcServerSuccessfulReturnValue) {
|
||
#if BUILDFLAG(IS_IOS)
|
||
// iOS 9 ≅ OS X 10.11.
|
||
const kern_return_t prefer_not_set_thread_state = KERN_SUCCESS;
|
||
#else
|
||
const kern_return_t prefer_not_set_thread_state =
|
||
MacOSVersionNumber() < 10'11'00 ? MACH_RCV_PORT_DIED : KERN_SUCCESS;
|
||
#endif
|
||
|
||
const struct {
|
||
exception_type_t exception;
|
||
exception_behavior_t behavior;
|
||
bool set_thread_state;
|
||
kern_return_t kr;
|
||
} kTestData[] = {
|
||
{EXC_CRASH, EXCEPTION_DEFAULT, false, KERN_SUCCESS},
|
||
{EXC_CRASH, EXCEPTION_STATE, false, prefer_not_set_thread_state},
|
||
{EXC_CRASH, EXCEPTION_STATE_IDENTITY, false, prefer_not_set_thread_state},
|
||
{EXC_CRASH, kMachExceptionCodes | EXCEPTION_DEFAULT, false, KERN_SUCCESS},
|
||
{EXC_CRASH,
|
||
kMachExceptionCodes | EXCEPTION_STATE,
|
||
false,
|
||
prefer_not_set_thread_state},
|
||
{EXC_CRASH,
|
||
kMachExceptionCodes | EXCEPTION_STATE_IDENTITY,
|
||
false,
|
||
prefer_not_set_thread_state},
|
||
{EXC_CRASH, EXCEPTION_DEFAULT, true, KERN_SUCCESS},
|
||
{EXC_CRASH, EXCEPTION_STATE, true, KERN_SUCCESS},
|
||
{EXC_CRASH, EXCEPTION_STATE_IDENTITY, true, KERN_SUCCESS},
|
||
{EXC_CRASH, kMachExceptionCodes | EXCEPTION_DEFAULT, true, KERN_SUCCESS},
|
||
{EXC_CRASH, kMachExceptionCodes | EXCEPTION_STATE, true, KERN_SUCCESS},
|
||
{EXC_CRASH,
|
||
kMachExceptionCodes | EXCEPTION_STATE_IDENTITY,
|
||
true,
|
||
KERN_SUCCESS},
|
||
{EXC_BAD_ACCESS, EXCEPTION_DEFAULT, false, KERN_SUCCESS},
|
||
{EXC_BAD_INSTRUCTION, EXCEPTION_STATE, false, MACH_RCV_PORT_DIED},
|
||
{EXC_ARITHMETIC, EXCEPTION_STATE_IDENTITY, false, MACH_RCV_PORT_DIED},
|
||
{EXC_EMULATION,
|
||
kMachExceptionCodes | EXCEPTION_DEFAULT,
|
||
false,
|
||
KERN_SUCCESS},
|
||
{EXC_SOFTWARE,
|
||
kMachExceptionCodes | EXCEPTION_STATE,
|
||
false,
|
||
MACH_RCV_PORT_DIED},
|
||
{EXC_BREAKPOINT,
|
||
kMachExceptionCodes | EXCEPTION_STATE_IDENTITY,
|
||
false,
|
||
MACH_RCV_PORT_DIED},
|
||
{EXC_SYSCALL, EXCEPTION_DEFAULT, true, KERN_SUCCESS},
|
||
{EXC_MACH_SYSCALL, EXCEPTION_STATE, true, KERN_SUCCESS},
|
||
{EXC_RPC_ALERT, EXCEPTION_STATE_IDENTITY, true, KERN_SUCCESS},
|
||
{EXC_RESOURCE,
|
||
kMachExceptionCodes | EXCEPTION_DEFAULT,
|
||
true,
|
||
KERN_SUCCESS},
|
||
{EXC_GUARD, kMachExceptionCodes | EXCEPTION_STATE, true, KERN_SUCCESS},
|
||
{EXC_CORPSE_NOTIFY,
|
||
kMachExceptionCodes | EXCEPTION_STATE_IDENTITY,
|
||
true,
|
||
KERN_SUCCESS},
|
||
};
|
||
|
||
for (size_t index = 0; index < std::size(kTestData); ++index) {
|
||
const auto& test_data = kTestData[index];
|
||
SCOPED_TRACE(
|
||
base::StringPrintf("index %zu, behavior %d, set_thread_state %s",
|
||
index,
|
||
test_data.behavior,
|
||
test_data.set_thread_state ? "true" : "false"));
|
||
|
||
EXPECT_EQ(ExcServerSuccessfulReturnValue(test_data.exception,
|
||
test_data.behavior,
|
||
test_data.set_thread_state),
|
||
test_data.kr);
|
||
}
|
||
}
|
||
|
||
TEST(ExcServerVariants, ExcServerCopyState) {
|
||
static constexpr natural_t old_state[] = {1, 2, 3, 4, 5};
|
||
natural_t new_state[10] = {};
|
||
|
||
constexpr mach_msg_type_number_t old_state_count = std::size(old_state);
|
||
mach_msg_type_number_t new_state_count = std::size(new_state);
|
||
|
||
// EXCEPTION_DEFAULT (with or without MACH_EXCEPTION_CODES) is not
|
||
// state-carrying. new_state and new_state_count should be untouched.
|
||
ExcServerCopyState(EXCEPTION_DEFAULT,
|
||
old_state,
|
||
old_state_count,
|
||
new_state,
|
||
&new_state_count);
|
||
EXPECT_EQ(new_state_count, std::size(new_state));
|
||
for (size_t i = 0; i < std::size(new_state); ++i) {
|
||
EXPECT_EQ(new_state[i], 0u) << "i " << i;
|
||
}
|
||
|
||
ExcServerCopyState(MACH_EXCEPTION_CODES | EXCEPTION_DEFAULT,
|
||
old_state,
|
||
old_state_count,
|
||
new_state,
|
||
&new_state_count);
|
||
EXPECT_EQ(new_state_count, std::size(new_state));
|
||
for (size_t i = 0; i < std::size(new_state); ++i) {
|
||
EXPECT_EQ(new_state[i], 0u) << "i " << i;
|
||
}
|
||
|
||
// This is a state-carrying exception where old_state_count is small.
|
||
mach_msg_type_number_t copy_limit = 2;
|
||
ExcServerCopyState(
|
||
EXCEPTION_STATE, old_state, copy_limit, new_state, &new_state_count);
|
||
EXPECT_EQ(new_state_count, copy_limit);
|
||
for (size_t i = 0; i < copy_limit; ++i) {
|
||
EXPECT_EQ(new_state[i], old_state[i]) << "i " << i;
|
||
}
|
||
for (size_t i = copy_limit; i < std::size(new_state); ++i) {
|
||
EXPECT_EQ(new_state[i], 0u) << "i " << i;
|
||
}
|
||
|
||
// This is a state-carrying exception where new_state_count is small.
|
||
copy_limit = 3;
|
||
new_state_count = copy_limit;
|
||
ExcServerCopyState(EXCEPTION_STATE_IDENTITY,
|
||
old_state,
|
||
old_state_count,
|
||
new_state,
|
||
&new_state_count);
|
||
EXPECT_EQ(new_state_count, copy_limit);
|
||
for (size_t i = 0; i < copy_limit; ++i) {
|
||
EXPECT_EQ(new_state[i], old_state[i]) << "i " << i;
|
||
}
|
||
for (size_t i = copy_limit; i < std::size(new_state); ++i) {
|
||
EXPECT_EQ(new_state[i], 0u) << "i " << i;
|
||
}
|
||
|
||
// This is a state-carrying exception where all of old_state is copied to
|
||
// new_state, which is large enough to receive it and then some.
|
||
new_state_count = std::size(new_state);
|
||
ExcServerCopyState(MACH_EXCEPTION_CODES | EXCEPTION_STATE_IDENTITY,
|
||
old_state,
|
||
old_state_count,
|
||
new_state,
|
||
&new_state_count);
|
||
EXPECT_EQ(new_state_count, old_state_count);
|
||
for (size_t i = 0; i < std::size(old_state); ++i) {
|
||
EXPECT_EQ(new_state[i], old_state[i]) << "i " << i;
|
||
}
|
||
for (size_t i = std::size(old_state); i < std::size(new_state); ++i) {
|
||
EXPECT_EQ(new_state[i], 0u) << "i " << i;
|
||
}
|
||
}
|
||
|
||
} // namespace
|
||
} // namespace test
|
||
} // namespace crashpad
|