Source/PLCrashAsyncDwarfPrimitives.cpp (278 lines of code) (raw):
/*
* 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 "PLCrashAsyncDwarfPrimitives.hpp"
#include "PLCrashAsyncDwarfEncoding.hpp"
#include "PLCrashFeatureConfig.h"
#include <inttypes.h>
#if PLCRASH_FEATURE_UNWIND_DWARF
using namespace plcrash::async;
/**
* @internal
* @ingroup plcrash_async_dwarf
* @{
*/
/**
* Default reader constructor.
*
* @param byteorder The pointer encoding byte order. This value must remain valid throughout the lifetime of the new
* reader instance.
*/
template <typename machine_ptr> gnu_ehptr_reader<machine_ptr>::gnu_ehptr_reader (const plcrash_async_byteorder_t *byteorder) {
_byteorder = byteorder;
_has_frame_section_base = false;
_text_base.valid = false;
_data_base.valid = false;
_func_base.valid = false;
}
/**
* Set the DW_EH_PE_aligned base addresses.
*
* @param frame_section_base The base address (in-memory) of the loaded debug_frame or eh_frame section. This is
* used to calculate the offset of DW_EH_PE_aligned from the start of the frame section. This address should be the
* actual base address at which the section has been mapped.
*
* @param frame_section_vm_addr The base VM address of the eh_frame or debug_frame section.
* This is used to calculate alignment for DW_EH_PE_aligned-encoded values. This address should be the aligned base VM
* address at which the section will (or has been loaded) during execution, and will be used to calculate
* DW_EH_PE_aligned alignment.
*/
template <typename machine_ptr> void gnu_ehptr_reader<machine_ptr>::set_frame_section_base (machine_ptr frame_section_base, machine_ptr frame_section_vm_addr) {
_has_frame_section_base = true;
_frame_section_base = frame_section_base;
_frame_section_vm_addr = frame_section_vm_addr;
}
/**
* Set the DW_EH_PE_textrel base address.
*
* @param text_base The base address of the text segment to be applied to DW_EH_PE_textrel offsets.
*/
template <typename machine_ptr> void gnu_ehptr_reader<machine_ptr>::set_text_base (machine_ptr text_base) {
_text_base.valid = true;
_text_base.address = text_base;
}
/**
* Set the DW_EH_PE_datarel base address.
*
* @param data_base The base address of the data segment to be applied to DW_EH_PE_datarel offsets.
*/
template <typename machine_ptr> void gnu_ehptr_reader<machine_ptr>::set_data_base (machine_ptr data_base) {
_data_base.valid = true;
_data_base.address = data_base;
}
/**
* Set the DW_EH_PE_funcrel base address.
*
* @param func_base The base address of the function to be applied to DW_EH_PE_funcrel offsets.
*/
template <typename machine_ptr> void gnu_ehptr_reader<machine_ptr>::set_func_base (machine_ptr func_base) {
_func_base.valid = true;
_func_base.address = func_base;
}
/**
* Read a GNU DWARF encoded pointer value from @a location within @a mobj. The encoding format is defined in
* the Linux Standard Base Core Specification 4.1, section 10.5, DWARF Extensions.
*
* @param mobj The memory object from which the pointer data (including TEXT/DATA-relative values) will be read. This
* should map the full binary that may be read; the pointer value may reference data that is relative to the binary
* sections, depending on the base addresses supplied via @a state.
* @param location A task-relative location within @a mobj.
* @param offset An offset to apply to @a location.
* @param encoding The encoding method to be used to decode the target pointer. If the encoding requires
* a base address that has not previously been set, PLCRASH_ENOTSUP will be returned.
* @param result On success, the pointer value.
* @param size On success, will be set to the total size of the pointer data read at @a location, in bytes.
*/
template <typename machine_ptr> plcrash_error_t gnu_ehptr_reader<machine_ptr>::read (plcrash_async_mobject_t *mobj,
pl_vm_address_t location,
pl_vm_off_t offset,
DW_EH_PE_t encoding,
machine_ptr *result,
size_t *size)
{
plcrash_error_t err;
/* Skip DW_EH_pe_omit -- as per LSB 4.1.0, this signifies that no value is present */
if (encoding == DW_EH_PE_omit) {
PLCF_DEBUG("Skipping decoding of DW_EH_PE_omit pointer");
return PLCRASH_ENOTFOUND;
}
/* Initialize the output size; we apply offsets to this size to allow for aligning the
* address prior to reading the pointer data, etc. */
*size = 0;
/* Calculate the base address; bits 5-8 are used to specify the relative offset type */
machine_ptr base;
switch (encoding & 0x70) {
case DW_EH_PE_pcrel:
/*
* Set the ptr PC relative base to our current read offset. The LSB specification does not define what value should
* be used for the DW_EH_PE_pcrel base address; reviewing the available implementations demonstrates that
* the current read buffer position should be used.
*/
base = (machine_ptr)(location + offset);
break;
case DW_EH_PE_absptr:
/* No flags are set */
base = 0x0;
break;
case DW_EH_PE_textrel:
if (!_text_base.valid) {
PLCF_DEBUG("Cannot decode DW_EH_PE_textrel value with PLCRASH_ASYNC_DWARF_INVALID_BASE_ADDR text_addr");
return PLCRASH_ENOTSUP;
}
base = _text_base.address;
break;
case DW_EH_PE_datarel:
if (!_data_base.valid) {
PLCF_DEBUG("Cannot decode DW_EH_PE_datarel value with PLCRASH_ASYNC_DWARF_INVALID_BASE_ADDR data_base");
return PLCRASH_ENOTSUP;
}
base = _data_base.address;
break;
case DW_EH_PE_funcrel:
if (!_func_base.valid) {
PLCF_DEBUG("Cannot decode DW_EH_PE_funcrel value with PLCRASH_ASYNC_DWARF_INVALID_BASE_ADDR func_base");
return PLCRASH_ENOTSUP;
}
base = _func_base.address;
break;
case DW_EH_PE_aligned: {
/* Verify availability of required base addresses */
if (!_has_frame_section_base) {
PLCF_DEBUG("Cannot decode DW_EH_PE_aligned value without a valid frame section base configured");
return PLCRASH_ENOTSUP;
}
/* Compute the offset+alignment relative to the section base */
PLCF_ASSERT(location >= _frame_section_base);
machine_ptr locationOffset = (machine_ptr)location - _frame_section_base;
/* Apply to the VM load address for the section. */
machine_ptr vm_addr = _frame_section_vm_addr + locationOffset;
machine_ptr vm_aligned = (vm_addr + (sizeof(machine_ptr)-1)) & ~(sizeof(machine_ptr)-1);
/* Apply the new offset to the actual load address */
location += (vm_aligned - vm_addr);
/* Set the base size to the number of bytes skipped */
base = 0x0;
*size = (size_t)(vm_aligned - vm_addr);
break;
}
default:
PLCF_DEBUG("Unsupported pointer base encoding of 0x%x", encoding);
return PLCRASH_ENOTSUP;
}
/*
* Decode and return the pointer value [+ offset].
*
* TODO: This code permits overflow to occur under the assumption that the failure will be caught
* when safely dereferencing the resulting address. This should only occur when either bad data is presented,
* or due to an implementation flaw in this code path -- in those cases, it would be preferable to
* detect overflow early.
*/
switch (encoding & 0x0F) {
case DW_EH_PE_absptr: {
machine_ptr value;
if ((err = plcrash_async_dwarf_read_uintmax64(mobj, _byteorder, location, offset, sizeof(machine_ptr), &value)) != PLCRASH_ESUCCESS) {
PLCF_DEBUG("Failed to read value at 0x%" PRIx64, (uint64_t) location);
return err;
}
*result = value + base;
*size += sizeof(machine_ptr);
break;
}
case DW_EH_PE_uleb128: {
uint64_t ulebv;
pl_vm_size_t uleb_size;
if ((err = plcrash_async_dwarf_read_uleb128(mobj, location, offset, &ulebv, &uleb_size)) != PLCRASH_ESUCCESS) {
PLCF_DEBUG("Failed to read uleb128 value at 0x%" PRIx64, (uint64_t) location);
return err;
}
*result = (machine_ptr)(ulebv + base);
*size += uleb_size;
break;
}
case DW_EH_PE_udata2: {
uint16_t udata2;
if ((err = plcrash_async_mobject_read_uint16(mobj, _byteorder, location, offset, &udata2)) != PLCRASH_ESUCCESS) {
PLCF_DEBUG("Failed to read udata2 value at 0x%" PRIx64, (uint64_t) location);
return err;
}
*result = udata2 + base;
*size += 2;
break;
}
case DW_EH_PE_udata4: {
uint32_t udata4;
if ((err = plcrash_async_mobject_read_uint32(mobj, _byteorder, location, offset, &udata4)) != PLCRASH_ESUCCESS) {
PLCF_DEBUG("Failed to read udata4 value at 0x%" PRIx64, (uint64_t) location);
return err;
}
*result = udata4 + base;
*size += 4;
break;
}
case DW_EH_PE_udata8: {
uint64_t udata8;
if ((err = plcrash_async_mobject_read_uint64(mobj, _byteorder, location, offset, &udata8)) != PLCRASH_ESUCCESS) {
PLCF_DEBUG("Failed to read udata8 value at 0x%" PRIx64, (uint64_t) location);
return err;
}
*result = (machine_ptr)(udata8 + base);
*size += 8;
break;
}
case DW_EH_PE_sleb128: {
int64_t slebv;
pl_vm_size_t sleb_size;
if ((err = plcrash_async_dwarf_read_sleb128(mobj, location, offset, &slebv, &sleb_size)) != PLCRASH_ESUCCESS) {
PLCF_DEBUG("Failed to read sleb128 value at 0x%" PRIx64, (uint64_t) location);
return err;
}
*result = (machine_ptr)(slebv + base);
*size += sleb_size;
break;
}
case DW_EH_PE_sdata2: {
int16_t sdata2;
if ((err = plcrash_async_mobject_read_uint16(mobj, _byteorder, location, offset, (uint16_t *) &sdata2)) != PLCRASH_ESUCCESS) {
PLCF_DEBUG("Failed to read sdata2 value at 0x%" PRIx64, (uint64_t) location);
return err;
}
*result = sdata2 + base;
*size += 2;
break;
}
case DW_EH_PE_sdata4: {
int32_t sdata4;
if ((err = plcrash_async_mobject_read_uint32(mobj, _byteorder, location, offset, (uint32_t *) &sdata4)) != PLCRASH_ESUCCESS) {
PLCF_DEBUG("Failed to read sdata4 value at 0x%" PRIx64, (uint64_t) location);
return err;
}
*result = sdata4 + base;
*size += 4;
break;
}
case DW_EH_PE_sdata8: {
int64_t sdata8;
if ((err = plcrash_async_mobject_read_uint64(mobj, _byteorder, location, offset, (uint64_t *) &sdata8)) != PLCRASH_ESUCCESS) {
PLCF_DEBUG("Failed to read sdata8 value at 0x%" PRIx64, (uint64_t) location);
return err;
}
*result = (machine_ptr)(sdata8 + base);
*size += 8;
break;
}
default:
PLCF_DEBUG("Unknown pointer encoding of type 0x%x", encoding);
return PLCRASH_ENOTSUP;
}
/* Handle indirection; the target value may only be an absptr; there is no way to define an
* encoding for the indirected target. */
if (encoding & DW_EH_PE_indirect) {
/*
* An indirect read may refer to memory outside of the eh_frame/debug_section; as such, we use task-based reading to handle
* indirect reads.
*
* TODO: This implementation should provide a resolvable GNUEHPtr value, rather than requiring resolution occur here.
*/
return plcrash_async_dwarf_read_task_uintmax64(plcrash_async_mobject_task(mobj), _byteorder, (pl_vm_address_t) *result, 0, sizeof(machine_ptr), result);
}
return PLCRASH_ESUCCESS;
}
#pragma mark Primitive Type Decoding
/**
* Read a SLEB128 value directly from @a location within @a task.
*
* @param task The task from which the LEB128 data will be read.
* @param location A task-relative location within @a mobj.
* @param offset Offset to be applied to @a location.
* @param result On success, the ULEB128 value.
* @param size On success, will be set to the total size of the decoded LEB128 value at @a location, in bytes.
*
* @warning Reading directly from the task requires performing memory remapping, and will incurs a higher runtime overhead
* than plcrash_async_dwarf_read_sleb128().
*/
plcrash_error_t plcrash::async::plcrash_async_dwarf_read_task_sleb128 (task_t task, pl_vm_address_t location, pl_vm_off_t offset, int64_t *result, pl_vm_size_t *size) {
pl_vm_address_t target;
plcrash_error_t err;
/* Calculate the absolute target */
if (!plcrash_async_address_apply_offset(location, offset, &target)) {
PLCF_DEBUG("Applying the offset of 0x%" PRId64 " to base address %" PRIx64 " exceeds PL_VM_ADDRESS_MAX", (int64_t) offset, (uint64_t) location);
return PLCRASH_EINVAL;
}
/*
* Map up to PAGE_SIZE of bytes; we allow for shorter allocations, and rely on the sleb128 reader code to determine whether
* the mapping is short. We use a page mapping, rather than reading data per-byte, to avoid per-byte syscall overhead.
*/
plcrash_async_mobject_t mobj;
if ((err = plcrash_async_mobject_init(&mobj, task, target, PAGE_SIZE, false)) != PLCRASH_ESUCCESS) {
PLCF_DEBUG("Failed to map uleb128 page");
return err;
}
/* Perform the actual read */
err = plcrash_async_dwarf_read_sleb128(&mobj, target, 0x0, result, size);
/* Clean up our mapping */
plcrash_async_mobject_free(&mobj);
return err;
}
/**
* Read a ULEB128 value directly from @a location within @a task.
*
* @param task The task from which the LEB128 data will be read.
* @param location A task-relative location within @a mobj.
* @param offset Offset to be applied to @a location.
* @param result On success, the ULEB128 value.
* @param size On success, will be set to the total size of the decoded LEB128 value at @a location, in bytes.
*
* @warning Reading directly from the task requires performing memory remapping, and will incurs a higher runtime overhead
* than plcrash_async_dwarf_read_sleb128().
*/
plcrash_error_t plcrash::async::plcrash_async_dwarf_read_task_uleb128 (task_t task, pl_vm_address_t location, pl_vm_off_t offset, uint64_t *result, pl_vm_size_t *size) {
pl_vm_address_t target;
plcrash_error_t err;
/* Calculate the absolute target */
if (!plcrash_async_address_apply_offset(location, offset, &target)) {
PLCF_DEBUG("Applying the offset of 0x%" PRId64 " to base address %" PRIx64 " exceeds PL_VM_ADDRESS_MAX", (int64_t) offset, (uint64_t) location);
return PLCRASH_EINVAL;
}
/*
* Map up to PAGE_SIZE of bytes; we allow for shorter allocations, and rely on the uleb128 reader code to determine whether
* the mapping is short. We use a page mapping, rather than reading data per-byte, to avoid per-byte syscall overhead.
*/
plcrash_async_mobject_t mobj;
if ((err = plcrash_async_mobject_init(&mobj, task, target, PAGE_SIZE, false)) != PLCRASH_ESUCCESS) {
PLCF_DEBUG("Failed to map uleb128 page");
return err;
}
/* Perform the actual read */
err = plcrash_async_dwarf_read_uleb128(&mobj, target, 0x0, result, size);
/* Clean up our mapping */
plcrash_async_mobject_free(&mobj);
return err;
}
/**
* Read a ULEB128 value from @a location within @a mobj.
*
* @param mobj The memory object from which the LEB128 data will be read.
* @param location A task-relative location within @a mobj.
* @param offset Offset to be applied to @a location.
* @param result On success, the ULEB128 value.
* @param size On success, will be set to the total size of the decoded LEB128 value at @a location, in bytes.
*/
plcrash_error_t plcrash::async::plcrash_async_dwarf_read_uleb128 (plcrash_async_mobject_t *mobj, pl_vm_address_t location, pl_vm_off_t offset, uint64_t *result, pl_vm_size_t *size) {
unsigned int shift = 0;
pl_vm_off_t position = 0;
*result = 0;
uint8_t *p;
while ((p = (uint8_t *) plcrash_async_mobject_remap_address(mobj, location, position + offset, 1)) != NULL) {
/* LEB128 uses 7 bits for the number, the final bit to signal completion */
uint8_t byte = *p;
*result |= ((uint64_t) (byte & 0x7f)) << shift;
shift += 7;
/* This is used to track length, so we must set it before
* potentially terminating the loop below */
position++;
/* Check for terminating bit */
if ((byte & 0x80) == 0)
break;
/* Check for a ULEB128 larger than 64-bits */
if (shift >= 64) {
PLCF_DEBUG("ULEB128 is larger than the maximum supported size of 64 bits");
return PLCRASH_ENOTSUP;
}
}
if (p == NULL) {
PLCF_DEBUG("ULEB128 value did not terminate within mapped memory range");
return PLCRASH_EINVAL;
}
*size = position;
return PLCRASH_ESUCCESS;
}
/**
* Read a SLEB128 value from @a location within @a mobj.
*
* @param mobj The memory object from which the LEB128 data will be read.
* @param location A task-relative location within @a mobj.
* @param offset Offset to be applied to @a location.
* @param result On success, the ULEB128 value.
* @param size On success, will be set to the total size of the decoded LEB128 value, in bytes.
*/
plcrash_error_t plcrash::async::plcrash_async_dwarf_read_sleb128 (plcrash_async_mobject_t *mobj, pl_vm_address_t location, pl_vm_off_t offset, int64_t *result, pl_vm_size_t *size) {
unsigned int shift = 0;
pl_vm_off_t position = 0;
*result = 0;
uint8_t *p;
while ((p = (uint8_t *) plcrash_async_mobject_remap_address(mobj, location, position + offset, 1)) != NULL) {
/* LEB128 uses 7 bits for the number, the final bit to signal completion */
uint8_t byte = *p;
*result |= ((uint64_t) (byte & 0x7f)) << shift;
shift += 7;
/* This is used to track length, so we must set it before
* potentially terminating the loop below */
position++;
/* Check for terminating bit */
if ((byte & 0x80) == 0)
break;
/* Check for a ULEB128 larger than 64-bits */
if (shift >= 64) {
PLCF_DEBUG("ULEB128 is larger than the maximum supported size of 64 bits");
return PLCRASH_ENOTSUP;
}
}
if (p == NULL) {
PLCF_DEBUG("ULEB128 value did not terminate within mapped memory range");
return PLCRASH_EINVAL;
}
/* Sign bit is 2nd high order bit */
if (shift < 64 && (*p & 0x40))
*result |= -(1ULL << shift);
*size = position;
return PLCRASH_ESUCCESS;
}
/* Provide explicit 32/64-bit instantiations */
template class plcrash::async::gnu_ehptr_reader<uint32_t>;
template class plcrash::async::gnu_ehptr_reader<uint64_t>;
/*
* @}
*/
#endif /* PLCRASH_FEATURE_UNWIND_DWARF */