Source/PLCrashAsyncThread.c (130 lines of code) (raw):

/* * Author: Landon Fuller <landonf@plausiblelabs.com> * * Copyright (c) 2008-2013 Plausible Labs Cooperative, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "PLCrashAsyncThread.h" #include "PLCrashMacros.h" /** * @internal * @ingroup plcrash_async * @defgroup plcrash_async_thread Thread State Handling * * An async-safe and architecture neutral API for introspecting and permuting thread state. * @{ */ /** * Initialize an empty @a thread_state with the given @a cpu_type. Not all CPU types are supported, in which case * PLCRASH_ENOTSUP will be returned. * * All registers will be marked as unavailable. * * @param thread_state The thread state to be initialized. * @param cpu_type The target thread CPU type. */ plcrash_error_t plcrash_async_thread_state_init (plcrash_async_thread_state_t *thread_state, cpu_type_t cpu_type) { memset(thread_state, 0, sizeof(*thread_state)); switch (cpu_type) { #if PLCRASH_ASYNC_THREAD_X86_SUPPORT case CPU_TYPE_X86: thread_state->x86_state.thread.tsh.count = x86_THREAD_STATE32_COUNT; thread_state->x86_state.thread.tsh.flavor = x86_THREAD_STATE32; thread_state->x86_state.exception.esh.count = x86_EXCEPTION_STATE32_COUNT; thread_state->x86_state.exception.esh.flavor = x86_EXCEPTION_STATE32; thread_state->stack_direction = PLCRASH_ASYNC_THREAD_STACK_DIRECTION_DOWN; thread_state->greg_size = 4; break; case CPU_TYPE_X86_64: thread_state->x86_state.thread.tsh.count = x86_THREAD_STATE64_COUNT; thread_state->x86_state.thread.tsh.flavor = x86_THREAD_STATE64; thread_state->x86_state.exception.esh.count = x86_EXCEPTION_STATE64_COUNT; thread_state->x86_state.exception.esh.flavor = x86_EXCEPTION_STATE64; thread_state->stack_direction = PLCRASH_ASYNC_THREAD_STACK_DIRECTION_DOWN; thread_state->greg_size = 8; break; #endif /* PLCRASH_ASYNC_THREAD_X86_SUPPORT */ #if PLCRASH_ASYNC_THREAD_ARM_SUPPORT case CPU_TYPE_ARM: thread_state->arm_state.thread.ash.flavor = ARM_THREAD_STATE32; thread_state->arm_state.thread.ash.count = ARM_THREAD_STATE32_COUNT; thread_state->stack_direction = PLCRASH_ASYNC_THREAD_STACK_DIRECTION_DOWN; thread_state->greg_size = 4; break; case CPU_TYPE_ARM64: thread_state->arm_state.thread.ash.flavor = ARM_THREAD_STATE64; thread_state->arm_state.thread.ash.count = ARM_THREAD_STATE64_COUNT; thread_state->stack_direction = PLCRASH_ASYNC_THREAD_STACK_DIRECTION_DOWN; thread_state->greg_size = 8; break; #endif /* PLCRASH_ASYNC_THREAD_ARM_SUPPORT */ default: return PLCRASH_ENOTSUP; } plcrash_async_thread_state_clear_all_regs(thread_state); return PLCRASH_ESUCCESS; } /** * Initialize the @a thread_state using the provided context. * * @param thread_state The thread state to be initialized. * @param mctx The context to use for cursor initialization. * * All registers will be marked as available. */ void plcrash_async_thread_state_mcontext_init (plcrash_async_thread_state_t *thread_state, pl_mcontext_t *mctx) { /* * Copy in the thread state. Unlike the mach thread variants, mcontext_t may only represent * the thread state of the host process, and we may assume that the compilation target matches the mcontext_t * thread type. */ #if defined(PLCRASH_ASYNC_THREAD_ARM_SUPPORT) && defined(__LP64__) plcrash_async_thread_state_init(thread_state, CPU_TYPE_ARM64); /* Sanity check. */ PLCF_ASSERT(sizeof(mctx->__ss) == sizeof(thread_state->arm_state.thread.ts_64)); plcrash_async_memcpy(&thread_state->arm_state.thread.ts_64, &mctx->__ss, sizeof(thread_state->arm_state.thread.ts_64)); #elif defined(PLCRASH_ASYNC_THREAD_ARM_SUPPORT) plcrash_async_thread_state_init(thread_state, CPU_TYPE_ARM); /* Sanity check. */ PLCF_ASSERT(sizeof(mctx->__ss) == sizeof(thread_state->arm_state.thread.ts_32)); plcrash_async_memcpy(&thread_state->arm_state.thread.ts_32, &mctx->__ss, sizeof(thread_state->arm_state.thread.ts_32)); #elif defined(PLCRASH_ASYNC_THREAD_X86_SUPPORT) && defined(__LP64__) plcrash_async_thread_state_init(thread_state, CPU_TYPE_X86_64); /* Sanity check. */ PLCF_ASSERT(sizeof(mctx->__ss) == sizeof(thread_state->x86_state.thread.uts.ts64)); PLCF_ASSERT(sizeof(mctx->__es) == sizeof(thread_state->x86_state.exception.ues.es64)); plcrash_async_memcpy(&thread_state->x86_state.thread.uts.ts64, &mctx->__ss, sizeof(thread_state->x86_state.thread.uts.ts64)); plcrash_async_memcpy(&thread_state->x86_state.exception.ues.es64, &mctx->__es, sizeof(thread_state->x86_state.exception.ues.es64)); #elif defined(PLCRASH_ASYNC_THREAD_X86_SUPPORT) plcrash_async_thread_state_init(thread_state, CPU_TYPE_X86); /* Sanity check. */ PLCF_ASSERT(sizeof(mctx->__ss) == sizeof(thread_state->x86_state.thread.uts.ts32)); PLCF_ASSERT(sizeof(mctx->__es) == sizeof(thread_state->x86_state.exception.ues.es32)); plcrash_async_memcpy(&thread_state->x86_state.thread.uts.ts32, &mctx->__ss, sizeof(thread_state->x86_state.thread.uts.ts32)); plcrash_async_memcpy(&thread_state->x86_state.exception.ues.es32, &mctx->__es, sizeof(thread_state->x86_state.exception.ues.es32)); #else #error Add platform support #endif /* Mark all registers as available */ memset(&thread_state->valid_regs, 0xFF, sizeof(thread_state->valid_regs)); } /** * Initialize the @a thread_state using thread state fetched from the given mach @a thread. If the thread is not * suspended, the fetched state may be inconsistent. * * All registers will be marked as available. * * @param thread_state The thread state to be initialized. * @param thread The thread from which to fetch thread state. * * @return Returns PLFRAME_ESUCCESS on success, or standard plframe_error_t code if an error occurs. */ plcrash_error_t plcrash_async_thread_state_mach_thread_init (plcrash_async_thread_state_t *thread_state, thread_t thread) { mach_msg_type_number_t state_count; kern_return_t kr; #if defined(PLCRASH_ASYNC_THREAD_ARM_SUPPORT) /* Fetch the thread state */ state_count = ARM_UNIFIED_THREAD_STATE_COUNT; kr = thread_get_state(thread, ARM_UNIFIED_THREAD_STATE, (thread_state_t) &thread_state->arm_state.thread, &state_count); if (kr != KERN_SUCCESS) { PLCF_DEBUG("Fetch of ARM thread state failed with Mach error: %d", kr); return PLCRASH_EINTERNAL; } /* Platform meta-data */ thread_state->stack_direction = PLCRASH_ASYNC_THREAD_STACK_DIRECTION_DOWN; if (thread_state->arm_state.thread.ash.flavor == ARM_THREAD_STATE64) { thread_state->greg_size = 8; } else { thread_state->greg_size = 4; } #elif defined(PLCRASH_ASYNC_THREAD_X86_SUPPORT) /* Fetch the thread state */ state_count = x86_THREAD_STATE_COUNT; kr = thread_get_state(thread, x86_THREAD_STATE, (thread_state_t) &thread_state->x86_state.thread, &state_count); if (kr != KERN_SUCCESS) { PLCF_DEBUG("Fetch of x86 thread state failed with Mach error: %d", kr); return PLCRASH_EINTERNAL; } /* Fetch the exception state */ state_count = x86_EXCEPTION_STATE_COUNT; kr = thread_get_state(thread, x86_EXCEPTION_STATE, (thread_state_t) &thread_state->x86_state.exception, &state_count); if (kr != KERN_SUCCESS) { PLCF_DEBUG("Fetch of x86 exception state failed with Mach error: %d", kr); return PLCRASH_EINTERNAL; } /* Platform meta-data */ thread_state->stack_direction = PLCRASH_ASYNC_THREAD_STACK_DIRECTION_DOWN; if (thread_state->x86_state.thread.tsh.flavor == x86_THREAD_STATE64) { thread_state->greg_size = 8; } else { thread_state->greg_size = 4; } #else #error Add platform support #endif /* Mark all registers as available */ memset(&thread_state->valid_regs, 0xFF, sizeof(thread_state->valid_regs)); return PLCRASH_ESUCCESS; } /** * Copy thread state @a source to @a dest. * * @param dest The destination to which the thread state will be copied. * @param source The thread state to be copied. * * @note If @a dest and @a source overlap, behavior is undefined. */ void plcrash_async_thread_state_copy (plcrash_async_thread_state_t *dest, const plcrash_async_thread_state_t *source) { plcrash_async_memcpy(dest, source, sizeof(*dest)); } /** * Return true if @a regnum is set in @a thread_state, false otherwise. * * @param thread_state The thread state to test. * @param regnum The register number to test for. */ bool plcrash_async_thread_state_has_reg (const plcrash_async_thread_state_t *thread_state, plcrash_regnum_t regnum) { if ((thread_state->valid_regs & (1ULL<<regnum)) != 0) return true; return false; } /** * Clear @a regnum in @a thread_state. * * @param thread_state The thread state to modify. * @param regnum The register to unset. */ void plcrash_async_thread_state_clear_reg (plcrash_async_thread_state_t *thread_state, plcrash_regnum_t regnum) { thread_state->valid_regs &= ~(1ULL<<regnum); } /** * Clear all registers in @a thread_state. * * @param thread_state The thread state to modify. */ void plcrash_async_thread_state_clear_all_regs (plcrash_async_thread_state_t *thread_state) { thread_state->valid_regs = 0x0; } /** * Return the direction used for stack growth by @a thread_state. * * @param thread_state The target thread state. */ plcrash_async_thread_stack_direction_t plcrash_async_thread_state_get_stack_direction (const plcrash_async_thread_state_t *thread_state) { return thread_state->stack_direction; } /** * Return the size (in bytes) of @a thread_state's general purpose registers. This value will be used to determine the * size of general purpose registers pushed/popped from the target thread's stack. * * @param thread_state The target thread state. */ size_t plcrash_async_thread_state_get_greg_size (const plcrash_async_thread_state_t *thread_state) { return thread_state->greg_size; } /* * @} */