plcrash_error_t dwarf_frame_reader::find_fde()

in Source/PLCrashAsyncDwarfEncoding.cpp [85:208]


plcrash_error_t dwarf_frame_reader::find_fde (pl_vm_off_t offset,
                                              pl_vm_address_t pc,
                                              plcrash_async_dwarf_fde_info_t *fde_info)
{
    const plcrash_async_byteorder_t *byteorder = _byteorder;
    const pl_vm_address_t base_addr = plcrash_async_mobject_base_address(_mobj);
    const pl_vm_address_t end_addr = base_addr + plcrash_async_mobject_length(_mobj);
    
    plcrash_error_t err;
    
    /* Apply the FDE offset */
    pl_vm_address_t cfi_entry = base_addr;
    if (!plcrash_async_address_apply_offset(base_addr, offset, &cfi_entry)) {
        PLCF_DEBUG("FDE offset hint overflows the mobject's base address");
        return PLCRASH_EINVAL;
    }
    
    if (cfi_entry >= end_addr) {
        PLCF_DEBUG("FDE base address + offset falls outside the mapped range");
        return PLCRASH_EINVAL;
    }
    
    /* Iterate over table entries */
    while (cfi_entry < end_addr) {
        /* Fetch the entry length (and determine wether it's 64-bit or 32-bit) */
        uint64_t length;
        pl_vm_size_t length_size;
        uint8_t dwarf_word_size;
        
        {
            uint32_t *length32 = (uint32_t *) plcrash_async_mobject_remap_address(_mobj, cfi_entry, 0x0, sizeof(uint32_t));
            if (length32 == NULL) {
                PLCF_DEBUG("The current CFI entry 0x%" PRIx64 " header lies outside the mapped range", (uint64_t) cfi_entry);
                return PLCRASH_EINVAL;
            }
            
            if (byteorder->swap32(*length32) == UINT32_MAX) {
                uint64_t *length64 = (uint64_t *) plcrash_async_mobject_remap_address(_mobj, cfi_entry, sizeof(uint32_t), sizeof(uint64_t));
                if (length64 == NULL) {
                    PLCF_DEBUG("The current CFI entry 0x%" PRIx64 " header lies outside the mapped range", (uint64_t) cfi_entry);
                    return PLCRASH_EINVAL;
                }
                
                length = byteorder->swap64(*length64);
                length_size = sizeof(uint64_t) + sizeof(uint32_t);
                dwarf_word_size = 8; // 64-bit DWARF
            } else {
                length = byteorder->swap32(*length32);
                length_size = sizeof(uint32_t);
                dwarf_word_size = 4; // 32-bit DWARF
            }
        }
        
        /*
         * APPLE EXTENSION
         * Check for end marker, as per Apple's libunwind-35.1. It's unclear if this is defined by the DWARF 3 or 4 specifications; I could not
         * find a reference to it.
         
         * Section 7.2.2 defines 0xfffffff0 - 0xffffffff as being reserved for extensions to the length
         * field relative to the DWARF 2 standard. There is no explicit reference to the use of an 0 value.
         *
         * In section 7.2.1, the value of 0 is defined as being reserved as an error value in the encodings for
         * "attribute names, attribute forms, base type encodings, location operations, languages, line number program
         * opcodes, macro information entries and tag names to represent an error condition or unknown value."
         *
         * Section 7.2.2 doesn't justify the usage of 0x0 as a termination marker, but given that Apple's code relies on it,
         * we will also do so here.
         */
        if (length == 0x0)
            return PLCRASH_ENOTFOUND;
        
        /* Calculate the next entry address; the length_size addition is known-safe, as we were able to successfully read the length from *cfi_entry */
        pl_vm_address_t next_cfi_entry;
        if (!plcrash_async_address_apply_offset(cfi_entry+length_size, (pl_vm_off_t) length, &next_cfi_entry)) {
            PLCF_DEBUG("Entry length size overflows the CFI address");
            return PLCRASH_EINVAL;
        }
        
        /* Fetch the entry id */
        uint64_t cie_id;
        
        if ((err = plcrash_async_dwarf_read_uintmax64(_mobj, byteorder, cfi_entry, length_size, dwarf_word_size, &cie_id)) != PLCRASH_ESUCCESS) {
            PLCF_DEBUG("The current CFI entry 0x%" PRIx64 " cie_id lies outside the mapped range", (uint64_t) cfi_entry);
            return PLCRASH_EINVAL;
        }
        
        /* Check for (and skip) CIE entries. */
        {
            bool is_cie = false;
            
            /* debug_frame uses UINT?_MAX to denote CIE entries. */
            if (_debug_frame && ((dwarf_word_size == 8 && cie_id == UINT64_MAX) || (dwarf_word_size == 4 && cie_id == UINT32_MAX)))
                is_cie = true;
            
            /* eh_frame uses a type of 0x0 to denote CIE entries. */
            if (!_debug_frame && cie_id == 0x0)
                is_cie = true;
            
            /* If not a FDE, skip */
            if (is_cie) {
                /* Not a FDE -- skip */
                cfi_entry = next_cfi_entry;
                continue;
            }
        }
        
        /* Decode the FDE */
        if (_m64)
            err = plcrash_async_dwarf_fde_info_init<uint64_t>(fde_info, _mobj, byteorder, cfi_entry, _debug_frame);
        else
            err = plcrash_async_dwarf_fde_info_init<uint32_t>(fde_info, _mobj, byteorder, cfi_entry, _debug_frame);
        if (err != PLCRASH_ESUCCESS)
            return err;
        
        /* Check if our PC is within range */
        if (pc >= fde_info->pc_start && pc < fde_info->pc_end)
            return PLCRASH_ESUCCESS;
        
        /* Skip to the next entry */
        cfi_entry = next_cfi_entry;
    }
    
    return PLCRASH_ENOTFOUND;
}