kicad/thirdparty/sentry-native/external/crashpad/client/simulate_crash_mac_test.cc

414 lines
16 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 "client/simulate_crash.h"
#include <mach/mach.h>
#include <string.h>
#include <sys/types.h>
#include "base/cxx17_backports.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "gtest/gtest.h"
#include "test/mac/mach_errors.h"
#include "test/mac/mach_multiprocess.h"
#include "util/mach/exc_server_variants.h"
#include "util/mach/exception_behaviors.h"
#include "util/mach/exception_ports.h"
#include "util/mach/mach_extensions.h"
#include "util/mach/mach_message.h"
#include "util/mach/mach_message_server.h"
#include "util/mach/symbolic_constants_mach.h"
#include "util/misc/implicit_cast.h"
namespace crashpad {
namespace test {
namespace {
class TestSimulateCrashMac final : public MachMultiprocess,
public UniversalMachExcServer::Interface {
public:
// Defines which targets the child should set an EXC_CRASH exception handler
// for.
enum ExceptionPortsTarget {
// The child should clear its EXC_CRASH handler for both its task and thread
// targets. SimulateCrash() will attempt to deliver the exception to the
// host target, which will fail if not running as root. In any case, the
// parent should not expect to receive any exception message from the child.
kExceptionPortsTargetNone = 0,
// The child will set an EXC_CRASH handler for its task target, and clear it
// for its thread target. The parent runs an exception server to receive
// the childs simulated crash message.
kExceptionPortsTargetTask,
// The child will set an EXC_CRASH handler for its thread target, and clear
// it for its task target. The parent runs an exception server to receive
// the childs simulated crash message.
kExceptionPortsTargetThread,
// The child sets an EXC_CRASH handler for both its task and thread targets.
// The parent runs an exception server to receive the message expected to be
// delivered to the thread target, but returns an error code. The child will
// then fall back to trying the server registered for the task target,
// sending a second message to the parent. The server in the parent will
// handle this one successfully.
kExceptionPortsTargetBoth,
};
TestSimulateCrashMac(ExceptionPortsTarget target,
exception_behavior_t behavior,
thread_state_flavor_t flavor)
: MachMultiprocess(),
UniversalMachExcServer::Interface(),
target_(target),
behavior_(behavior),
flavor_(flavor),
succeed_(true) {
}
TestSimulateCrashMac(const TestSimulateCrashMac&) = delete;
TestSimulateCrashMac& operator=(const TestSimulateCrashMac&) = delete;
~TestSimulateCrashMac() {}
// UniversalMachExcServer::Interface:
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;
// Check the entire exception message, because most or all of it was
// generated by SimulateCrash() instead of the kernel.
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, kMachExceptionSimulated);
EXPECT_EQ(code_count, 2u);
if (code_count >= 1) {
EXPECT_EQ(code[0], 0);
}
if (code_count >= 2) {
EXPECT_EQ(code[1], 0);
}
if (!ExceptionBehaviorHasState(behavior)) {
EXPECT_EQ(*flavor, THREAD_STATE_NONE);
} else {
EXPECT_EQ(*flavor, flavor_);
switch (*flavor) {
#if defined(ARCH_CPU_X86_FAMILY)
case x86_THREAD_STATE: {
EXPECT_EQ(old_state_count, x86_THREAD_STATE_COUNT);
const x86_thread_state* state =
reinterpret_cast<const x86_thread_state*>(old_state);
switch (state->tsh.flavor) {
case x86_THREAD_STATE32:
EXPECT_EQ(implicit_cast<uint32_t>(state->tsh.count),
implicit_cast<uint32_t>(x86_THREAD_STATE32_COUNT));
break;
case x86_THREAD_STATE64:
EXPECT_EQ(implicit_cast<uint32_t>(state->tsh.count),
implicit_cast<uint32_t>(x86_THREAD_STATE64_COUNT));
break;
default:
ADD_FAILURE() << "unexpected tsh.flavor " << state->tsh.flavor;
break;
}
break;
}
case x86_FLOAT_STATE: {
EXPECT_EQ(old_state_count, x86_FLOAT_STATE_COUNT);
const x86_float_state* state =
reinterpret_cast<const x86_float_state*>(old_state);
switch (state->fsh.flavor) {
case x86_FLOAT_STATE32:
EXPECT_EQ(implicit_cast<uint32_t>(state->fsh.count),
implicit_cast<uint32_t>(x86_FLOAT_STATE32_COUNT));
break;
case x86_FLOAT_STATE64:
EXPECT_EQ(implicit_cast<uint32_t>(state->fsh.count),
implicit_cast<uint32_t>(x86_FLOAT_STATE64_COUNT));
break;
default:
ADD_FAILURE() << "unexpected fsh.flavor " << state->fsh.flavor;
break;
}
break;
}
case x86_DEBUG_STATE: {
EXPECT_EQ(old_state_count, x86_DEBUG_STATE_COUNT);
const x86_debug_state* state =
reinterpret_cast<const x86_debug_state*>(old_state);
switch (state->dsh.flavor) {
case x86_DEBUG_STATE32:
EXPECT_EQ(implicit_cast<uint32_t>(state->dsh.count),
implicit_cast<uint32_t>(x86_DEBUG_STATE32_COUNT));
break;
case x86_DEBUG_STATE64:
EXPECT_EQ(implicit_cast<uint32_t>(state->dsh.count),
implicit_cast<uint32_t>(x86_DEBUG_STATE64_COUNT));
break;
default:
ADD_FAILURE() << "unexpected dsh.flavor " << state->dsh.flavor;
break;
}
break;
}
case x86_THREAD_STATE32:
EXPECT_EQ(old_state_count, x86_THREAD_STATE32_COUNT);
break;
case x86_FLOAT_STATE32:
EXPECT_EQ(old_state_count, x86_FLOAT_STATE32_COUNT);
break;
case x86_DEBUG_STATE32:
EXPECT_EQ(old_state_count, x86_DEBUG_STATE32_COUNT);
break;
case x86_THREAD_STATE64:
EXPECT_EQ(old_state_count, x86_THREAD_STATE64_COUNT);
break;
case x86_FLOAT_STATE64:
EXPECT_EQ(old_state_count, x86_FLOAT_STATE64_COUNT);
break;
case x86_DEBUG_STATE64:
EXPECT_EQ(old_state_count, x86_DEBUG_STATE64_COUNT);
break;
#elif defined(ARCH_CPU_ARM64)
case ARM_UNIFIED_THREAD_STATE: {
EXPECT_EQ(old_state_count, ARM_UNIFIED_THREAD_STATE_COUNT);
const arm_unified_thread_state* state =
reinterpret_cast<const arm_unified_thread_state*>(old_state);
EXPECT_EQ(state->ash.flavor,
implicit_cast<uint32_t>(ARM_THREAD_STATE64));
if (state->ash.flavor == ARM_THREAD_STATE64) {
EXPECT_EQ(state->ash.count,
implicit_cast<uint32_t>(ARM_THREAD_STATE64_COUNT));
}
break;
}
case ARM_THREAD_STATE64:
EXPECT_EQ(old_state_count, ARM_THREAD_STATE64_COUNT);
break;
case ARM_NEON_STATE64:
EXPECT_EQ(old_state_count, ARM_NEON_STATE64_COUNT);
break;
case ARM_DEBUG_STATE64:
EXPECT_EQ(old_state_count, ARM_DEBUG_STATE64_COUNT);
break;
#else
#error Port to your CPU architecture
#endif
default:
ADD_FAILURE() << "unexpected flavor " << *flavor;
break;
}
// Attempt to set a garbage thread state, which would cause the child to
// crash inside SimulateCrash() if it actually succeeded. This tests that
// SimulateCrash() ignores new_state instead of attempting to set the
// state as the kernel would do. This operates in conjunction with the
// |true| argument to ExcServerSuccessfulReturnValue() below.
*new_state_count = old_state_count;
size_t new_state_size = sizeof(natural_t) * old_state_count;
memset(new_state, 0xa5, new_state_size);
}
if (!succeed_) {
// The client has registered EXC_CRASH handlers for both its thread and
// task targets, and sent a simulated exception message to its
// thread-level EXC_CRASH handler. To test that it will fall back to
// trying the task-level EXC_CRASH handler, return a failure code, which
// should cause SimulateCrash() to try the next target.
EXPECT_EQ(target_, kExceptionPortsTargetBoth);
return KERN_ABORTED;
}
ExcServerCopyState(
behavior, old_state, old_state_count, new_state, new_state_count);
return ExcServerSuccessfulReturnValue(exception, behavior, true);
}
private:
// MachMultiprocess:
void MachMultiprocessParent() override {
if (target_ == kExceptionPortsTargetNone) {
// The child does not have any EXC_CRASH handlers registered for its
// thread or task targets, so no exception message is expected to be
// generated. Dont run the server at all.
return;
}
UniversalMachExcServer universal_mach_exc_server(this);
mach_msg_return_t mr;
if (target_ == kExceptionPortsTargetBoth) {
// The client has registered EXC_CRASH handlers for both its thread and
// task targets. Run a server that will return a failure code when the
// exception message is sent to the thread target, which will cause the
// client to fall back to the task target and send another message.
succeed_ = false;
mr = MachMessageServer::Run(&universal_mach_exc_server,
LocalPort(),
MACH_MSG_OPTION_NONE,
MachMessageServer::kOneShot,
MachMessageServer::kReceiveLargeError,
kMachMessageTimeoutWaitIndefinitely);
EXPECT_EQ(mr, MACH_MSG_SUCCESS)
<< MachErrorMessage(mr, "MachMessageServer::Run");
}
succeed_ = true;
mr = MachMessageServer::Run(&universal_mach_exc_server,
LocalPort(),
MACH_MSG_OPTION_NONE,
MachMessageServer::kOneShot,
MachMessageServer::kReceiveLargeError,
kMachMessageTimeoutWaitIndefinitely);
EXPECT_EQ(mr, MACH_MSG_SUCCESS)
<< MachErrorMessage(mr, "MachMessageServer::Run");
}
void MachMultiprocessChild() override {
bool task_valid = target_ == kExceptionPortsTargetTask ||
target_ == kExceptionPortsTargetBoth;
ExceptionPorts task_exception_ports(ExceptionPorts::kTargetTypeTask,
TASK_NULL);
ASSERT_TRUE(task_exception_ports.SetExceptionPort(
EXC_MASK_CRASH,
task_valid ? RemotePort() : MACH_PORT_NULL,
behavior_,
flavor_));
bool thread_valid = target_ == kExceptionPortsTargetThread ||
target_ == kExceptionPortsTargetBoth;
ExceptionPorts thread_exception_ports(ExceptionPorts::kTargetTypeThread,
THREAD_NULL);
ASSERT_TRUE(thread_exception_ports.SetExceptionPort(
EXC_MASK_CRASH,
thread_valid ? RemotePort() : MACH_PORT_NULL,
behavior_,
flavor_));
CRASHPAD_SIMULATE_CRASH();
}
ExceptionPortsTarget target_;
exception_behavior_t behavior_;
thread_state_flavor_t flavor_;
bool succeed_;
};
TEST(SimulateCrash, SimulateCrash) {
static constexpr TestSimulateCrashMac::ExceptionPortsTarget kTargets[] = {
TestSimulateCrashMac::kExceptionPortsTargetNone,
TestSimulateCrashMac::kExceptionPortsTargetTask,
TestSimulateCrashMac::kExceptionPortsTargetThread,
TestSimulateCrashMac::kExceptionPortsTargetBoth,
};
static constexpr exception_behavior_t kBehaviors[] = {
EXCEPTION_DEFAULT,
EXCEPTION_STATE,
EXCEPTION_STATE_IDENTITY,
EXCEPTION_DEFAULT | kMachExceptionCodes,
EXCEPTION_STATE | kMachExceptionCodes,
EXCEPTION_STATE_IDENTITY | kMachExceptionCodes,
};
static constexpr thread_state_flavor_t kFlavors[] = {
#if defined(ARCH_CPU_X86_FAMILY)
x86_THREAD_STATE,
x86_FLOAT_STATE,
x86_DEBUG_STATE,
#if defined(ARCH_CPU_X86)
x86_THREAD_STATE32,
x86_FLOAT_STATE32,
x86_DEBUG_STATE32,
#elif defined(ARCH_CPU_X86_64)
x86_THREAD_STATE64,
x86_FLOAT_STATE64,
x86_DEBUG_STATE64,
#endif
#elif defined(ARCH_CPU_ARM64)
ARM_UNIFIED_THREAD_STATE,
ARM_THREAD_STATE64,
ARM_NEON_STATE64,
ARM_DEBUG_STATE64,
#else
#error Port to your CPU architecture
#endif
};
for (size_t target_index = 0; target_index < base::size(kTargets);
++target_index) {
TestSimulateCrashMac::ExceptionPortsTarget target = kTargets[target_index];
SCOPED_TRACE(base::StringPrintf(
"target_index %zu, target %d", target_index, target));
for (size_t behavior_index = 0; behavior_index < base::size(kBehaviors);
++behavior_index) {
exception_behavior_t behavior = kBehaviors[behavior_index];
SCOPED_TRACE(base::StringPrintf(
"behavior_index %zu, behavior %s",
behavior_index,
ExceptionBehaviorToString(behavior, kUseFullName | kUnknownIsNumeric)
.c_str()));
if (!ExceptionBehaviorHasState(behavior)) {
TestSimulateCrashMac test_simulate_crash_mac(
target, behavior, THREAD_STATE_NONE);
test_simulate_crash_mac.Run();
} else {
for (size_t flavor_index = 0; flavor_index < base::size(kFlavors);
++flavor_index) {
thread_state_flavor_t flavor = kFlavors[flavor_index];
SCOPED_TRACE(base::StringPrintf(
"flavor_index %zu, flavor %s",
flavor_index,
ThreadStateFlavorToString(
flavor, kUseFullName | kUnknownIsNumeric).c_str()));
TestSimulateCrashMac test_simulate_crash_mac(
target, behavior, flavor);
test_simulate_crash_mac.Run();
}
}
}
}
}
} // namespace
} // namespace test
} // namespace crashpad