Index: client/simulate_crash_mac.cc |
diff --git a/client/simulate_crash_mac.cc b/client/simulate_crash_mac.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..eb9b010e1e0162802a1c7a2ca2f21c58f4cecd6f |
--- /dev/null |
+++ b/client/simulate_crash_mac.cc |
@@ -0,0 +1,244 @@ |
+// 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_mac.h" |
+ |
+#include <string.h> |
+ |
+#include <vector> |
+ |
+#include "base/basictypes.h" |
+#include "base/logging.h" |
+#include "base/mac/mach_logging.h" |
+#include "base/mac/scoped_mach_port.h" |
+#include "base/strings/stringprintf.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" |
+ |
+namespace crashpad { |
+ |
+namespace { |
+ |
+//! \brief Sends an exception message to an exception port in accordance with |
+//! the behavior and thread state flavor it’s 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 won’t be |
+ // dealing with any state at all, no state should be set. |
+ set_state = false; |
+ } |
+ |
+ // A const version of thread_state_t. |
+ typedef const natural_t* ConstThreadState; |
+ |
+ // old_state is only used if the context already captured doesn’t match (or |
+ // can’t be converted to) what’s 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 |
+#else |
+#error Port to your CPU architecture |
+#endif |
+ |
+ case THREAD_STATE_NONE: |
+ // This is only acceptable if the handler doesn’t 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) { |
+ // Don’t bother getting any thread state if the handler’s 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, static_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(cpu_context->tsh.flavor, |
+ static_cast<thread_state_flavor_t>(x86_THREAD_STATE32)); |
+ DCHECK_EQ(static_cast<mach_msg_type_number_t>(cpu_context->tsh.count), |
+ x86_THREAD_STATE32_COUNT); |
+#elif defined(ARCH_CPU_X86_64) |
+ DCHECK_EQ(cpu_context->tsh.flavor, |
+ static_cast<thread_state_flavor_t>(x86_THREAD_STATE64)); |
+ DCHECK_EQ(static_cast<mach_msg_type_number_t>(cpu_context->tsh.count), |
+ x86_THREAD_STATE64_COUNT); |
+#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 = arraysize(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(). |
+ const ExceptionPorts::TargetType kTargetTypes[] = { |
+ ExceptionPorts::kTargetTypeThread, |
+ ExceptionPorts::kTargetTypeTask, |
+ |
+ // This is not expected to succeed, because mach_host_self() doesn’t |
+ // return the host_priv port to non-root users, and this is the port |
+ // that’s 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 < arraysize(kTargetTypes); |
+ ++target_type_index) { |
+ std::vector<ExceptionPorts::ExceptionHandler> 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, |
+ 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 |