Source/PLCrashFrameStackUnwind.c (60 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 "PLCrashFrameStackUnwind.h"
#include "PLCrashAsync.h"
/**
* Fetch the next frame, assuming a valid frame pointer in @a cursor's current frame.
*
* @param task The task containing the target frame stack.
* @param current_frame The current stack frame.
* @param previous_frame The previous stack frame, or NULL if this is the first frame.
* @param next_frame The new frame to be initialized.
*
* @return Returns PLFRAME_ESUCCESS on success, PLFRAME_ENOFRAME is no additional frames are available, or a standard plframe_error_t code if an error occurs.
*/
plframe_error_t plframe_cursor_read_frame_ptr (task_t task,
plcrash_async_image_list_t *image_list,
const plframe_stackframe_t *current_frame,
const plframe_stackframe_t *previous_frame,
plframe_stackframe_t *next_frame)
{
/* Determine the appropriate type width for the target thread */
bool x64 = plcrash_async_thread_state_get_greg_size(¤t_frame->thread_state) == sizeof(uint64_t);
union {
uint64_t greg64[2];
uint32_t greg32[2];
} regs;
void *dest;
size_t len;
if (x64) {
dest = regs.greg64;
len = sizeof(regs.greg64);
} else {
dest = regs.greg32;
len = sizeof(regs.greg32);
}
/* Verify that we have a frame pointer to work with */
if (!plcrash_async_thread_state_has_reg(¤t_frame->thread_state, PLCRASH_REG_FP)) {
PLCF_DEBUG("The frame pointer is unavailable, can't read saved register.")
return PLFRAME_EBADFRAME;
}
/* Fetch the current frame's frame pointer */
plcrash_greg_t fp = plcrash_async_thread_state_get_reg(¤t_frame->thread_state, PLCRASH_REG_FP);
/* A NULL FP means a terminated frame */
if (fp == 0x0)
return PLFRAME_ENOFRAME;
/* Verify that the stack is growing in the right direction. */
if (previous_frame != NULL && plcrash_async_thread_state_has_reg(&previous_frame->thread_state, PLCRASH_REG_FP)) {
plcrash_greg_t prev_fp = plcrash_async_thread_state_get_reg(&previous_frame->thread_state, PLCRASH_REG_FP);
plcrash_async_thread_stack_direction_t stack_direction = plcrash_async_thread_state_get_stack_direction(¤t_frame->thread_state);
if ((stack_direction == PLCRASH_ASYNC_THREAD_STACK_DIRECTION_DOWN && fp < prev_fp) ||
(stack_direction == PLCRASH_ASYNC_THREAD_STACK_DIRECTION_UP && fp > prev_fp))
{
PLCF_DEBUG("Stack growing in wrong direction, terminating stack walk");
return PLFRAME_EBADFRAME;
}
}
/* Read the registers off the stack via the frame pointer */
plcrash_greg_t new_fp;
plcrash_greg_t new_pc;
plcrash_error_t err;
err = plcrash_async_task_memcpy(task, (pl_vm_address_t) fp, 0, dest, len);
if (err != PLCRASH_ESUCCESS) {
PLCF_DEBUG("Failed to read frame: %d", err);
return PLFRAME_EBADFRAME;
}
if (x64) {
new_fp = regs.greg64[0];
new_pc = regs.greg64[1];
} else {
new_fp = regs.greg32[0];
new_pc = regs.greg32[1];
}
/* Initialize the new frame, deriving state from the previous frame. */
*next_frame = *current_frame;
plcrash_async_thread_state_clear_all_regs(&next_frame->thread_state);
plcrash_async_thread_state_set_reg(&next_frame->thread_state, PLCRASH_REG_FP, new_fp);
plcrash_async_thread_state_set_reg(&next_frame->thread_state, PLCRASH_REG_IP, new_pc);
return PLFRAME_ESUCCESS;
}