Source/PLCrashFrameWalker.c (102 lines of code) (raw):
/*
* Author: Landon Fuller <landonf@plausiblelabs.com>
*
* Copyright (c) 2008-2009 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 "PLCrashFrameWalker.h"
#include "PLCrashAsync.h"
#include "PLCrashFrameStackUnwind.h"
#include "PLCrashFrameCompactUnwind.h"
#include "PLCrashFrameDWARFUnwind.h"
#include "PLCrashFeatureConfig.h"
#pragma mark Error Handling
/**
* Return an error description for the given plframe_error_t.
*/
const char *plframe_strerror (plframe_error_t error) {
switch (error) {
case PLFRAME_ESUCCESS:
return "No error";
case PLFRAME_EUNKNOWN:
return "Unknown error";
case PLFRAME_ENOFRAME:
return "No frames are available";
case PLFRAME_EBADFRAME:
return "Corrupted frame";
case PLFRAME_ENOTSUP:
return "Operation not supported";
case PLFRAME_EINVAL:
return "Invalid argument";
case PLFRAME_INTERNAL:
return "Internal error";
case PLFRAME_EBADREG:
return "Invalid register";
}
/* Should be unreachable */
return "Unhandled error code";
}
#pragma mark Frame Walking
/**
* @internal
* Shared initializer. Assumes that the initial frame has all registers available.
*
* @param cursor Cursor record to be initialized.
* @param task The task from which @a uap was derived. All memory will be mapped from this task.
* @param image_list The task's current image list. This is a borrowed reference, and must remain valid for the lifetime of the cursor.
*/
static void plframe_cursor_internal_init (plframe_cursor_t *cursor, task_t task, plcrash_async_image_list_t *image_list) {
cursor->depth = 0;
cursor->task = task;
cursor->image_list = image_list;
mach_port_mod_refs(mach_task_self(), cursor->task, MACH_PORT_RIGHT_SEND, 1);
}
/**
* Initialize the frame cursor using the provided thread state.
*
* @param cursor Cursor record to be initialized.
* @param task The task from which @a uap was derived. All memory will be mapped from this task.
* @param thread_state The thread state to use for cursor initialization.
* @param image_list The task's current image list. This is a borrowed reference, and must remain valid for the lifetime of the cursor.
*
* @return Returns PLFRAME_ESUCCESS on success, or standard plframe_error_t code if an error occurs.
*
* @warning Callers must call plframe_cursor_free() on @a cursor to free any associated resources, even if initialization
* fails.
*/
plframe_error_t plframe_cursor_init (plframe_cursor_t *cursor, task_t task, plcrash_async_thread_state_t *thread_state, plcrash_async_image_list_t *image_list) {
plframe_cursor_internal_init(cursor, task, image_list);
plcrash_async_memcpy(&cursor->frame.thread_state, thread_state, sizeof(cursor->frame.thread_state));
return PLFRAME_ESUCCESS;
}
/**
* Initialize the frame cursor by acquiring state from the provided mach thread. If the thread is not suspended,
* the fetched state may be inconsistent.
*
* @param cursor Cursor record to be initialized.
* @param task The task in which @a thread is running. All memory will be mapped from this task.
* @param thread The thread to use for cursor initialization.
* @param image_list The task's current image list. This is a borrowed reference, and must remain valid for the lifetime of the cursor.
*
* @return Returns PLFRAME_ESUCCESS on success, or standard plframe_error_t code if an error occurs.
*
* @warning Callers must call plframe_cursor_free() on @a cursor to free any associated resources, even if initialization
* fails.
*/
plframe_error_t plframe_cursor_thread_init (plframe_cursor_t *cursor, task_t task, thread_t thread, plcrash_async_image_list_t *image_list) {
/* Standard initialization */
plframe_cursor_internal_init(cursor, task, image_list);
return (plframe_error_t)plcrash_async_thread_state_mach_thread_init(&cursor->frame.thread_state, thread);
}
/**
* Fetch the next frame using the provided frame readers.
*
* @param cursor A cursor instance initialized with plframe_cursor_init();
* @param readers Frame readers to be used to fetch the next frame. Each reader will be executed in the provided order until a valid frame is read.
* @param reader_count The number of readers provided in @a readers.
* @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_next_with_readers (plframe_cursor_t *cursor, plframe_cursor_frame_reader_t *readers[], size_t reader_count) {
/* The first frame is already available via existing thread state. */
if (cursor->depth == 0) {
cursor->depth++;
return PLFRAME_ESUCCESS;
}
/* A previous frame is only available if we're on the second frame */
plframe_stackframe_t *prev_frame = NULL;
if (cursor->depth >= 2)
prev_frame = &cursor->prev_frame;
/* Read in the next frame using the first successful frame reader. */
plframe_stackframe_t frame;
plframe_error_t ferr = PLFRAME_EINVAL; // default return value if reader_count is 0.
for (size_t i = 0; i < reader_count; i++) {
ferr = readers[i](cursor->task, cursor->image_list, &cursor->frame, prev_frame, &frame);
if (ferr == PLFRAME_ESUCCESS)
break;
}
if (ferr != PLFRAME_ESUCCESS) {
return ferr;
}
/* Check for completion */
if (!plcrash_async_thread_state_has_reg(&frame.thread_state, PLCRASH_REG_IP)) {
PLCF_DEBUG("Missing expected IP value in successfully read frame");
return PLFRAME_ENOFRAME;
}
/* A pc within the NULL page is a terminating frame */
plcrash_greg_t ip = plcrash_async_thread_state_get_reg(&frame.thread_state, PLCRASH_REG_IP);
if (ip <= PAGE_SIZE)
return PLFRAME_ENOFRAME;
/* Save the newly fetched frame */
cursor->prev_frame = cursor->frame;
cursor->frame = frame;
cursor->depth++;
return PLFRAME_ESUCCESS;
}
/**
* Fetch the next frame.
*
* @param cursor A cursor instance initialized with plframe_cursor_init();
* @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_next (plframe_cursor_t *cursor) {
plframe_cursor_frame_reader_t *readers[] = {
#if PLCRASH_FEATURE_UNWIND_COMPACT
plframe_cursor_read_compact_unwind,
#endif
#if PLCRASH_FEATURE_UNWIND_DWARF
plframe_cursor_read_dwarf_unwind,
#endif
plframe_cursor_read_frame_ptr
};
return plframe_cursor_next_with_readers(cursor, readers, sizeof(readers)/sizeof(readers[0]));
}
/**
* Get a register value. Returns PLFRAME_ENOTSUP if the given register is unavailable within the current frame.
*
* @param cursor A cursor instance representing a valid frame, as initialized by plframe_cursor_next().
* @param regnum The register to fetch from the current frame's state.
* @param reg On success, will be set to the register's value.
*/
plframe_error_t plframe_cursor_get_reg (plframe_cursor_t *cursor, plcrash_regnum_t regnum, plcrash_greg_t *reg) {
/* Verify that the register is available */
if (!plcrash_async_thread_state_has_reg(&cursor->frame.thread_state, regnum))
return PLFRAME_ENOTSUP;
/* Fetch from thread state */
*reg = plcrash_async_thread_state_get_reg(&cursor->frame.thread_state, regnum);
return PLFRAME_ESUCCESS;
}
/**
* Get a register's name.
*
* @param cursor A cursor instance initialized with plframe_cursor_init();
* @param regnum The register number for which a name should be returned.
*/
char const *plframe_cursor_get_regname (plframe_cursor_t *cursor, plcrash_regnum_t regnum) {
return plcrash_async_thread_state_get_reg_name(&cursor->frame.thread_state, regnum);
}
/**
* Get the total number of registers supported by the @a cursor's target thread.
*
* @param cursor The target cursor.
*/
size_t plframe_cursor_get_regcount (plframe_cursor_t *cursor) {
return plcrash_async_thread_state_get_reg_count(&cursor->frame.thread_state);
}
/**
* Free any resources associated with the frame cursor.
*
* @param cursor Cursor record to be freed
*/
void plframe_cursor_free(plframe_cursor_t *cursor) {
if (cursor->task != MACH_PORT_NULL)
mach_port_mod_refs(mach_task_self(), cursor->task, MACH_PORT_RIGHT_SEND, -1);
}