in Source/PLCrashAsyncDwarfFDE.cpp [60:216]
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;
}