crashpad/client/simulate_crash_mac.cc
Mark Mentovai 6278690abe Update copyright boilerplate, 2022 edition (Crashpad)
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>
2022-09-06 23:54:07 +00:00

259 lines
10 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
//
// 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_mac.h"
#include <string.h>
#include <sys/types.h>
#include <iterator>
#include "base/check_op.h"
#include "base/logging.h"
#include "base/mac/mach_logging.h"
#include "base/mac/scoped_mach_port.h"
#include "build/build_config.h"
#include "util/mach/exc_client_variants.h"
#include "util/mach/exception_behaviors.h"
#include "util/mach/exception_ports.h"
#include "util/mach/mach_extensions.h"
#include "util/misc/implicit_cast.h"
namespace crashpad {
namespace {
//! \brief Sends an exception message to an exception port in accordance with
//! the behavior and thread state flavor its registered to receive.
//!
//! \param[in] thread, task, exception, code, code_count These parameters will
//! be passed to the exception handler as appropriate.
//! \param[in] cpu_context The value to use for the thread state, if \a behavior
//! indicates that the handler should receive a thread state and if the
//! supplied thread state matches or can be converted to \a flavor. If \a
//! behavior requires a thread state but this argument cannot be converted
//! to match \a flavor, `thread_get_state()` will be called to obtain a
//! suitable thread state value.
//! \param[in] handler The Mach exception handler to deliver the exception to.
//! \param[in] set_state If `true` and \a behavior indicates that the handler
//! should receive and return a thread state, a new thread state will be set
//! by `thread_set_state()` upon successful completion of the exception
//! handler. If `false`, this will be suppressed, even when \a behavior
//! indicates that the handler receives and returns a thread state.
//!
//! \return `true` if the exception was delivered to the handler and the handler
//! indicated success. `false` otherwise, with a warning message logged.
bool DeliverException(thread_t thread,
task_t task,
exception_type_t exception,
const mach_exception_data_t code,
mach_msg_type_number_t code_count,
const NativeCPUContext& cpu_context,
const ExceptionPorts::ExceptionHandler& handler,
bool set_state) {
kern_return_t kr;
bool handler_wants_state = ExceptionBehaviorHasState(handler.behavior);
if (!handler_wants_state) {
// Regardless of the passed-in value of |set_state|, if the handler wont be
// dealing with any state at all, no state should be set.
set_state = false;
}
// old_state is only used if the context already captured doesnt match (or
// cant be converted to) whats registered for the handler.
thread_state_data_t old_state;
thread_state_flavor_t flavor = handler.flavor;
ConstThreadState state;
mach_msg_type_number_t state_count;
switch (flavor) {
#if defined(ARCH_CPU_X86_FAMILY)
case x86_THREAD_STATE:
state = reinterpret_cast<ConstThreadState>(&cpu_context);
state_count = x86_THREAD_STATE_COUNT;
break;
#if defined(ARCH_CPU_X86)
case x86_THREAD_STATE32:
state = reinterpret_cast<ConstThreadState>(&cpu_context.uts.ts32);
state_count = cpu_context.tsh.count;
break;
#elif defined(ARCH_CPU_X86_64)
case x86_THREAD_STATE64:
state = reinterpret_cast<ConstThreadState>(&cpu_context.uts.ts64);
state_count = cpu_context.tsh.count;
break;
#endif
#elif defined(ARCH_CPU_ARM64)
case ARM_UNIFIED_THREAD_STATE:
state = reinterpret_cast<ConstThreadState>(&cpu_context);
state_count = ARM_UNIFIED_THREAD_STATE_COUNT;
break;
case ARM_THREAD_STATE64:
state = reinterpret_cast<ConstThreadState>(&cpu_context.ts_64);
state_count = cpu_context.ash.count;
break;
#else
#error Port to your CPU architecture
#endif
case THREAD_STATE_NONE:
// This is only acceptable if the handler doesnt have one of the “state”
// behaviors. Otherwise, if the kernel were attempting to send an
// exception message to this port, it would call thread_getstatus() (known
// outside the kernel as thread_get_state()) which would fail because
// THREAD_STATE_NONE is not a valid state to get. See 10.9.5
// xnu-2422.115.4/osfmk/kern/exception.c exception_deliver() and
// xnu-2422.115.4/osfmk/i386/pcb.c machine_thread_get_state().
if (!handler_wants_state) {
state = nullptr;
state_count = 0;
break;
}
LOG(WARNING) << "exception handler has unexpected state flavor" << flavor;
return false;
default:
if (!handler_wants_state) {
// Dont bother getting any thread state if the handlers not actually
// going to use it.
state = nullptr;
state_count = 0;
} else {
state = old_state;
state_count = THREAD_STATE_MAX;
kr = thread_get_state(thread, flavor, old_state, &state_count);
if (kr != KERN_SUCCESS) {
MACH_LOG(WARNING, kr) << "thread_get_state";
return false;
}
}
break;
}
// new_state is supposed to be an out parameter only, but in case the handler
// doesn't touch it, make sure it's initialized to a valid thread state.
// Otherwise, the thread_set_state() call below would set a garbage thread
// state.
thread_state_data_t new_state;
size_t state_size =
sizeof(natural_t) *
std::min(state_count, implicit_cast<unsigned int>(THREAD_STATE_MAX));
memcpy(new_state, state, state_size);
mach_msg_type_number_t new_state_count = THREAD_STATE_MAX;
kr = UniversalExceptionRaise(handler.behavior,
handler.port,
thread,
task,
exception,
code,
code_count,
&flavor,
state,
state_count,
new_state,
&new_state_count);
// The kernel treats a return value of MACH_RCV_PORT_DIED as successful,
// although it will not set a new thread state in that case. See 10.9.5
// xnu-2422.115.4/osfmk/kern/exception.c exception_deliver(), and the more
// elaborate comment at util/mach/exc_server_variants.h
// ExcServerSuccessfulReturnValue(). Duplicate that behavior.
bool success = kr == KERN_SUCCESS || kr == MACH_RCV_PORT_DIED;
MACH_LOG_IF(WARNING, !success, kr) << "UniversalExceptionRaise";
if (kr == KERN_SUCCESS && set_state) {
kr = thread_set_state(thread, flavor, new_state, new_state_count);
MACH_LOG_IF(WARNING, kr != KERN_SUCCESS, kr) << "thread_set_state";
}
return success;
}
} // namespace
void SimulateCrash(const NativeCPUContext& cpu_context) {
#if defined(ARCH_CPU_X86)
DCHECK_EQ(implicit_cast<thread_state_flavor_t>(cpu_context.tsh.flavor),
implicit_cast<thread_state_flavor_t>(x86_THREAD_STATE32));
DCHECK_EQ(implicit_cast<mach_msg_type_number_t>(cpu_context.tsh.count),
x86_THREAD_STATE32_COUNT);
#elif defined(ARCH_CPU_X86_64)
DCHECK_EQ(implicit_cast<thread_state_flavor_t>(cpu_context.tsh.flavor),
implicit_cast<thread_state_flavor_t>(x86_THREAD_STATE64));
DCHECK_EQ(implicit_cast<mach_msg_type_number_t>(cpu_context.tsh.count),
x86_THREAD_STATE64_COUNT);
#elif defined(ARCH_CPU_ARM64)
DCHECK_EQ(implicit_cast<thread_state_flavor_t>(cpu_context.ash.flavor),
implicit_cast<thread_state_flavor_t>(ARM_THREAD_STATE64));
DCHECK_EQ(implicit_cast<mach_msg_type_number_t>(cpu_context.ash.count),
ARM_THREAD_STATE64_COUNT);
#else
#error Port to your CPU architecture
#endif
base::mac::ScopedMachSendRight thread(mach_thread_self());
exception_type_t exception = kMachExceptionSimulated;
mach_exception_data_type_t codes[] = {0, 0};
mach_msg_type_number_t code_count = std::size(codes);
// Look up the handler for EXC_CRASH exceptions in the same way that the
// kernel would: try a thread handler, then a task handler, and finally a host
// handler. 10.9.5 xnu-2422.115.4/osfmk/kern/exception.c exception_triage().
static constexpr ExceptionPorts::TargetType kTargetTypes[] = {
ExceptionPorts::kTargetTypeThread,
ExceptionPorts::kTargetTypeTask,
// This is not expected to succeed, because mach_host_self() doesnt
// return the host_priv port to non-root users, and this is the port
// thats required for host_get_exception_ports().
//
// See 10.9.5 xnu-2422.115.4/bsd/kern/kern_prot.c set_security_token(),
// xnu-2422.115.4/osfmk/kern/task.c host_security_set_task_token(), and
// xnu-2422.115.4/osfmk/kern/ipc_host.c host_get_exception_ports().
ExceptionPorts::kTargetTypeHost,
};
bool success = false;
for (size_t target_type_index = 0;
!success && target_type_index < std::size(kTargetTypes);
++target_type_index) {
ExceptionPorts::ExceptionHandlerVector handlers;
ExceptionPorts exception_ports(kTargetTypes[target_type_index],
MACH_PORT_NULL);
if (exception_ports.GetExceptionPorts(EXC_MASK_CRASH, &handlers)) {
DCHECK_LE(handlers.size(), 1u);
if (handlers.size() == 1) {
DCHECK(handlers[0].mask & EXC_MASK_CRASH);
success = DeliverException(thread.get(),
mach_task_self(),
exception,
codes,
code_count,
cpu_context,
handlers[0],
false);
}
}
}
LOG_IF(WARNING, !success)
<< "SimulateCrash did not find an appropriate exception handler";
}
} // namespace crashpad