Source/PLCrashAsyncDwarfFDE.cpp (127 lines of code) (raw):
/*
* Copyright (c) 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 "PLCrashAsyncDwarfFDE.hpp"
#include "PLCrashAsyncDwarfCIE.hpp"
#include "PLCrashFeatureConfig.h"
#include <inttypes.h>
#if PLCRASH_FEATURE_UNWIND_DWARF
PLCR_CPP_BEGIN_NS
namespace async {
/**
* @internal
* @ingroup plcrash_async_dwarf
* @{
*/
/**
* Decode FDE info at target-relative @a address.
*
* Any resources held by a successfully initialized instance must be freed via plcrash_async_dwarf_fde_info_free();
*
* @param info The FDE record to be initialized.
* @param mobj The memory object containing frame data (eh_frame or debug_frame) at the start address.
* @param byteorder The byte order of the data referenced by @a mobj.
* @param fde_address The target-relative address containing the FDE data to be decoded. This must include
* the length field of the FDE.
* @param debug_frame If true, interpret the DWARF data as a debug_frame section. Otherwise, the
* frame reader will assume eh_frame data.
*/
template <typename machine_ptr>
plcrash_error_t plcrash_async_dwarf_fde_info_init (plcrash_async_dwarf_fde_info_t *info,
plcrash_async_mobject_t *mobj,
const plcrash_async_byteorder_t *byteorder,
pl_vm_address_t fde_address,
bool debug_frame)
{
const pl_vm_address_t sect_addr = plcrash_async_mobject_base_address(mobj);
plcrash_error_t err;
pl_vm_size_t offset = 0;
/* Extract and save the FDE length */
uint8_t dwarf_word_size;
pl_vm_size_t length_size;
{
uint32_t length32;
if (plcrash_async_mobject_read_uint32(mobj, byteorder, fde_address, offset, &length32) != PLCRASH_ESUCCESS) {
PLCF_DEBUG("The current FDE entry 0x%" PRIx64 " header lies outside the mapped range", (uint64_t) fde_address);
return PLCRASH_EINVAL;
}
offset += sizeof(uint32_t);
if (length32 == UINT32_MAX) {
if ((err = plcrash_async_mobject_read_uint64(mobj, byteorder, fde_address, sizeof(uint32_t), &info->fde_length)) != PLCRASH_ESUCCESS) {
PLCF_DEBUG("Failed to read FDE 64-bit length value value; FDE entry lies outside the mapped range");
return err;
}
length_size = sizeof(uint64_t) + sizeof(uint32_t);
offset += sizeof(uint64_t);
dwarf_word_size = 8; // 64-bit DWARF
} else {
info->fde_length = length32;
length_size = sizeof(uint32_t);
dwarf_word_size = 4; // 32-bit DWARF
}
}
/* Save the FDE offset; this is the FDE address, relative to the mobj base address, not including
* the FDE initial length. */
info->fde_offset = (fde_address - sect_addr) + length_size;
/*
* Calculate the the offset to the CIE entry.
*/
pl_vm_address_t cie_target_address;
{
uint64_t raw_offset;
if ((err = plcrash_async_dwarf_read_uintmax64(mobj, byteorder, fde_address, offset, dwarf_word_size, &raw_offset)) != PLCRASH_ESUCCESS) {
PLCF_DEBUG("FDE instruction offset falls outside the mapped range");
return err;
}
offset += dwarf_word_size;
/* In a .debug_frame, the CIE offset is already relative to the start of the section;
* In a .eh_frame, the CIE offset is negative, relative to the current offset of the the FDE. */
if (debug_frame) {
info->cie_offset = (pl_vm_address_t) raw_offset;
/* (Safely) calculate the absolute, task-relative address */
if (raw_offset > PL_VM_OFF_MAX || !plcrash_async_address_apply_offset(sect_addr, (pl_vm_address_t) raw_offset, &cie_target_address)) {
PLCF_DEBUG("CIE offset of 0x%" PRIx64 " overflows representable range of pl_vm_address_t", raw_offset);
return PLCRASH_EINVAL;
}
} else {
/* First, verify that the below subtraction won't overflow */
if (raw_offset > (fde_address+length_size)) {
PLCF_DEBUG("CIE offset 0x%" PRIx64 " would place the CIE value outside of the .eh_frame section", raw_offset);
return PLCRASH_EINVAL;
}
cie_target_address = (fde_address+length_size) - (pl_vm_address_t) raw_offset;
info->cie_offset = cie_target_address - sect_addr;
}
}
/*
* Set up default pointer state. TODO: Mac OS X and iOS do not currently use any relative-based encodings other
* than pcrel. This matches libunwind-35.1, but we should ammend our API to support supplying the remainder of
* the supported base addresses.
*/
gnu_ehptr_reader<machine_ptr> ptr_reader(byteorder);
/* Parse the CIE */
plcrash_async_dwarf_cie_info_t cie;
if ((err = plcrash_async_dwarf_cie_info_init(&cie, mobj, byteorder, &ptr_reader, cie_target_address)) != PLCRASH_ESUCCESS) {
PLCF_DEBUG("Failed to parse CFE for FDE");
return err;
}
/*
* Fetch the address range described by this entry
*/
{
machine_ptr value;
size_t ptr_size;
/* Determine the correct encoding to use. This will either be encoded using the standard plaform
* pointer size (as per DWARF), or using the encoding defined in the augmentation string
* (as per the LSB 4.1.0 eh_frame specification). */
DW_EH_PE_t pc_encoding = DW_EH_PE_absptr;
if (cie.has_eh_augmentation && cie.eh_augmentation.has_pointer_encoding)
pc_encoding = (DW_EH_PE_t) cie.eh_augmentation.pointer_encoding;
/* Fetch the base PC address */
if ((err = ptr_reader.read(mobj, fde_address, offset, pc_encoding, &value, &ptr_size)) != PLCRASH_ESUCCESS) {
PLCF_DEBUG("Failed to read FDE initial_location");
return err;
}
info->pc_start = value;
offset += ptr_size;
/* Fetch the PC length. In DWARF 3&4 specifications, this value is defined to use the standard platform pointer size. The
* LSB 4.1.0 specification does not define the expected format, but a review of GNU's GDB implementation (along with
* other independent implementations), demonstrates that this value uses the FDE pointer encoding with all indirection
* flags cleared. */
machine_ptr pc_length;
if ((err = ptr_reader.read(mobj, fde_address, offset, (DW_EH_PE_t) (pc_encoding & DW_EH_PE_MASK_ENCODING), &pc_length, &ptr_size))) {
PLCF_DEBUG("Failed to read FDE address_length");
return err;
}
if (UINT64_MAX - pc_length < info->pc_start) {
PLCF_DEBUG("FDE address_length + initial_location exceeds UINT64_MAX");
return PLCRASH_EINVAL;
}
info->pc_end = info->pc_start + pc_length;
offset += ptr_size;
}
/* The remainder of the FDE data is comprised of call frame instructions; we calculate the offset to the instructions,
* as well as their length.
*
* This requires validating the lengths/offsets here to prevent overflow/underflow. Most values here have been calculated
* as part of reading the data, and must be valid; hoever, it's possible that the declared FDE length itself short,
* however, which we validate here.
*/
info->instructions_offset = (fde_address+offset) - sect_addr;
if (info->fde_length < (info->instructions_offset - info->fde_offset)) {
PLCF_DEBUG("FDE length of 0x%" PRIu64 "declared to be less than the actual read length of 0x%" PRIu64, (uint64_t) info->fde_length,
(uint64_t)(info->instructions_offset - info->fde_offset));
return PLCRASH_EINVAL;
}
info->instructions_length = (pl_vm_size_t) info->fde_length - (info->instructions_offset - info->fde_offset);
/* Clean up */
plcrash_async_dwarf_cie_info_free(&cie);
return PLCRASH_ESUCCESS;
}
/**
* Return the offset of the FDE instructions, relative to the eh_frame/debug_frame section base.
*
* @param info The FDE info record for which the instruction offset should be returned.
*/
pl_vm_address_t plcrash_async_dwarf_fde_info_instructions_offset (plcrash_async_dwarf_fde_info_t *info) {
return info->instructions_offset;
}
/**
* The length, in bytes, of the FDE instructions referenced by plcrash_async_dwarf_fde_info_instructions_length().
*
* @param info The FDE info record for which the instruction length should be returned.
*/
pl_vm_size_t plcrash_async_dwarf_fde_info_instructions_length (plcrash_async_dwarf_fde_info_t *info) {
return info->instructions_length;
}
/**
* Free all resources associated with @a fde_info.
*
* @param fde_info A previously initialized FDE info instance.
*/
void plcrash_async_dwarf_fde_info_free (plcrash_async_dwarf_fde_info_t *fde_info) {
// noop
}
/* Provide explicit 32/64-bit instantiations */
template
plcrash_error_t plcrash_async_dwarf_fde_info_init<uint32_t> (plcrash_async_dwarf_fde_info_t *info,
plcrash_async_mobject_t *mobj,
const plcrash_async_byteorder_t *byteorder,
pl_vm_address_t fde_address,
bool debug_frame);
template
plcrash_error_t plcrash_async_dwarf_fde_info_init<uint64_t> (plcrash_async_dwarf_fde_info_t *info,
plcrash_async_mobject_t *mobj,
const plcrash_async_byteorder_t *byteorder,
pl_vm_address_t fde_address,
bool debug_frame);
/*
* @}
*/
}
PLCR_CPP_END_NS
#endif /* PLCRASH_FEATURE_UNWIND_DWARF */