/** * Sanity check list validity. Intended to be used from the unit tests; will fire * an assertion if the list structure is invalid. * * This method acquires no locks and is not thread-safe. It should not be used * when making concurrent changes to the list, or otherwise outside of a test environment. */ inline void assert_list_valid (void) { /* Verify list linkage in both directions. */ node *prev = NULL; for (node *cur = _head; cur != NULL; cur = cur->_next) { PLCF_ASSERT(cur->_prev == prev); prev = cur; } PLCF_ASSERT(prev == _tail); }
/** * Iterate over list nodes. This method is async-safe. If no additional nodes are available, will return NULL. * * The list must be marked for reading before iteration is performed. * * @param current The current list node, or NULL to start iteration. */ template <typename V> typename async_list<V>::node *async_list<V>::next (node *current) { PLCF_ASSERT(_refcount > 0); if (current != NULL) return current->_next; return _head; }
/** * Initialize the @a thread_state using the provided context. * * @param thread_state The thread state to be initialized. * @param uap The context to use for cursor initialization. * * All registers will be marked as available. */ void plcrash_async_thread_state_mcontext_init (plcrash_async_thread_state_t *thread_state, pl_mcontext_t *mctx) { /* * Copy in the thread state. Unlike the mach thread variants, mcontext_t may only represent * the thread state of the host process, and we may assume that the compilation target matches the mcontext_t * thread type. */ #if defined(PLCRASH_ASYNC_THREAD_ARM_SUPPORT) && defined(__LP64__) plcrash_async_thread_state_init(thread_state, CPU_TYPE_ARM64); /* Sanity check. */ PLCF_ASSERT(sizeof(mctx->__ss) == sizeof(thread_state->arm_state.thread.ts_64)); plcrash_async_memcpy(&thread_state->arm_state.thread.ts_64, &mctx->__ss, sizeof(thread_state->arm_state.thread.ts_64)); #elif defined(PLCRASH_ASYNC_THREAD_ARM_SUPPORT) plcrash_async_thread_state_init(thread_state, CPU_TYPE_ARM); /* Sanity check. */ PLCF_ASSERT(sizeof(mctx->__ss) == sizeof(thread_state->arm_state.thread.ts_32)); plcrash_async_memcpy(&thread_state->arm_state.thread.ts_32, &mctx->__ss, sizeof(thread_state->arm_state.thread.ts_32)); #elif defined(PLCRASH_ASYNC_THREAD_X86_SUPPORT) && defined(__LP64__) plcrash_async_thread_state_init(thread_state, CPU_TYPE_X86_64); /* Sanity check. */ PLCF_ASSERT(sizeof(mctx->__ss) == sizeof(thread_state->x86_state.thread.uts.ts64)); PLCF_ASSERT(sizeof(mctx->__es) == sizeof(thread_state->x86_state.exception.ues.es64)); plcrash_async_memcpy(&thread_state->x86_state.thread.uts.ts64, &mctx->__ss, sizeof(thread_state->x86_state.thread.uts.ts64)); plcrash_async_memcpy(&thread_state->x86_state.exception.ues.es64, &mctx->__es, sizeof(thread_state->x86_state.exception.ues.es64)); #elif defined(PLCRASH_ASYNC_THREAD_X86_SUPPORT) plcrash_async_thread_state_init(thread_state, CPU_TYPE_X86); /* Sanity check. */ PLCF_ASSERT(sizeof(mctx->__ss) == sizeof(thread_state->x86_state.thread.uts.ts32)); PLCF_ASSERT(sizeof(mctx->__es) == sizeof(thread_state->x86_state.exception.ues.es32)); plcrash_async_memcpy(&thread_state->x86_state.thread.uts.ts32, &mctx->__ss, sizeof(thread_state->x86_state.thread.uts.ts32)); plcrash_async_memcpy(&thread_state->x86_state.exception.ues.es32, &mctx->__es, sizeof(thread_state->x86_state.exception.ues.es32)); #else #error Add platform support #endif /* Mark all registers as available */ memset(&thread_state->valid_regs, 0xFF, sizeof(thread_state->valid_regs)); }
// Custom new/delete that do not rely on the stdlib void *operator new (size_t size) { void *ptr = malloc(size); PLCF_ASSERT(ptr != NULL); return ptr; };
/** * @internal * Encode a ordered register list using the 10 bit register encoding as defined by the CFE format. * * @param registers The ordered list of registers to encode. These values must correspond to the CFE register values, * <em>not</em> the register values as defined in the PLCrashReporter thread state APIs. * @warning This API is unlikely to be useful outside the CFE encoder implementation, and should not generally be used. * Callers must be careful to pass only literal register values defined in the CFE format (eg, values 1-6). */ uint32_t apigee_plcrash_async_cfe_register_encode (const uint32_t registers[], uint32_t count) { /* * Use a positional encoding to encode each integer in the list as an integer value * that is less than the previous greatest integer in the list. We know that each * integer (numbered 1-6) may appear only once in the list. * * For example: * 6 5 4 3 2 1 -> * 5 4 3 2 1 0 * * 6 3 5 2 1 -> * 5 2 3 1 0 * * 1 2 3 4 5 6 -> * 0 0 0 0 0 0 */ uint32_t renumbered[PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX]; for (int i = 0; i < count; ++i) { unsigned countless = 0; for (int j = 0; j < i; ++j) if (registers[j] < registers[i]) countless++; renumbered[i] = registers[i] - countless - 1; } uint32_t permutation = 0; /* * Using the renumbered list, we map each element of the list (positionally) into a range large enough to represent * the range of any valid element, as well as be subdivided to represent the range of later elements. * * For example, if we use a factor of 120 for the first position (encoding multiples, decoding divides), that * provides us with a range of 0-719. There are 6 possible values that may be encoded in 0-719 (assuming later * division by 120), the range is broken down as: * * 0 - 119: 0 * 120 - 239: 1 * 240 - 359: 2 * 360 - 479: 3 * 480 - 599: 4 * 600 - 719: 5 * * Within that range, further positions may be encoded. Assuming a value of 1 in position 0, and a factor of * 24 for position 1, the range breakdown would be as follows: * 120 - 143: 0 * 144 - 167: 1 * 168 - 191: 2 * 192 - 215: 3 * 216 - 239: 4 * * Note that due to the positional renumbering performed prior to this step, we know that each subsequent position * in the list requires fewer elements; eg, position 0 may include 0-5, position 1 0-4, and position 2 0-3. This * allows us to allocate smaller overall ranges to represent all possible elements. */ PLCF_ASSERT(PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX == 6); switch (count) { case 1: permutation |= renumbered[0]; break; case 2: permutation |= (5*renumbered[0] + renumbered[1]); break; case 3: permutation |= (20*renumbered[0] + 4*renumbered[1] + renumbered[2]); break; case 4: permutation |= (60*renumbered[0] + 12*renumbered[1] + 3*renumbered[2] + renumbered[3]); break; case 5: permutation |= (120*renumbered[0] + 24*renumbered[1] + 6*renumbered[2] + 2*renumbered[3] + renumbered[4]); break; case 6: /* * There are 6 elements in the list, 6 possible values for each element, and values may not repeat. The * value of the last element can be derived from the values previously seen (and due to the positional * renumbering performed above, the value of the last element will *always* be 0. */ permutation |= (120*renumbered[0] + 24*renumbered[1] + 6*renumbered[2] + 2*renumbered[3] + renumbered[4]); break; } PLCF_ASSERT((permutation & 0x3FF) == permutation); return permutation; }
/** * @internal * Decode a ordered register list from the 10 bit register encoding as defined by the CFE format. * * @param permutation The 10-bit encoded register list. * @param count The number of registers to decode from @a permutation. * @param registers On return, the ordered list of decoded register values. These values must correspond to the CFE * register values, <em>not</em> the register values as defined in the PLCrashReporter thread state APIs. * * @warning This API is unlikely to be useful outside the CFE encoder implementation, and should not generally be used. * Callers must be careful to pass only literal register values defined in the CFE format (eg, values 1-6). */ void apigee_plcrash_async_cfe_register_decode (uint32_t permutation, uint32_t count, uint32_t registers[]) { PLCF_ASSERT(count <= PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX); /* * Each register is encoded by mapping the values to a 10-bit range, and then further sub-ranges within that range, * with a subrange allocated to each position. See the encoding function for full documentation. */ int permunreg[PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX]; #define PERMUTE(pos, factor) do { \ permunreg[pos] = permutation/factor; \ permutation -= (permunreg[pos]*factor); \ } while (0) PLCF_ASSERT(PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX == 6); switch (count) { case 6: PERMUTE(0, 120); PERMUTE(1, 24); PERMUTE(2, 6); PERMUTE(3, 2); PERMUTE(4, 1); /* * There are 6 elements in the list, 6 possible values for each element, and values may not repeat. The * value of the last element can be derived from the values previously seen (and due to the positional * renumbering performed, the value of the last element will *always* be 0). */ permunreg[5] = 0; break; case 5: PERMUTE(0, 120); PERMUTE(1, 24); PERMUTE(2, 6); PERMUTE(3, 2); PERMUTE(4, 1); break; case 4: PERMUTE(0, 60); PERMUTE(1, 12); PERMUTE(2, 3); PERMUTE(3, 1); break; case 3: PERMUTE(0, 20); PERMUTE(1, 4); PERMUTE(2, 1); break; case 2: PERMUTE(0, 5); PERMUTE(1, 1); break; case 1: PERMUTE(0, 1); break; } #undef PERMUTE /* Recompute the actual register values based on the position-relative values. */ bool position_used[PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX+1] = { 0 }; for (uint32_t i = 0; i < count; ++i) { int renumbered = 0; for (int u = 1; u < 7; u++) { if (!position_used[u]) { if (renumbered == permunreg[i]) { registers[i] = u; position_used[u] = true; break; } renumbered++; } } } }
/** * Rethrn the otal length of the opcode stream, in bytes, for this rule. This method is only applicable to and may only be called on * DWARF_CFA_STATE_CFA_TYPE_EXPRESSION rules. */ pl_vm_size_t expression_length (void) { PLCF_ASSERT(_cfa_type == DWARF_CFA_STATE_CFA_TYPE_EXPRESSION); return _cfa_data.expression.length; }
/** * Return the compact frame encoding entry for @a pc via @a encoding, if available. * * @param reader The initialized CFE reader which will be searched for the entry. * @param pc The PC value to search for within the CFE data. Note that this value must be relative to * the target Mach-O image's __TEXT vmaddr. * @param function_base On success, will be populated with the base address of the function. This value is relative to * the image's load address, rather than the in-memory address of the loaded image. * @param encoding On success, will be populated with the compact frame encoding entry. * * @return Returns PLFRAME_ESUCCCESS on success, or one of the remaining error codes if a CFE parsing error occurs. If * the entry can not be found, PLFRAME_ENOTFOUND will be returned. */ apigee_plcrash_error_t apigee_plcrash_async_cfe_reader_find_pc (apigee_plcrash_async_cfe_reader_t *reader, pl_vm_address_t pc, pl_vm_address_t *function_base, uint32_t *encoding) { const apigee_plcrash_async_byteorder_t *byteorder = reader->byteorder; const pl_vm_address_t base_addr = apigee_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 APIGEE_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 = apigee_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 APIGEE_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 APIGEE_PLCRASH_EINVAL; } if (index_count == 0) { PLCF_DEBUG("CFE index contains no entries"); return APIGEE_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 = apigee_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 APIGEE_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 APIGEE_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 = apigee_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 = apigee_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 APIGEE_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 APIGEE_PLCRASH_EINVAL; } if (!apigee_plcrash_async_mobject_verify_local_pointer(reader->mobj, 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 APIGEE_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 APIGEE_PLCRASH_ENOTFOUND; } *encoding = byteorder->swap32(entry->encoding); *function_base = byteorder->swap32(entry->functionOffset); return APIGEE_PLCRASH_ESUCCESS; } case UNWIND_SECOND_LEVEL_COMPRESSED: { struct unwind_info_compressed_second_level_page_header *header; header = apigee_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 APIGEE_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 APIGEE_PLCRASH_EINVAL; } if (!apigee_plcrash_async_mobject_verify_local_pointer(reader->mobj, header, entries_offset, entries_count * sizeof(uint32_t))) { PLCF_DEBUG("CFE entries table lies outside the mapped CFE range"); return APIGEE_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 APIGEE_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 APIGEE_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 APIGEE_PLCRASH_EINVAL; } if (!apigee_plcrash_async_mobject_verify_local_pointer(reader->mobj, header, encodings_offset, encodings_count * sizeof(uint32_t))) { PLCF_DEBUG("CFE compressed encodings table lies outside the mapped CFE range"); return APIGEE_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 APIGEE_PLCRASH_EINVAL; } /* Save the results */ *encoding = byteorder->swap32(encodings[c_encoding_idx]); return APIGEE_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 APIGEE_PLCRASH_EINVAL; } // Unreachable __builtin_trap(); return APIGEE_PLCRASH_ENOTFOUND; }
/** * Return the target-relative absolute address of the expression opcode stream for this rule. This method is only applicable to and may only be called on * DWARF_CFA_STATE_CFA_TYPE_EXPRESSION rules. */ pl_vm_address_t expression_address (void) { PLCF_ASSERT(_cfa_type == DWARF_CFA_STATE_CFA_TYPE_EXPRESSION); return _cfa_data.expression.address; }
/** * Return the signed register offset for this rule. This method is only applicable to and may only be called on * DWARF_CFA_STATE_CFA_TYPE_REGISTER_SIGNED rules. */ machine_ptr_s register_offset_signed (void) { PLCF_ASSERT(_cfa_type == DWARF_CFA_STATE_CFA_TYPE_REGISTER_SIGNED); return _cfa_data.reg.offset.s_off; }
/** * Return the unsigned register offset for this rule. This method is only applicable to and may only be called on * DWARF_CFA_STATE_CFA_TYPE_REGISTER rules. */ machine_ptr register_offset (void) { PLCF_ASSERT(_cfa_type == DWARF_CFA_STATE_CFA_TYPE_REGISTER); return _cfa_data.reg.offset.u_off; }
/** * Return the register number for this rule. This method is only applicable to and may only be called on * DWARF_CFA_STATE_CFA_TYPE_REGISTER and DWARF_CFA_STATE_CFA_TYPE_REGISTER_SIGNED rules. */ dwarf_cfa_state_regnum_t register_number (void) { PLCF_ASSERT(_cfa_type == DWARF_CFA_STATE_CFA_TYPE_REGISTER || _cfa_type == DWARF_CFA_STATE_CFA_TYPE_REGISTER_SIGNED); return _cfa_data.reg.regnum; }
/** Destroy the referenced value. */ ~ReferenceValue() { PLCF_ASSERT(refs == 0); }