plcrash_error_t plcrash_async_cfe_entry_init()

in Source/PLCrashAsyncCompactUnwindEncoding.c [674:951]


plcrash_error_t plcrash_async_cfe_entry_init (plcrash_async_cfe_entry_t *entry, cpu_type_t cpu_type, uint32_t encoding) {
    plcrash_error_t ret;
    
    /* Target-neutral initialization */
    entry->cpu_type = cpu_type;
    entry->stack_adjust = 0;
    entry->return_address_register = PLCRASH_REG_INVALID;

    /* Perform target-specific decoding */
    if (cpu_type == CPU_TYPE_X86) {
        uint32_t mode = encoding & UNWIND_X86_MODE_MASK;
        switch (mode) {
            case UNWIND_X86_MODE_EBP_FRAME: {
                entry->type = PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAME_PTR;

                /* Extract the register frame offset */
                entry->stack_offset = -(EXTRACT_BITS(encoding, UNWIND_X86_EBP_FRAME_OFFSET) * sizeof(uint32_t));

                /* Extract the register values. They're stored as a bitfield of of 3 bit values. We support
                 * sparse entries, but terminate the loop if no further entries remain. */
                uint32_t regs = EXTRACT_BITS(encoding, UNWIND_X86_EBP_FRAME_REGISTERS);
                entry->register_count = 0;
                for (uint32_t i = 0; i < PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX; i++) {
                    /* Check for completion */
                    uint32_t remaining = regs >> (3 * i);
                    if (remaining == 0)
                        break;

                    /* Map to the correct PLCrashReporter register name */
                    uint32_t reg = remaining & 0x7;
                    ret = plcrash_async_map_register_name(reg, &entry->register_list[i], cpu_type);
                    if (ret != PLCRASH_ESUCCESS) {
                        PLCF_DEBUG("Failed to map register value of %" PRIx32, reg);
                        return ret;
                    }

                    /* Update the register count */
                    entry->register_count++;
                }
                
                return PLCRASH_ESUCCESS;
            }

            case UNWIND_X86_MODE_STACK_IMMD:
            case UNWIND_X86_MODE_STACK_IND: {
                /* These two types are identical except for the interpretation of the stack offset and adjustment values */
                if (mode == UNWIND_X86_MODE_STACK_IMMD) {
                    entry->type = PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_IMMD;
                    entry->stack_offset = EXTRACT_BITS(encoding, UNWIND_X86_FRAMELESS_STACK_SIZE) * sizeof(uint32_t);
                } else {
                    entry->type = PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_INDIRECT;
                    entry->stack_offset = EXTRACT_BITS(encoding, UNWIND_X86_FRAMELESS_STACK_SIZE);
                    entry->stack_adjust = EXTRACT_BITS(encoding, UNWIND_X86_FRAMELESS_STACK_ADJUST) * sizeof(uint32_t);
                }

                /* Extract the register values */
                entry->register_count = EXTRACT_BITS(encoding, UNWIND_X86_FRAMELESS_STACK_REG_COUNT);
                uint32_t encoded_regs = EXTRACT_BITS(encoding, UNWIND_X86_FRAMELESS_STACK_REG_PERMUTATION);
                uint32_t decoded_regs[PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX];
                
                ret = plcrash_async_cfe_register_decode(encoded_regs, entry->register_count, decoded_regs);
                if (ret != PLCRASH_ESUCCESS) {
                    PLCF_DEBUG("Failed to decode register list: %d", ret);
                    return ret;
                }
                
                /* Map to the correct PLCrashReporter register names */
                for (uint32_t i = 0; i < entry->register_count; i++) {
                    ret = plcrash_async_map_register_name(decoded_regs[i], &entry->register_list[i], cpu_type);
                    if (ret != PLCRASH_ESUCCESS) {
                        PLCF_DEBUG("Failed to map register value of %" PRIx32, entry->register_list[i]);
                        return ret;
                    }
                }

                return PLCRASH_ESUCCESS;
            }

            case UNWIND_X86_MODE_DWARF:
                entry->type = PLCRASH_ASYNC_CFE_ENTRY_TYPE_DWARF;

                /* Extract the register frame offset */
                entry->stack_offset = EXTRACT_BITS(encoding, UNWIND_X86_DWARF_SECTION_OFFSET);
                entry->register_count = 0;

                return PLCRASH_ESUCCESS;

            case 0:
                /* Handle a NULL encoding. This interpretation is derived from Apple's actual implementation; the correct interpretation of
                 * a 0x0 value is not defined in what documentation exists. */
                entry->type = PLCRASH_ASYNC_CFE_ENTRY_TYPE_NONE;
                entry->stack_offset = 0;
                entry->register_count = 0;
                return PLCRASH_ESUCCESS;
                
            default:
                PLCF_DEBUG("Unexpected entry mode of %" PRIx32, mode);
                return PLCRASH_ENOTSUP;
        }
        
        // Unreachable
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunreachable-code"
        __builtin_trap();
        return PLCRASH_EINTERNAL;
#pragma clang diagnostic pop
    } else if (cpu_type == CPU_TYPE_X86_64) {
        uint32_t mode = encoding & UNWIND_X86_64_MODE_MASK;
        switch (mode) {
            case UNWIND_X86_64_MODE_RBP_FRAME: {
                entry->type = PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAME_PTR;
                
                /* Extract the register frame offset */
                entry->stack_offset = -(EXTRACT_BITS(encoding, UNWIND_X86_64_RBP_FRAME_OFFSET) * sizeof(uint64_t));

                /* Extract the register values. They're stored as a bitfield of of 3 bit values. We support
                 * sparse entries, but terminate the loop if no further entries remain. */
                uint32_t regs = EXTRACT_BITS(encoding, UNWIND_X86_64_RBP_FRAME_REGISTERS);
                entry->register_count = 0;
                for (uint32_t i = 0; i < PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX; i++) {
                    /* Check for completion */
                    uint32_t remaining = regs >> (3 * i);
                    if (remaining == 0)
                        break;
                    
                    /* Map to the correct PLCrashReporter register name */
                    uint32_t reg = remaining & 0x7;
                    ret = plcrash_async_map_register_name(reg, &entry->register_list[i], cpu_type);
                    if (ret != PLCRASH_ESUCCESS) {
                        PLCF_DEBUG("Failed to map register value of %" PRIx32, reg);
                        return ret;
                    }
                    
                    /* Update the register count */
                    entry->register_count++;
                }
                
                return PLCRASH_ESUCCESS;
            }
    
            case UNWIND_X86_64_MODE_STACK_IMMD:
            case UNWIND_X86_64_MODE_STACK_IND: {
                /* These two types are identical except for the interpretation of the stack offset and adjustment values */
                if (mode == UNWIND_X86_64_MODE_STACK_IMMD) {
                    entry->type = PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_IMMD;
                    entry->stack_offset = EXTRACT_BITS(encoding, UNWIND_X86_64_FRAMELESS_STACK_SIZE) * sizeof(uint64_t);
                } else {
                    entry->type = PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_INDIRECT;
                    entry->stack_offset = EXTRACT_BITS(encoding, UNWIND_X86_64_FRAMELESS_STACK_SIZE);
                    entry->stack_adjust = EXTRACT_BITS(encoding, UNWIND_X86_64_FRAMELESS_STACK_ADJUST) * sizeof(uint64_t);
                }
                
                /* Extract the register values */
                entry->register_count = EXTRACT_BITS(encoding, UNWIND_X86_64_FRAMELESS_STACK_REG_COUNT);
                uint32_t encoded_regs = EXTRACT_BITS(encoding, UNWIND_X86_64_FRAMELESS_STACK_REG_PERMUTATION);
                uint32_t decoded_regs[PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX];

                ret = plcrash_async_cfe_register_decode(encoded_regs, entry->register_count, decoded_regs);
                if (ret != PLCRASH_ESUCCESS) {
                    PLCF_DEBUG("Failed to decode register list: %d", ret);
                    return ret;
                }

                /* Map to the correct PLCrashReporter register names */
                for (uint32_t i = 0; i < entry->register_count; i++) {
                    ret = plcrash_async_map_register_name(decoded_regs[i], &entry->register_list[i], cpu_type);
                    if (ret != PLCRASH_ESUCCESS) {
                        PLCF_DEBUG("Failed to map register value of %" PRIx32, entry->register_list[i]);
                        return ret;
                    }
                }
                
                return PLCRASH_ESUCCESS;
            }
                
            case UNWIND_X86_64_MODE_DWARF:
                entry->type = PLCRASH_ASYNC_CFE_ENTRY_TYPE_DWARF;
                
                /* Extract the register frame offset */
                entry->stack_offset = EXTRACT_BITS(encoding, UNWIND_X86_64_DWARF_SECTION_OFFSET);
                entry->register_count = 0;
                
                return PLCRASH_ESUCCESS;
            
            case 0:
                /* Handle a NULL encoding. This interpretation is derived from Apple's actual implementation; the correct interpretation of
                 * a 0x0 value is not defined in what documentation exists. */
                entry->type = PLCRASH_ASYNC_CFE_ENTRY_TYPE_NONE;
                entry->stack_offset = 0;
                entry->register_count = 0;
                return PLCRASH_ESUCCESS;
                
                
            default:
                PLCF_DEBUG("Unexpected entry mode of %" PRIx32, mode);
                return PLCRASH_ENOTSUP;
        }
        
        // Unreachable
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunreachable-code"
        __builtin_trap();
        return PLCRASH_EINTERNAL;
#pragma clang diagnostic pop
    } else if (cpu_type == CPU_TYPE_ARM64) {
        uint32_t mode = encoding & UNWIND_ARM64_MODE_MASK;
        switch (mode) {
            case UNWIND_ARM64_MODE_FRAME:
                // Fall through
            case UNWIND_ARM64_MODE_FRAMELESS:
                if (mode == UNWIND_ARM64_MODE_FRAME) {
                    entry->type = PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAME_PTR;
                    /* The stack offset will be calculated below */
                } else {
                    /*
                     * The compact_unwind header documents this as UNWIND_ARM64_MODE_LEAF, but actually defines UNWIND_ARM64_MODE_FRAMELESS.
                     * Reviewing the libunwind stepWithCompactEncodingFrameless() assembly demonstrates that this actually uses the
                     * i386/x86-64 frameless immediate style of encoding an offset from the stack pointer. Unlike x86, however, the
                     * offset is multipled by 16 bytes (since each register is stored in pairs), rather than the platform word size.
                     *
                     * The header discrepancy was reported as rdar://15057141
                     */
                    entry->type = PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_IMMD;
                    entry->stack_offset = EXTRACT_BITS(encoding, UNWIND_ARM64_FRAMELESS_STACK_SIZE_MASK) * (sizeof(uint64_t) * 2);
                    entry->return_address_register = PLCRASH_ARM64_LR;
                }
                
                
                /* Extract the register values */
                size_t reg_pos = 0;
                entry->register_count = 0;
                #define CHECK_REG(name, val1, val2) do { \
                    if ((encoding & name) == name) { \
                        PLCF_ASSERT(entry->register_count+2 <= PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX); \
                        entry->register_list[reg_pos++] = val2; \
                        entry->register_list[reg_pos++] = val1; \
                        entry->register_count += 2; \
                    } \
                } while(0)
                CHECK_REG(UNWIND_ARM64_FRAME_X27_X28_PAIR, PLCRASH_ARM64_X27, PLCRASH_ARM64_X28);
                CHECK_REG(UNWIND_ARM64_FRAME_X25_X26_PAIR, PLCRASH_ARM64_X25, PLCRASH_ARM64_X26);
                CHECK_REG(UNWIND_ARM64_FRAME_X23_X24_PAIR, PLCRASH_ARM64_X23, PLCRASH_ARM64_X24);
                CHECK_REG(UNWIND_ARM64_FRAME_X21_X22_PAIR, PLCRASH_ARM64_X21, PLCRASH_ARM64_X22);
                CHECK_REG(UNWIND_ARM64_FRAME_X19_X20_PAIR, PLCRASH_ARM64_X19, PLCRASH_ARM64_X20);
                #undef CHECK_REG

                /* Offset depends on the number of saved registers */
                if (mode == UNWIND_ARM64_MODE_FRAME)
                    entry->stack_offset = -(entry->register_count * sizeof(uint64_t));
            
                return PLCRASH_ESUCCESS;
                
            case UNWIND_ARM64_MODE_DWARF:
                entry->type = PLCRASH_ASYNC_CFE_ENTRY_TYPE_DWARF;
                
                /* Extract the register frame offset */
                entry->stack_offset = EXTRACT_BITS(encoding, UNWIND_ARM64_DWARF_SECTION_OFFSET);
                entry->register_count = 0;
                return PLCRASH_ESUCCESS;
                
            case 0:
                /* Handle a NULL encoding. This interpretation is derived from Apple's actual implementation; the correct interpretation of
                 * a 0x0 value is not defined in what documentation exists. */
                entry->type = PLCRASH_ASYNC_CFE_ENTRY_TYPE_NONE;
                entry->stack_offset = 0;
                entry->register_count = 0;
                return PLCRASH_ESUCCESS;
                
            default:
                PLCF_DEBUG("Unexpected entry mode of %" PRIx32, mode);
                return PLCRASH_ENOTSUP;
        }

    }

    PLCF_DEBUG("Unsupported CPU type: %" PRIu32, cpu_type);
    return PLCRASH_ENOTSUP;
}