in Source/PLCrashAsyncCompactUnwindEncoding.c [1043:1221]
plcrash_error_t plcrash_async_cfe_entry_apply (task_t task,
pl_vm_address_t function_address,
const plcrash_async_thread_state_t *thread_state,
plcrash_async_cfe_entry_t *entry,
plcrash_async_thread_state_t *new_thread_state)
{
/* Set up register load target */
size_t greg_size = plcrash_async_thread_state_get_greg_size(thread_state);
bool x64 = (greg_size == sizeof(uint64_t));
void *dest;
union {
/* Room for (frame pointer, return address) + saved registers */
uint64_t greg64[PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX];
uint32_t greg32[PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX];
} regs;
if (x64)
dest = regs.greg64;
else
dest = regs.greg32;
/* Sanity check: We'll use this buffer for popping the fp and pc, as well as restoring the saved registers. */
PLCF_ASSERT(PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX >= 2);
/* Initialize the new thread state */
*new_thread_state = *thread_state;
plcrash_async_thread_state_clear_volatile_regs(new_thread_state);
pl_vm_address_t saved_reg_addr = 0x0;
plcrash_async_cfe_entry_type_t entry_type = plcrash_async_cfe_entry_type(entry);
switch (entry_type) {
case PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAME_PTR: {
plcrash_error_t err;
/* Fetch the current frame pointer */
if (!plcrash_async_thread_state_has_reg(thread_state, PLCRASH_REG_FP)) {
PLCF_DEBUG("Can't apply FRAME_PTR unwind type without a valid frame pointer");
return PLCRASH_ENOTFOUND;
}
plcrash_greg_t fp = plcrash_async_thread_state_get_reg(thread_state, PLCRASH_REG_FP);
/* Address of saved registers */
saved_reg_addr = (pl_vm_address_t)(fp + entry->stack_offset);
/* Restore the previous frame's stack pointer from the saved frame pointer. This is
* the FP + saved FP + return address. */
pl_vm_address_t new_sp;
if (!plcrash_async_address_apply_offset((pl_vm_address_t) fp, greg_size * 2, &new_sp)) {
PLCF_DEBUG("Current frame pointer falls outside of addressable bounds");
return PLCRASH_EINVAL;
}
plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_SP, new_sp);
/* Read the saved fp and retaddr */
err = plcrash_async_task_memcpy(task, (pl_vm_address_t) fp, 0, dest, greg_size * 2);
if (err != PLCRASH_ESUCCESS) {
PLCF_DEBUG("Failed to read frame data at address 0x%" PRIx64 ": %d", (uint64_t) fp, err);
return err;
}
// XXX: This assumes downward stack growth.
if (x64) {
plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_FP, regs.greg64[0]);
plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_IP, regs.greg64[1]);
} else {
plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_FP, regs.greg32[0]);
plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_IP, regs.greg32[1]);
}
break;
}
case PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_INDIRECT:
// Fallthrough
case PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_IMMD: {
plcrash_error_t err;
/* Fetch the current stack pointer */
if (!plcrash_async_thread_state_has_reg(thread_state, PLCRASH_REG_SP)) {
PLCF_DEBUG("Can't apply FRAME_IMMD unwind type without a valid stack pointer");
return PLCRASH_ENOTFOUND;
}
/* Extract the stack size */
pl_vm_address_t stack_size = entry->stack_offset;
if (entry_type == PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_INDIRECT) {
/* Stack size is encoded as a 32-bit value within the target process' TEXT segment; the value
* provided from the entry is used as an offset from the start of the function to the actual
* stack size. */
uint32_t indirect;
err = plcrash_async_task_memcpy(task, function_address, stack_size, &indirect, sizeof(indirect));
if (err != PLCRASH_ESUCCESS) {
PLCF_DEBUG("Failed to read indirect stack size from 0x%" PRIx64 " + 0x%" PRIx64 ": %d",
(uint64_t) function_address, (uint64_t)stack_size, err);
return err;
}
stack_size = indirect + entry->stack_adjust;
}
/* Compute the stack pointer address */
plcrash_greg_t sp = stack_size + plcrash_async_thread_state_get_reg(thread_state, PLCRASH_REG_SP);
plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_SP, sp);
if (entry->return_address_register == PLCRASH_REG_INVALID) {
/* Return address is on the stack */
pl_vm_address_t retaddr = (pl_vm_address_t)(sp - greg_size);
saved_reg_addr = retaddr - (greg_size * entry->register_count); /* retaddr - [saved registers] */
/* Original SP is found just before the return address. */
plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_SP, retaddr + greg_size);
/* Read the saved return address */
err = plcrash_async_task_memcpy(task, (pl_vm_address_t) retaddr, 0, dest, greg_size);
if (err != PLCRASH_ESUCCESS) {
PLCF_DEBUG("Failed to read return address from 0x%" PRIx64 ": %d", (uint64_t) retaddr, err);
return err;
}
if (x64) {
plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_IP, regs.greg64[0]);
} else {
plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_IP, regs.greg32[0]);
}
} else {
/* Return address is in a register; verify that the register is available */
if (!plcrash_async_thread_state_has_reg(thread_state, entry->return_address_register)) {
PLCF_DEBUG("The specified return_address_register '%s' is not available", plcrash_async_thread_state_get_reg_name(thread_state, entry->return_address_register));
return PLCRASH_ENOTFOUND;
}
/* Copy the return address value to the new thread state's IP */
plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_IP, plcrash_async_thread_state_get_reg(thread_state, entry->return_address_register));
/* Saved registers are found below the new stack pointer. */
saved_reg_addr = (pl_vm_address_t) sp - (greg_size * entry->register_count); /* sp - [saved registers] */
}
break;
}
case PLCRASH_ASYNC_CFE_ENTRY_TYPE_DWARF:
return PLCRASH_ENOTSUP;
case PLCRASH_ASYNC_CFE_ENTRY_TYPE_NONE:
return PLCRASH_ENOTSUP;
}
/* Extract the saved registers */
uint32_t register_count = plcrash_async_cfe_entry_register_count(entry);
plcrash_regnum_t register_list[PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX];
plcrash_async_cfe_entry_register_list(entry, register_list);
for (uint32_t i = 0; i < register_count; i++) {
/* The register list may be sparse */
if (register_list[i] == PLCRASH_REG_INVALID)
continue;
/* Fetch and save register data */
plcrash_error_t err;
err = plcrash_async_task_memcpy(task, (pl_vm_address_t) saved_reg_addr, i*greg_size, dest, greg_size);
if (err != PLCRASH_ESUCCESS) {
PLCF_DEBUG("Failed to read register data for index %s: %d", plcrash_async_thread_state_get_reg_name(thread_state, register_list[i]), err);
return err;
}
if (x64) {
plcrash_async_thread_state_set_reg(new_thread_state, register_list[i], regs.greg64[0]);
} else {
plcrash_async_thread_state_set_reg(new_thread_state, register_list[i], regs.greg32[0]);
}
}
return PLCRASH_ESUCCESS;
}