plcrash_error_t plcrash_async_cfe_reader_find_pc()

in Source/PLCrashAsyncCompactUnwindEncoding.c [163:367]


plcrash_error_t plcrash_async_cfe_reader_find_pc (plcrash_async_cfe_reader_t *reader, pl_vm_address_t pc, pl_vm_address_t *function_base, uint32_t *encoding) {
    const plcrash_async_byteorder_t *byteorder = reader->byteorder;
    const pl_vm_address_t base_addr = plcrash_async_mobject_base_address(reader->mobj);

    /* Find and map the common encodings table */
    uint32_t common_enc_count = byteorder->swap32(reader->header.commonEncodingsArrayCount);
    uint32_t *common_enc;
    {
        if (VERIFY_SIZE_T(uint32_t, common_enc_count)) {
            PLCF_DEBUG("CFE common encoding count extends beyond the range of size_t");
            return PLCRASH_EINVAL;
        }

        size_t common_enc_len = common_enc_count * sizeof(uint32_t);
        uint32_t common_enc_off = byteorder->swap32(reader->header.commonEncodingsArraySectionOffset);
        common_enc = plcrash_async_mobject_remap_address(reader->mobj, base_addr, common_enc_off, common_enc_len);
        if (common_enc == NULL) {
            PLCF_DEBUG("The declared common table lies outside the mapped CFE range");
            return PLCRASH_EINVAL;
        }
    }

    /* Find and load the first level entry */
    struct unwind_info_section_header_index_entry *first_level_entry = NULL;
    {
        /* Find and map the index */
        uint32_t index_off = byteorder->swap32(reader->header.indexSectionOffset);
        uint32_t index_count = byteorder->swap32(reader->header.indexCount);
        
        if (VERIFY_SIZE_T(sizeof(struct unwind_info_section_header_index_entry), index_count)) {
            PLCF_DEBUG("CFE index count extends beyond the range of size_t");
            return PLCRASH_EINVAL;
        }
        
        if (index_count == 0) {
            PLCF_DEBUG("CFE index contains no entries");
            return PLCRASH_ENOTFOUND;
        }
        
        /*
         * NOTE: CFE includes an extra entry in the total count of second-level pages, ie, from ld64:
         * const uint32_t indexCount = secondLevelPageCount+1;
         *
         * There's no explanation as to why, and tools appear to explicitly ignore the entry entirely. We do the same
         * here.
         */
        PLCF_ASSERT(index_count != 0);
        index_count--;
        
        /* Load the index entries */
        size_t index_len = index_count * sizeof(struct unwind_info_section_header_index_entry);
        struct unwind_info_section_header_index_entry *index_entries = plcrash_async_mobject_remap_address(reader->mobj, base_addr, index_off, index_len);
        if (index_entries == NULL) {
            PLCF_DEBUG("The declared entries table lies outside the mapped CFE range");
            return PLCRASH_EINVAL;
        }
        
        /* Binary search for the first-level entry */
#define CFE_FUN_BINARY_SEARCH_ENTVAL(_tval) (byteorder->swap32(_tval.functionOffset))
        CFE_FUN_BINARY_SEARCH(pc, index_entries, index_count, first_level_entry);
#undef CFE_FUN_BINARY_SEARCH_ENTVAL
        
        if (first_level_entry == NULL) {
            PLCF_DEBUG("Could not find a first level CFE entry for pc=%" PRIx64, (uint64_t) pc);
            return PLCRASH_ENOTFOUND;
        }
    }

    /* Locate and decode the second-level entry */
    uint32_t second_level_offset = byteorder->swap32(first_level_entry->secondLevelPagesSectionOffset);
    uint32_t *second_level_kind = plcrash_async_mobject_remap_address(reader->mobj, base_addr, second_level_offset, sizeof(uint32_t));
    switch (byteorder->swap32(*second_level_kind)) {
        case UNWIND_SECOND_LEVEL_REGULAR: {
            struct unwind_info_regular_second_level_page_header *header;
            header = plcrash_async_mobject_remap_address(reader->mobj, base_addr, second_level_offset, sizeof(*header));
            if (header == NULL) {
                PLCF_DEBUG("The second-level page header lies outside the mapped CFE range");
                return PLCRASH_EINVAL;
            }

            /* Find the entries array */
            uint32_t entries_offset = byteorder->swap16(header->entryPageOffset);
            uint32_t entries_count = byteorder->swap16(header->entryCount);
            
            if (VERIFY_SIZE_T(sizeof(struct unwind_info_regular_second_level_entry), entries_count)) {
                PLCF_DEBUG("CFE second level entry count extends beyond the range of size_t");
                return PLCRASH_EINVAL;
            }
            
            if (!plcrash_async_mobject_verify_local_pointer(reader->mobj, (uintptr_t)header, entries_offset, entries_count * sizeof(struct unwind_info_regular_second_level_entry))) {
                PLCF_DEBUG("CFE entries table lies outside the mapped CFE range");
                return PLCRASH_EINVAL;
            }
            
            /* Binary search for the target entry */
            struct unwind_info_regular_second_level_entry *entries = (struct unwind_info_regular_second_level_entry *) (((uintptr_t)header) + entries_offset);
            struct unwind_info_regular_second_level_entry *entry = NULL;
            
#define CFE_FUN_BINARY_SEARCH_ENTVAL(_tval) (byteorder->swap32(_tval.functionOffset))
            CFE_FUN_BINARY_SEARCH(pc, entries, entries_count, entry);
#undef CFE_FUN_BINARY_SEARCH_ENTVAL
            
            if (entry == NULL) {
                PLCF_DEBUG("Could not find a second level regular CFE entry for pc=%" PRIx64, (uint64_t) pc);
                return PLCRASH_ENOTFOUND;
            }

            *encoding = byteorder->swap32(entry->encoding);
            *function_base = byteorder->swap32(entry->functionOffset);
            return PLCRASH_ESUCCESS;
        }

        case UNWIND_SECOND_LEVEL_COMPRESSED: {
            struct unwind_info_compressed_second_level_page_header *header;
            header = plcrash_async_mobject_remap_address(reader->mobj, base_addr, second_level_offset, sizeof(*header));
            if (header == NULL) {
                PLCF_DEBUG("The second-level page header lies outside the mapped CFE range");
                return PLCRASH_EINVAL;
            }
            
            /* Record the base offset */
            uint32_t base_foffset = byteorder->swap32(first_level_entry->functionOffset);

            /* Find the entries array */
            uint32_t entries_offset = byteorder->swap16(header->entryPageOffset);
            uint32_t entries_count = byteorder->swap16(header->entryCount);

            if (VERIFY_SIZE_T(sizeof(uint32_t), entries_count)) {
                PLCF_DEBUG("CFE second level entry count extends beyond the range of size_t");
                return PLCRASH_EINVAL;
            }
            
            if (!plcrash_async_mobject_verify_local_pointer(reader->mobj, (uintptr_t)header, entries_offset, entries_count * sizeof(uint32_t))) {
                PLCF_DEBUG("CFE entries table lies outside the mapped CFE range");
                return PLCRASH_EINVAL;
            }

            /* Binary search for the target entry */
            uint32_t *compressed_entries = (uint32_t *) (((uintptr_t)header) + entries_offset);
            uint32_t *c_entry_ptr = NULL;

#define CFE_FUN_BINARY_SEARCH_ENTVAL(_tval) (base_foffset + UNWIND_INFO_COMPRESSED_ENTRY_FUNC_OFFSET(byteorder->swap32(_tval)))
            CFE_FUN_BINARY_SEARCH(pc, compressed_entries, entries_count, c_entry_ptr);
#undef CFE_FUN_BINARY_SEARCH_ENTVAL
            
            if (c_entry_ptr == NULL) {
                PLCF_DEBUG("Could not find a second level compressed CFE entry for pc=%" PRIx64, (uint64_t) pc);
                return PLCRASH_ENOTFOUND;
            }

            /* Find the actual encoding */
            uint32_t c_entry = byteorder->swap32(*c_entry_ptr);
            uint8_t c_encoding_idx = UNWIND_INFO_COMPRESSED_ENTRY_ENCODING_INDEX(c_entry);
            
            /* Save the function base */
            *function_base = base_foffset + UNWIND_INFO_COMPRESSED_ENTRY_FUNC_OFFSET(byteorder->swap32(c_entry));
            
            /* Handle common table entries */
            if (c_encoding_idx < common_enc_count) {
                /* Found in the common table. The offset is verified as being within the mapped memory range by
                 * the < common_enc_count check above. */
                *encoding = byteorder->swap32(common_enc[c_encoding_idx]);
                return PLCRASH_ESUCCESS;
            }

            /* Map in the encodings table */
            uint32_t encodings_offset = byteorder->swap16(header->encodingsPageOffset);
            uint32_t encodings_count = byteorder->swap16(header->encodingsCount);
            
            if (VERIFY_SIZE_T(sizeof(uint32_t), encodings_count)) {
                PLCF_DEBUG("CFE second level entry count extends beyond the range of size_t");
                return PLCRASH_EINVAL;
            }

            if (!plcrash_async_mobject_verify_local_pointer(reader->mobj, (uintptr_t)header, encodings_offset, encodings_count * sizeof(uint32_t))) {
                PLCF_DEBUG("CFE compressed encodings table lies outside the mapped CFE range");
                return PLCRASH_EINVAL;
            }

            uint32_t *encodings = (uint32_t *) (((uintptr_t)header) + encodings_offset);

            /* Verify that the entry is within range */
            c_encoding_idx -= common_enc_count;
            if (c_encoding_idx >= encodings_count) {
                PLCF_DEBUG("Encoding index lies outside the second level encoding table");
                return PLCRASH_EINVAL;
            }

            /* Save the results */
            *encoding = byteorder->swap32(encodings[c_encoding_idx]);
            return PLCRASH_ESUCCESS;
        }

        default:
            PLCF_DEBUG("Unsupported second-level CFE table kind: 0x%" PRIx32 " at 0x%" PRIx32, byteorder->swap32(*second_level_kind), second_level_offset);
            return PLCRASH_EINVAL;
    }

    // Unreachable
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunreachable-code"
    __builtin_trap();
    return PLCRASH_ENOTFOUND;
#pragma clang diagnostic pop
}