diff options
Diffstat (limited to 'lldb/source/Plugins/Process/Darwin/MachException.cpp')
-rw-r--r-- | lldb/source/Plugins/Process/Darwin/MachException.cpp | 684 |
1 files changed, 684 insertions, 0 deletions
diff --git a/lldb/source/Plugins/Process/Darwin/MachException.cpp b/lldb/source/Plugins/Process/Darwin/MachException.cpp new file mode 100644 index 00000000000..d9693195e38 --- /dev/null +++ b/lldb/source/Plugins/Process/Darwin/MachException.cpp @@ -0,0 +1,684 @@ +//===-- MachException.cpp ---------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Created by Greg Clayton on 6/18/07. +// +//===----------------------------------------------------------------------===// + +#include "MachException.h" + +// C includes +#include <errno.h> +#include <sys/types.h> +#include <sys/ptrace.h> + +// C++ includes +#include <mutex> + +// LLDB includes +#include "lldb/Core/Error.h" +#include "lldb/Core/Log.h" +#include "lldb/Core/Stream.h" +#include "lldb/Target/UnixSignals.h" +#include "lldb/Utility/LLDBAssert.h" + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::process_darwin; + +// Routine mach_exception_raise +extern "C" +kern_return_t catch_mach_exception_raise +( + mach_port_t exception_port, + mach_port_t thread, + mach_port_t task, + exception_type_t exception, + mach_exception_data_t code, + mach_msg_type_number_t codeCnt +); + +extern "C" +kern_return_t catch_mach_exception_raise_state +( + mach_port_t exception_port, + exception_type_t exception, + const mach_exception_data_t code, + mach_msg_type_number_t codeCnt, + int *flavor, + const thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, + thread_state_t new_state, + mach_msg_type_number_t *new_stateCnt +); + +// Routine mach_exception_raise_state_identity +extern "C" +kern_return_t catch_mach_exception_raise_state_identity +( + mach_port_t exception_port, + mach_port_t thread, + mach_port_t task, + exception_type_t exception, + mach_exception_data_t code, + mach_msg_type_number_t codeCnt, + int *flavor, + thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, + thread_state_t new_state, + mach_msg_type_number_t *new_stateCnt +); + +extern "C" boolean_t mach_exc_server( + mach_msg_header_t *InHeadP, + mach_msg_header_t *OutHeadP); + +// Any access to the g_message variable should be done by locking the +// g_message_mutex first, using the g_message variable, then unlocking +// the g_message_mutex. See MachException::Message::CatchExceptionRaise() +// for sample code. + +static MachException::Data *g_message = NULL; + + +extern "C" +kern_return_t +catch_mach_exception_raise_state +( + mach_port_t exc_port, + exception_type_t exc_type, + const mach_exception_data_t exc_data, + mach_msg_type_number_t exc_data_count, + int * flavor, + const thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, + thread_state_t new_state, + mach_msg_type_number_t * new_stateCnt +) +{ + // TODO change to LIBLLDB_LOG_EXCEPTION + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | + LIBLLDB_LOG_VERBOSE)); + if (log) + { + log->Printf("::%s(exc_port = 0x%4.4x, exc_type = %d (%s), " + "exc_data = 0x%llx, exc_data_count = %d)", + __FUNCTION__, exc_port, exc_type, + MachException::Name(exc_type), (uint64_t)exc_data, + exc_data_count); + } + return KERN_FAILURE; +} + +extern "C" +kern_return_t +catch_mach_exception_raise_state_identity +( + mach_port_t exc_port, + mach_port_t thread_port, + mach_port_t task_port, + exception_type_t exc_type, + mach_exception_data_t exc_data, + mach_msg_type_number_t exc_data_count, + int * flavor, + thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, + thread_state_t new_state, + mach_msg_type_number_t *new_stateCnt +) +{ + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | + LIBLLDB_LOG_VERBOSE)); + if (log) + { + log->Printf("::%s(exc_port = 0x%4.4x, thd_port = 0x%4.4x, " + "tsk_port = 0x%4.4x, exc_type = %d (%s), exc_data[%d] = " + "{ 0x%llx, 0x%llx })", __FUNCTION__, exc_port, thread_port, + task_port, exc_type, MachException::Name(exc_type), + exc_data_count, + (uint64_t)(exc_data_count > 0 ? exc_data[0] : 0xBADDBADD), + (uint64_t)(exc_data_count > 1 ? exc_data[1] : 0xBADDBADD)); + } + mach_port_deallocate (mach_task_self(), task_port); + mach_port_deallocate (mach_task_self(), thread_port); + + return KERN_FAILURE; +} + +extern "C" +kern_return_t +catch_mach_exception_raise +( + mach_port_t exc_port, + mach_port_t thread_port, + mach_port_t task_port, + exception_type_t exc_type, + mach_exception_data_t exc_data, + mach_msg_type_number_t exc_data_count) +{ + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | + LIBLLDB_LOG_VERBOSE)); + if (log) + { + log->Printf("::%s(exc_port = 0x%4.4x, thd_port = 0x%4.4x, " + "tsk_port = 0x%4.4x, exc_type = %d (%s), exc_data[%d] " + "= { 0x%llx, 0x%llx })", __FUNCTION__, exc_port, + thread_port, task_port, exc_type, + MachException::Name(exc_type), exc_data_count, + (uint64_t)(exc_data_count > 0 ? exc_data[0] : 0xBADDBADD), + (uint64_t)(exc_data_count > 1 ? exc_data[1] : 0xBADDBADD)); + } + + if (task_port == g_message->task_port) + { + g_message->task_port = task_port; + g_message->thread_port = thread_port; + g_message->exc_type = exc_type; + g_message->exc_data.resize(exc_data_count); + ::memcpy(&g_message->exc_data[0], exc_data, + g_message->exc_data.size() * + sizeof (mach_exception_data_type_t)); + return KERN_SUCCESS; + } + return KERN_FAILURE; +} + +#if 0 +void +MachException::Message::Dump(Stream &stream) const +{ + stream.Printf("exc_msg { bits = 0x%8.8x size = 0x%8.8x remote-port = " + "0x%8.8x local-port = 0x%8.8x reserved = 0x%8.8x " + "id = 0x%8.8x }\n", + exc_msg.hdr.msgh_bits, + exc_msg.hdr.msgh_size, + exc_msg.hdr.msgh_remote_port, + exc_msg.hdr.msgh_local_port, + exc_msg.hdr.msgh_reserved, + exc_msg.hdr.msgh_id); + + stream.Printf("reply_msg { bits = 0x%8.8x size = 0x%8.8x remote-port " + "= 0x%8.8x local-port = 0x%8.8x reserved = 0x%8.8x " + "id = 0x%8.8x }", + reply_msg.hdr.msgh_bits, + reply_msg.hdr.msgh_size, + reply_msg.hdr.msgh_remote_port, + reply_msg.hdr.msgh_local_port, + reply_msg.hdr.msgh_reserved, + reply_msg.hdr.msgh_id); + stream.Flush(); +} +#endif + +bool +MachException::Data::GetStopInfo(struct ThreadStopInfo *stop_info, + const UnixSignals &signals, + Stream &stream) const +{ + if (!stop_info) + return false; + + // Zero out the structure. + memset(stop_info, 0, sizeof(struct ThreadStopInfo)); + + if (exc_type == 0) + { + stop_info->reason = eStopReasonInvalid; + return true; + } + + // We always stop with a mach exception. + stop_info->reason = eStopReasonException; + // Save the EXC_XXXX exception type. + stop_info->details.exception.type = exc_type; + + // Fill in a text description + const char * exc_name = MachException::Name(exc_type); + if (exc_name) + stream.Printf("%s", exc_name); + else + stream.Printf("%i", exc_type); + + stop_info->details.exception.data_count = exc_data.size(); + + int soft_signal = SoftSignal(); + if (soft_signal) + { + const char *sig_str = signals.GetSignalAsCString(soft_signal); + stream.Printf(" EXC_SOFT_SIGNAL( %i ( %s ))", soft_signal, + sig_str ? sig_str : "unknown signal"); + } + else + { + // No special disassembly for exception data, just print it. + size_t idx; + stream.Printf(" data[%llu] = {", + (uint64_t)stop_info->details.exception.data_count); + + for (idx = 0; idx < stop_info->details.exception.data_count; ++idx) + { + stream.Printf("0x%llx%c", (uint64_t)exc_data[idx], + ((idx + 1 == stop_info->details.exception.data_count) + ? '}' : ',')); + } + } + + // Copy the exception data + for (size_t i = 0; i < stop_info->details.exception.data_count; i++) + stop_info->details.exception.data[i] = exc_data[i]; + + return true; +} + +Error +MachException::Message::Receive(mach_port_t port, mach_msg_option_t options, + mach_msg_timeout_t timeout, + mach_port_t notify_port) +{ + Error error; + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | + LIBLLDB_LOG_VERBOSE)); + + mach_msg_timeout_t mach_msg_timeout = + options & MACH_RCV_TIMEOUT ? timeout : 0; + if (log && ((options & MACH_RCV_TIMEOUT) == 0)) + { + // Dump this log message if we have no timeout in case it never returns + log->Printf("::mach_msg(msg->{bits = %#x, size = %u remote_port = %#x, " + "local_port = %#x, reserved = 0x%x, id = 0x%x}, " + "option = %#x, send_size = 0, rcv_size = %llu, " + "rcv_name = %#x, timeout = %u, notify = %#x)", + exc_msg.hdr.msgh_bits, + exc_msg.hdr.msgh_size, + exc_msg.hdr.msgh_remote_port, + exc_msg.hdr.msgh_local_port, + exc_msg.hdr.msgh_reserved, + exc_msg.hdr.msgh_id, + options, + (uint64_t)sizeof (exc_msg.data), + port, + mach_msg_timeout, + notify_port); + } + + mach_msg_return_t mach_err = + ::mach_msg (&exc_msg.hdr, + options, // options + 0, // Send size + sizeof (exc_msg.data), // Receive size + port, // exception port to watch for + // exception on + mach_msg_timeout, // timeout in msec (obeyed only + // if MACH_RCV_TIMEOUT is ORed + // into the options parameter) + notify_port); + error.SetError(mach_err, eErrorTypeMachKernel); + + // Dump any errors we get + if (error.Fail() && log) + { + log->Printf("::mach_msg(msg->{bits = %#x, size = %u remote_port = %#x, " + "local_port = %#x, reserved = 0x%x, id = 0x%x}, " + "option = %#x, send_size = %u, rcv_size = %lu, rcv_name " + "= %#x, timeout = %u, notify = %#x) failed: %s", + exc_msg.hdr.msgh_bits, + exc_msg.hdr.msgh_size, + exc_msg.hdr.msgh_remote_port, + exc_msg.hdr.msgh_local_port, + exc_msg.hdr.msgh_reserved, + exc_msg.hdr.msgh_id, + options, + 0, + sizeof(exc_msg.data), + port, + mach_msg_timeout, + notify_port, + error.AsCString()); + } + return error; +} + +void +MachException::Message::Dump(Stream &stream) const +{ + stream.Printf(" exc_msg { bits = 0x%8.8x size = 0x%8.8x remote-port = " + "0x%8.8x local-port = 0x%8.8x reserved = 0x%8.8x id = " + "0x%8.8x }\n", + exc_msg.hdr.msgh_bits, + exc_msg.hdr.msgh_size, + exc_msg.hdr.msgh_remote_port, + exc_msg.hdr.msgh_local_port, + exc_msg.hdr.msgh_reserved, + exc_msg.hdr.msgh_id); + + stream.Printf(" reply_msg { bits = 0x%8.8x size = 0x%8.8x remote-port = " + "0x%8.8x local-port = 0x%8.8x reserved = 0x%8.8x id = " + "0x%8.8x }", + reply_msg.hdr.msgh_bits, + reply_msg.hdr.msgh_size, + reply_msg.hdr.msgh_remote_port, + reply_msg.hdr.msgh_local_port, + reply_msg.hdr.msgh_reserved, + reply_msg.hdr.msgh_id); +} + +bool +MachException::Message::CatchExceptionRaise(task_t task) +{ + bool success = false; + // locker will keep a mutex locked until it goes out of scope +// PThreadMutex::Locker locker(&g_message_mutex); + // DNBLogThreaded("calling mach_exc_server"); + state.task_port = task; + g_message = &state; + // The exc_server function is the MIG generated server handling function + // to handle messages from the kernel relating to the occurrence of an + // exception in a thread. Such messages are delivered to the exception port + // set via thread_set_exception_ports or task_set_exception_ports. When an + // exception occurs in a thread, the thread sends an exception message to + // its exception port, blocking in the kernel waiting for the receipt of a + // reply. The exc_server function performs all necessary argument handling + // for this kernel message and calls catch_exception_raise, + // catch_exception_raise_state or catch_exception_raise_state_identity, + // which should handle the exception. If the called routine returns + // KERN_SUCCESS, a reply message will be sent, allowing the thread to + // continue from the point of the exception; otherwise, no reply message + // is sent and the called routine must have dealt with the exception + // thread directly. + if (mach_exc_server (&exc_msg.hdr, &reply_msg.hdr)) + { + success = true; + } + else + { + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | + LIBLLDB_LOG_VERBOSE)); + if (log) + log->Printf("MachException::Message::%s(): mach_exc_server " + "returned zero...", __FUNCTION__); + } + g_message = NULL; + return success; +} + +Error +MachException::Message::Reply(::pid_t inferior_pid, task_t inferior_task, + int signal) +{ + // Reply to the exception... + Error error; + + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | + LIBLLDB_LOG_VERBOSE)); + + // If we had a soft signal, we need to update the thread first so it can + // continue without signaling + int soft_signal = state.SoftSignal(); + if (soft_signal) + { + int state_pid = -1; + if (inferior_task == state.task_port) + { + // This is our task, so we can update the signal to send to it + state_pid = inferior_pid; + soft_signal = signal; + } + else + { + auto mach_err = ::pid_for_task(state.task_port, &state_pid); + if (mach_err) + { + error.SetError(mach_err, eErrorTypeMachKernel); + if (log) + log->Printf("MachException::Message::%s(): pid_for_task() " + "failed: %s", __FUNCTION__, error.AsCString()); + return error; + } + } + + lldbassert(state_pid != -1); + if (state_pid != -1) + { + errno = 0; + caddr_t thread_port_caddr = (caddr_t)(uintptr_t)state.thread_port; + if (::ptrace(PT_THUPDATE, state_pid, thread_port_caddr, soft_signal) + != 0) + error.SetError(errno, eErrorTypePOSIX); + + if (!error.Success()) + { + if (log) + log->Printf("::ptrace(request = PT_THUPDATE, pid = " + "0x%4.4x, tid = 0x%4.4x, signal = %i)", + state_pid, state.thread_port, soft_signal); + return error; + } + } + } + + if (log) + log->Printf("::mach_msg ( msg->{bits = %#x, size = %u, remote_port " + "= %#x, local_port = %#x, reserved = 0x%x, id = 0x%x}, " + "option = %#x, send_size = %u, rcv_size = %u, rcv_name " + "= %#x, timeout = %u, notify = %#x)", + reply_msg.hdr.msgh_bits, + reply_msg.hdr.msgh_size, + reply_msg.hdr.msgh_remote_port, + reply_msg.hdr.msgh_local_port, + reply_msg.hdr.msgh_reserved, + reply_msg.hdr.msgh_id, + MACH_SEND_MSG | MACH_SEND_INTERRUPT, + reply_msg.hdr.msgh_size, + 0, + MACH_PORT_NULL, + MACH_MSG_TIMEOUT_NONE, + MACH_PORT_NULL); + + auto mach_err = ::mach_msg(&reply_msg.hdr, + MACH_SEND_MSG | MACH_SEND_INTERRUPT, + reply_msg.hdr.msgh_size, + 0, + MACH_PORT_NULL, + MACH_MSG_TIMEOUT_NONE, + MACH_PORT_NULL); + if (mach_err) + error.SetError(mach_err, eErrorTypeMachKernel); + + // Log our error if we have one. + if (error.Fail() && log) + { + if (error.GetError() == MACH_SEND_INTERRUPTED) + { + log->PutCString("::mach_msg() - send interrupted"); + // TODO: keep retrying to reply??? + } + else if (state.task_port == inferior_task) + { + log->Printf("mach_msg(): returned an error when replying " + "to a mach exception: error = %u (%s)", + error.GetError(), error.AsCString()); + } + else + { + log->Printf("::mach_msg() - failed (child of task): %u (%s)", + error.GetError(), error.AsCString()); + } + } + + return error; +} + +#define PREV_EXC_MASK_ALL (EXC_MASK_BAD_ACCESS | \ + EXC_MASK_BAD_INSTRUCTION | \ + EXC_MASK_ARITHMETIC | \ + EXC_MASK_EMULATION | \ + EXC_MASK_SOFTWARE | \ + EXC_MASK_BREAKPOINT | \ + EXC_MASK_SYSCALL | \ + EXC_MASK_MACH_SYSCALL | \ + EXC_MASK_RPC_ALERT | \ + EXC_MASK_MACHINE) + +// Don't listen for EXC_RESOURCE, it should really get handled by the system handler. + +#ifndef EXC_RESOURCE +#define EXC_RESOURCE 11 +#endif + +#ifndef EXC_MASK_RESOURCE +#define EXC_MASK_RESOURCE (1 << EXC_RESOURCE) +#endif + +#define LLDB_EXC_MASK (EXC_MASK_ALL & ~EXC_MASK_RESOURCE) + +Error +MachException::PortInfo::Save(task_t task) +{ + Error error; + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | + LIBLLDB_LOG_VERBOSE)); + + if (log) + log->Printf("MachException::PortInfo::%s(task = 0x%4.4x)", + __FUNCTION__, task); + + // Be careful to be able to have debugserver built on a newer OS than what + // it is currently running on by being able to start with all exceptions + // and back off to just what is supported on the current system + mask = LLDB_EXC_MASK; + + count = (sizeof(ports) / sizeof(ports[0])); + auto mach_err = ::task_get_exception_ports(task, mask, masks, &count, ports, + behaviors, flavors); + if (mach_err) + error.SetError(mach_err, eErrorTypeMachKernel); + + if (log) + { + if (error.Success()) + { + log->Printf("::task_get_exception_ports(task = 0x%4.4x, mask = " + "0x%x, maskCnt => %u, ports, behaviors, flavors)", + task, mask, count); + } + else + { + log->Printf("::task_get_exception_ports(task = 0x%4.4x, mask = 0x%x, " + "maskCnt => %u, ports, behaviors, flavors) error: %u (%s)", + task, mask, count, error.GetError(), error.AsCString()); + } + } + + if ((error.GetError() == KERN_INVALID_ARGUMENT) && + (mask != PREV_EXC_MASK_ALL)) + { + mask = PREV_EXC_MASK_ALL; + count = (sizeof(ports) / sizeof(ports[0])); + mach_err = ::task_get_exception_ports(task, mask, masks, &count, ports, + behaviors, flavors); + error.SetError(mach_err, eErrorTypeMachKernel); + if (log) + { + if (error.Success()) + { + log->Printf("::task_get_exception_ports(task = 0x%4.4x, " + "mask = 0x%x, maskCnt => %u, ports, behaviors, " + "flavors)", task, mask, count); + } + else + { + log->Printf("::task_get_exception_ports(task = 0x%4.4x, mask = " + "0x%x, maskCnt => %u, ports, behaviors, flavors) " + "error: %u (%s)", task, mask, count, + error.GetError(), error.AsCString()); + } + } + } + if (error.Fail()) + { + mask = 0; + count = 0; + } + return error; +} + +Error +MachException::PortInfo::Restore(task_t task) +{ + Error error; + + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_PROCESS | + LIBLLDB_LOG_VERBOSE)); + + if (log) + log->Printf("MachException::PortInfo::Restore(task = 0x%4.4x)", task); + + uint32_t i = 0; + if (count > 0) + { + for (i = 0; i < count; i++) + { + auto mach_err = ::task_set_exception_ports(task, masks[i], ports[i], + behaviors[i], + flavors[i]); + if (mach_err) + error.SetError(mach_err, eErrorTypeMachKernel); + if (log) + { + if (error.Success()) + { + log->Printf("::task_set_exception_ports(task = 0x%4.4x, " + "exception_mask = 0x%8.8x, new_port = 0x%4.4x, " + "behavior = 0x%8.8x, new_flavor = 0x%8.8x)", + task, masks[i], ports[i], behaviors[i], + flavors[i]); + } + else + { + log->Printf("::task_set_exception_ports(task = 0x%4.4x, " + "exception_mask = 0x%8.8x, new_port = 0x%4.4x, " + "behavior = 0x%8.8x, new_flavor = 0x%8.8x): " + "error %u (%s)", task, masks[i], ports[i], + behaviors[i], flavors[i], error.GetError(), + error.AsCString()); + } + } + + // Bail if we encounter any errors + if (error.Fail()) + break; + } + } + + count = 0; + return error; +} + +const char * +MachException::Name(exception_type_t exc_type) +{ + switch (exc_type) + { + case EXC_BAD_ACCESS: return "EXC_BAD_ACCESS"; + case EXC_BAD_INSTRUCTION: return "EXC_BAD_INSTRUCTION"; + case EXC_ARITHMETIC: return "EXC_ARITHMETIC"; + case EXC_EMULATION: return "EXC_EMULATION"; + case EXC_SOFTWARE: return "EXC_SOFTWARE"; + case EXC_BREAKPOINT: return "EXC_BREAKPOINT"; + case EXC_SYSCALL: return "EXC_SYSCALL"; + case EXC_MACH_SYSCALL: return "EXC_MACH_SYSCALL"; + case EXC_RPC_ALERT: return "EXC_RPC_ALERT"; +#ifdef EXC_CRASH + case EXC_CRASH: return "EXC_CRASH"; +#endif + default: + break; + } + return NULL; +} |