text: /*
   * Author: Landon Fuller <landonf@plausiblelabs.com>
   * Author: Gwynne Raskind <gwynne@darkrainfall.org>
   *
   * Copyright (c) 2013 Plausible Labs Cooperative, Inc.
   * All rights reserved.
   *
   * Permission is hereby granted, free of charge, to any person
   * obtaining a copy of this software and associated documentation
   * files (the "Software"), to deal in the Software without
   * restriction, including without limitation the rights to use,
   * copy, modify, merge, publish, distribute, sublicense, and/or sell
   * copies of the Software, and to permit persons to whom the
   * Software is furnished to do so, subject to the following
   * conditions:
   *
   * The above copyright notice and this permission notice shall be
   * included in all copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
   * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
   * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
   * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
   * OTHER DEALINGS IN THE SOFTWARE.
   */
  
  #include "PLCrashAsyncCompactUnwindEncoding.h"
  
  #include "PLCrashFeatureConfig.h"
  #include "PLCrashCompatConstants.h"
  
  #include <inttypes.h>
  
  #if PLCRASH_FEATURE_UNWIND_COMPACT
  
  /**
   * @internal
   * @ingroup plcrash_async
   * @defgroup plcrash_async_cfe Compact Frame Encoding
   *
   * Implements async-safe parsing of compact frame unwind encodings.
   * @{
   */
  
  /* Extract @a mask bits from @a value. */
  #define EXTRACT_BITS(value, mask) ((value >> __builtin_ctz(mask)) & (((1 << __builtin_popcount(mask)))-1))
  
  #pragma mark CFE Reader
  
  /**
   * Initialize a new CFE reader using the provided memory object. Any resources held by a successfully initialized
   * instance must be freed via plcrash_async_cfe_reader_free();
   *
   * @param reader The reader instance to initialize.
   * @param mobj The memory object containing CFE data at the start address. This instance must survive for the lifetime
   * of the reader.
   * @param cputype The target architecture of the CFE data, encoded as a Mach-O CPU type. Interpreting CFE data is
   * architecture-specific, and Apple has not defined encodings for all supported architectures.
   */
  plcrash_error_t plcrash_async_cfe_reader_init (plcrash_async_cfe_reader_t *reader, plcrash_async_mobject_t *mobj, cpu_type_t cputype) {
      reader->mobj = mobj;
      reader->cpu_type = cputype;
  
      /* Determine the expected encoding */
      switch (cputype) {
          case CPU_TYPE_X86:
          case CPU_TYPE_X86_64:
          case CPU_TYPE_ARM64:
              reader->byteorder = plcrash_async_byteorder_little_endian();
              break;
  
          default:
              PLCF_DEBUG("Unsupported CPU type: %" PRIu32, cputype);
              return PLCRASH_ENOTSUP;
      }
  
      /* Fetch and verify the header */
      pl_vm_address_t base_addr = plcrash_async_mobject_base_address(mobj);
      struct unwind_info_section_header *header = plcrash_async_mobject_remap_address(mobj, base_addr, 0, sizeof(*header));
      if (header == NULL) {
          PLCF_DEBUG("Could not map the unwind info section header");
          return PLCRASH_EINVAL;
      }
  
      /* Verify the format version */
      uint32_t version = reader->byteorder->swap32(header->version);
      if (version != 1) {
          PLCF_DEBUG("Unsupported CFE version: %" PRIu32, version);
          return PLCRASH_ENOTSUP;
      }
  
      reader->header = *header;
      return PLCRASH_ESUCCESS;
  }
  
  /**
   * @internal
   *
   * Binary search in macro form. Pass the table, count, and result
   * pointers.
   *
   * CFE_FUN_BINARY_SEARCH_ENTVAL must also be defined, and it must
   * return the integer value to be compared.
   */
  #define CFE_FUN_BINARY_SEARCH(_pc, _table, _count, _result) do { \
      uint32_t min = 0; \
      uint32_t mid = 0; \
      uint32_t max = _count - 1; \
  \
      /* Search while _table[min:max] is not empty */ \
      while (max >= min) { \
          /* Calculate midpoint */ \
          mid = (min + max) / 2; \
  \
          /* Determine which half of the array to search */ \
          uint32_t mid_fun_offset = CFE_FUN_BINARY_SEARCH_ENTVAL(_table[mid]); \
          if (mid_fun_offset < _pc) { \
              /* Check for inclusive equality */ \
              if (mid == max || CFE_FUN_BINARY_SEARCH_ENTVAL(_table[mid+1]) > _pc) { \
                  _result = &_table[mid]; \
                  break; \
              } \
  \
              /* Base our search on the upper array */ \
              min = mid + 1; \
          } else if (mid_fun_offset > _pc) { \
              /* Check for range exclusion; if we hit 0, then the range starts after our PC. */ \
              if (mid == 0) { \
                  break; \
              } \
              \
              /* Base our search on the lower array */ \
              max = mid - 1; \
          } else if (mid_fun_offset == _pc) { \
              /* Direct match found */ \
              _result = &_table[mid]; \
              break; \
          } \
      } \
  } while (0)
  
  /* Evaluates to true if the length of @a _ecount * @a sizof(_etype) can not be represented
   * by size_t. */
  #define VERIFY_SIZE_T(_etype, _ecount) (SIZE_MAX / sizeof(_etype) < (size_t) _ecount)
  
  /**
   * 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.
   */
  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, 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, 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, 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
      __builtin_trap();
      return PLCRASH_ENOTFOUND;
  }
  
  /**
   * Free all resources associated with @a reader.
   */
  void plcrash_async_cfe_reader_free (plcrash_async_cfe_reader_t *reader) {
      // noop
  }
  
  #pragma mark CFE Entry
  
  
  /**
   * @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.
   * @param count The number of registers in @a registers. This must not exceed the maximum number of registers supported by the
   * permutation encoding (PLCRASH_ASYNC_CFE_PERMUTATION_REGISTER_MAX).
   *
   * @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 plcrash_async_cfe_register_encode (const uint32_t registers[], uint32_t count) {
      /* Supplied count must be within supported range */
      PLCF_ASSERT(count <= PLCRASH_ASYNC_CFE_PERMUTATION_REGISTER_MAX);
  
      /*
       * 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_PERMUTATION_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.
       */
      
      /* Assert that the maximum register count matches our switch() statement. */
      PLCF_ASSERT_STATIC(expected_max_register_count, PLCRASH_ASYNC_CFE_PERMUTATION_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.
   *
   * @return Returns PLCRASH_ESUCCESS on success, or an appropriate error on failure. This function may fail if @a count
   * exceeds the total number of register values supported by the permutation encoding; this should only occur in the
   * case that the register count supplied from the binary is invalid.
   *
   * @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).
   */
  plcrash_error_t plcrash_async_cfe_register_decode (uint32_t permutation, uint32_t count, uint32_t registers[]) {
      /* Validate that count falls within the supported range */
      if (count > PLCRASH_ASYNC_CFE_PERMUTATION_REGISTER_MAX) {
          PLCF_DEBUG("Register permutation decoding attempted with an unsupported count of %" PRIu32, count);
          return PLCRASH_EINVAL;
      }
  
      /*
       * 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_PERMUTATION_REGISTER_MAX];
  #define PERMUTE(pos, factor) do { \
  permunreg[pos] = permutation/factor; \
  permutation -= (permunreg[pos]*factor); \
  } while (0)
  
      /* Assert that the maximum register count matches our switch() statement. */
      PLCF_ASSERT_STATIC(expected_max_register_count, PLCRASH_ASYNC_CFE_PERMUTATION_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++;
  			}
  		}
  	}
      
      return PLCRASH_ESUCCESS;
  }
  
  /**
   * @internal
   *
   * Map the @a orig_reg CFE register name to a PLCrashReporter PLCRASH_* register name constant.
   *
   * @param orig_reg Register name as decoded from a CFE entry.
   * @param cpu_type The CPU type that should be used when interpreting @a orig_reg;
   */
  static plcrash_error_t plcrash_async_map_register_name (uint32_t orig_reg, plcrash_regnum_t *result, cpu_type_t cpu_type) {
      if (cpu_type == CPU_TYPE_X86) {
          switch (orig_reg) {
              case UNWIND_X86_REG_NONE:
                  *result = PLCRASH_REG_INVALID;
                  return PLCRASH_ESUCCESS;
      
              case UNWIND_X86_REG_EBX:
                  *result = PLCRASH_X86_EBX;
                  return PLCRASH_ESUCCESS;
  
              case UNWIND_X86_REG_ECX:
                  *result = PLCRASH_X86_ECX;
                  return PLCRASH_ESUCCESS;
                  
              case UNWIND_X86_REG_EDX:
                  *result = PLCRASH_X86_EDX;
                  return PLCRASH_ESUCCESS;
                  
              case UNWIND_X86_REG_EDI:
                  *result = PLCRASH_X86_EDI;
                  return PLCRASH_ESUCCESS;
                  
              case UNWIND_X86_REG_ESI:
                  *result = PLCRASH_X86_ESI;
                  return PLCRASH_ESUCCESS;
                  
              case UNWIND_X86_REG_EBP:
                  *result = PLCRASH_X86_EBP;
                  return PLCRASH_ESUCCESS;
              default:
                  PLCF_DEBUG("Requested register mapping for unknown register %" PRId32, orig_reg);
                  return PLCRASH_EINVAL;
          }
      } else if (cpu_type == CPU_TYPE_X86_64) {
          switch (orig_reg) {
              case UNWIND_X86_64_REG_NONE:
                  *result = PLCRASH_REG_INVALID;
                  return PLCRASH_ESUCCESS;
      
              case UNWIND_X86_64_REG_RBX:
                  *result = PLCRASH_X86_64_RBX;
                  return PLCRASH_ESUCCESS;
              case UNWIND_X86_64_REG_R12:
                  *result = PLCRASH_X86_64_R12;
                  return PLCRASH_ESUCCESS;
              case UNWIND_X86_64_REG_R13:
                  *result = PLCRASH_X86_64_R13;
                  return PLCRASH_ESUCCESS;
              case UNWIND_X86_64_REG_R14:
                  *result = PLCRASH_X86_64_R14;
                  return PLCRASH_ESUCCESS;
              case UNWIND_X86_64_REG_R15:
                  *result = PLCRASH_X86_64_R15;
                  return PLCRASH_ESUCCESS;
              case UNWIND_X86_64_REG_RBP:
                  *result = PLCRASH_X86_64_RBP;
                  return PLCRASH_ESUCCESS;
              default:
                  PLCF_DEBUG("Requested register mapping for unknown register %" PRId32, orig_reg);
                  return PLCRASH_EINVAL;
          }
      } else {
          PLCF_DEBUG("Requested register mapping for unknown cpu type %" PRIu32, cpu_type);
          return PLCRASH_ENOTSUP;
      }
  }
  
  /**
   * Initialize a new decoded CFE entry using the provided encoded CFE data. Any resources held by a successfully
   * initialized instance must be freed via plcrash_async_cfe_entry_free();
   *
   * @param entry The entry instance to initialize.
   * @param cpu_type The target architecture of the CFE data, encoded as a Mach-O CPU type. Interpreting CFE data is
   * architecture-specific, and Apple has not defined encodings for all supported architectures.
   * @param encoding The CFE entry data, in the hosts' native byte order.
   *
   * @internal
   * This code supports sparse register lists for the EBP_FRAME and RBP_FRAME modes. It's unclear as to whether these
   * actually ever occur in the wild, but they are supported by Apple's unwinddump tool.
   */
  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
          __builtin_trap();
          return PLCRASH_EINTERNAL;
  
      } 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
          __builtin_trap();
          return PLCRASH_EINTERNAL;
      } 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;
  }
  
  /**
   * Return the CFE entry type.
   *
   * @param entry The entry for which the type should be returned.
   */
  plcrash_async_cfe_entry_type_t plcrash_async_cfe_entry_type (plcrash_async_cfe_entry_t *entry) {
      return entry->type;
  }
  
  /**
   * Return the stack offset value. Interpretation of this value depends on the CFE type:
   * - PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAME_PTR: Unused.
   * - PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_IMMD: The return address may be found at ± offset from the stack
   *   pointer (eg, esp/rsp), and is followed all non-volatile registers that need to be restored.
   * - PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_INDIRECT: The actual offset may be loaded from the target function's
   *   instruction prologue. The offset given here must be added to the start address of the function to determine
   *   the location of the actual stack size as encoded in the prologue.
   *
   *   The return address may be found at ± offset from the stack pointer (eg, esp/rsp), and is followed all
   *   non-volatile registers that need to be restored.
   *
   *   TODO: Need a mechanism to define the actual size of the offset. For x86-32/x86-64, it is defined as being
   *   encoded in a subl instruction.
   * - PLCRASH_ASYNC_CFE_ENTRY_TYPE_DWARF: Unused.
   *
   * @param entry The entry from which the stack offset value will be fetched.
   */
  intptr_t plcrash_async_cfe_entry_stack_offset (plcrash_async_cfe_entry_t *entry) {
      return entry->stack_offset;
  }
  
  /**
   * Return the stack adjustment value. This is an offset to be applied to the final stack value read via
   * PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_INDIRECT.
   *
   * This value is unused for all other CFE types.
   */
  uint32_t plcrash_async_cfe_entry_stack_adjustment (plcrash_async_cfe_entry_t *entry) {
      return entry->stack_adjust;
  }
  
  /**
   * The register to be used for the return address (eg, such as in a ARM leaf frame, where the return address may be found in lr),
   * or PLCRASH_REG_INVALID if the return address is found on the stack. This value is only supported for the following CFE types:
   *
   * - PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_IMMD and
   * - PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_INDIRECT
   */
  plcrash_regnum_t plcrash_async_cfe_entry_return_address_register (plcrash_async_cfe_entry_t *entry) {
      PLCF_ASSERT(entry->return_address_register == PLCRASH_REG_INVALID || (entry->type == PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_IMMD || PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_INDIRECT));
      return entry->return_address_register;
  }
  
  /**
   * The number of non-volatile registers that need to be restored from the stack.
   */
  uint32_t plcrash_async_cfe_entry_register_count (plcrash_async_cfe_entry_t *entry) {
      return entry->register_count;
  }
  
  /**
   * Copy the ordered list of non-volatile registers that must be restored from the stack to @a register_list. These
   * values are specific to the target platform, and are defined in the @a plcrash_async_thread API.
   * @sa plcrash_x86_regnum_t and @sa plcrash_x86_64_regnum_t.
   *
   * Note that the list may be sparse; some entries may be set to a value of PLCRASH_REG_INVALID.
   *
   * @param entry The entry from which the register list should be copied.
   * @param register_list An array to which the registers will be copied. plcrash_async_cfe_register_count() may be used
   * to determine the number of registers to be copied.
   */
  void plcrash_async_cfe_entry_register_list (plcrash_async_cfe_entry_t *entry, plcrash_regnum_t *register_list) {
      memcpy(register_list, entry->register_list, sizeof(entry->register_list[0]) * entry->register_count);
  }
  
  /**
   * Apply the decoded @a entry to @a thread_state, fetching data from @a task, populating @a new_thread_state
   * with the result.
   *
   * @param task The task containing any data referenced by @a thread_state.
   * @param function_address The task-relative in-memory address of the function containing @a entry. This may be computed
   * by adding the function_base returned by plcrash_async_cfe_reader_find_pc() to the base address of the loaded image.
   * @param thread_state The current thread state corresponding to @a entry.
   * @param entry A CFE unwind entry.
   * @param new_thread_state The new thread state to be initialized.
   *
   * @return Returns PLCRASH_ESUCCESS on success, or a standard plcrash_error_t code if an error occurs.
   *
   * @todo This implementation assumes downwards stack growth.
   */
  plcrash_error_t plcrash_async_cfe_entry_apply (task_t task,
                                                 pl_vm_address_t function_address,
                                                 const plcrash_async_thread_state_t *thread_state,
                                                 plcrash_async_cfe_entry_t *entry,
                                                 plcrash_async_thread_state_t *new_thread_state)
  {
      /* Set up register load target */
      size_t greg_size = plcrash_async_thread_state_get_greg_size(thread_state);
      bool x64 = (greg_size == sizeof(uint64_t));
      void *dest;
      union {
          /* Room for (frame pointer, return address) + saved registers */
          uint64_t greg64[PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX];
          uint32_t greg32[PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX];
      } regs;
  
      if (x64)
          dest = regs.greg64;
      else
          dest = regs.greg32;
      
      /* Sanity check: We'll use this buffer for popping the fp and pc, as well as restoring the saved registers. */
      PLCF_ASSERT(PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX >= 2);
  
      /* Initialize the new thread state */
      *new_thread_state = *thread_state;
      plcrash_async_thread_state_clear_volatile_regs(new_thread_state);
  
      pl_vm_address_t saved_reg_addr = 0x0;
      plcrash_async_cfe_entry_type_t entry_type = plcrash_async_cfe_entry_type(entry);
      switch (entry_type) {
          case PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAME_PTR: {
              plcrash_error_t err;
  
              /* Fetch the current frame pointer */
              if (!plcrash_async_thread_state_has_reg(thread_state, PLCRASH_REG_FP)) {
                  PLCF_DEBUG("Can't apply FRAME_PTR unwind type without a valid frame pointer");
                  return PLCRASH_ENOTFOUND;
              }
  
              plcrash_greg_t fp = plcrash_async_thread_state_get_reg(thread_state, PLCRASH_REG_FP);
              
              /* Address of saved registers */
              saved_reg_addr = fp + entry->stack_offset;
              
              /* Restore the previous frame's stack pointer from the saved frame pointer. This is
               * the FP + saved FP + return address. */
              pl_vm_address_t new_sp;
              if (!plcrash_async_address_apply_offset(fp, greg_size * 2, &new_sp)) {
                  PLCF_DEBUG("Current frame pointer falls outside of addressable bounds");
                  return PLCRASH_EINVAL;
              }
      
              plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_SP, new_sp);
  
              /* Read the saved fp and retaddr */
              err = plcrash_async_task_memcpy(task, (pl_vm_address_t) fp, 0, dest, greg_size * 2);
              if (err != PLCRASH_ESUCCESS) {
                  PLCF_DEBUG("Failed to read frame data at address 0x%" PRIx64 ": %d", (uint64_t) fp, err);
                  return err;
              }
  
              // XXX: This assumes downward stack growth.
              if (x64) {
                  plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_FP, regs.greg64[0]);
                  plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_IP, regs.greg64[1]);
              } else {
                  plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_FP, regs.greg32[0]);
                  plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_IP, regs.greg32[1]);
              }
              break;
          }
              
          case PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_INDIRECT:
              // Fallthrough
              
          case PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_IMMD: {
              plcrash_error_t err;
  
              /* Fetch the current stack pointer */
              if (!plcrash_async_thread_state_has_reg(thread_state, PLCRASH_REG_SP)) {
                  PLCF_DEBUG("Can't apply FRAME_IMMD unwind type without a valid stack pointer");
                  return PLCRASH_ENOTFOUND;
              }
  
              /* Extract the stack size */
              pl_vm_address_t stack_size = entry->stack_offset;
              if (entry_type == PLCRASH_ASYNC_CFE_ENTRY_TYPE_FRAMELESS_INDIRECT) {
                  /* Stack size is encoded as a 32-bit value within the target process' TEXT segment; the value
                   * provided from the entry is used as an offset from the start of the function to the actual
                   * stack size. */
                  uint32_t indirect;
  
                  err = plcrash_async_task_memcpy(task, function_address, stack_size, &indirect, sizeof(indirect));
                  if (err != PLCRASH_ESUCCESS) {
                      PLCF_DEBUG("Failed to read indirect stack size from 0x%" PRIx64 " + 0x%" PRIx64 ": %d",
                                 (uint64_t) function_address, (uint64_t)stack_size, err);
                      return err;
                  }
  
                  stack_size = indirect + entry->stack_adjust;
              }
  
              /* Compute the stack pointer address */
              plcrash_greg_t sp = stack_size + plcrash_async_thread_state_get_reg(thread_state, PLCRASH_REG_SP);
              plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_SP, sp);
  
              if (entry->return_address_register == PLCRASH_REG_INVALID) {
                  /* Return address is on the stack */
                  pl_vm_address_t retaddr = sp - greg_size;
                  saved_reg_addr = retaddr - (greg_size * entry->register_count); /* retaddr - [saved registers] */
  
                  /* Original SP is found just before the return address. */
                  plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_SP, retaddr + greg_size);
  
                  /* Read the saved return address */
                  err = plcrash_async_task_memcpy(task, (pl_vm_address_t) retaddr, 0, dest, greg_size);
                  if (err != PLCRASH_ESUCCESS) {
                      PLCF_DEBUG("Failed to read return address from 0x%" PRIx64 ": %d", (uint64_t) retaddr, err);
                      return err;
                  }
                  
                  if (x64) {
                      plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_IP, regs.greg64[0]);
                  } else {
                      plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_IP, regs.greg32[0]);
                  }
              } else {
                  /* Return address is in a register; verify that the register is available */
                  if (!plcrash_async_thread_state_has_reg(thread_state, entry->return_address_register)) {
                      PLCF_DEBUG("The specified return_address_register '%s' is not available", plcrash_async_thread_state_get_reg_name(thread_state, entry->return_address_register));
                      return PLCRASH_ENOTFOUND;
                  }
                  
                  /* Copy the return address value to the new thread state's IP */
                  plcrash_async_thread_state_set_reg(new_thread_state, PLCRASH_REG_IP, plcrash_async_thread_state_get_reg(thread_state, entry->return_address_register));
                  
                  /* Saved registers are found below the new stack pointer. */
                  saved_reg_addr = sp - (greg_size * entry->register_count); /* sp - [saved registers] */
              }
  
              break;
          }
  
              
          case PLCRASH_ASYNC_CFE_ENTRY_TYPE_DWARF:
              return PLCRASH_ENOTSUP;
              
          case PLCRASH_ASYNC_CFE_ENTRY_TYPE_NONE:
              return PLCRASH_ENOTSUP;
      }
  
      /* Extract the saved registers */
      uint32_t register_count = plcrash_async_cfe_entry_register_count(entry);
      plcrash_regnum_t register_list[PLCRASH_ASYNC_CFE_SAVED_REGISTER_MAX];
      plcrash_async_cfe_entry_register_list(entry, register_list);
      for (uint32_t i = 0; i < register_count; i++) {
          /* The register list may be sparse */
          if (register_list[i] == PLCRASH_REG_INVALID)
              continue;
  
          /* Fetch and save register data */
          plcrash_error_t err;
          err = plcrash_async_task_memcpy(task, (pl_vm_address_t) saved_reg_addr, i*greg_size, dest, greg_size);
          if (err != PLCRASH_ESUCCESS) {
              PLCF_DEBUG("Failed to read register data for index %s: %d", plcrash_async_thread_state_get_reg_name(thread_state, register_list[i]), err);
              return err;
          }
  
          if (x64) {
              plcrash_async_thread_state_set_reg(new_thread_state, register_list[i], regs.greg64[0]);
          } else {
              plcrash_async_thread_state_set_reg(new_thread_state, register_list[i], regs.greg32[0]);
          }
      }
      
  
      return PLCRASH_ESUCCESS;
  }
  
  /**
   * Free all resources associated with @a entry.
   */
  void plcrash_async_cfe_entry_free (plcrash_async_cfe_entry_t *entry) {
      // noop
  }
  
  /**
   * @} plcrash_async_cfe
   */
  
  #endif /* PLCRASH_FEATURE_UNWIND_COMPACT */
  