480 строки
17 KiB
C++
480 строки
17 KiB
C++
/*
|
|
* 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.
|
|
*/
|
|
|
|
#ifndef PLCRASH_ASYNC_DWARF_PRIVATE_H
|
|
#define PLCRASH_ASYNC_DWARF_PRIVATE_H 1
|
|
|
|
#include "PLCrashAsync.h"
|
|
#include "PLCrashAsyncImageList.h"
|
|
#include "PLCrashAsyncThread.h"
|
|
|
|
#include "PLCrashFeatureConfig.h"
|
|
#include "PLCrashMacros.h"
|
|
|
|
#include <inttypes.h>
|
|
|
|
#if PLCRASH_FEATURE_UNWIND_DWARF
|
|
|
|
PLCR_CPP_BEGIN_NS
|
|
namespace async {
|
|
|
|
/**
|
|
* @internal
|
|
* @ingroup plcrash_async_dwarf
|
|
* @{
|
|
*/
|
|
|
|
/**
|
|
* DWARF CFA opcodes, as defined by the DWARF 4 Specification (Section 7.23).
|
|
*
|
|
* The DWARF CFA opcodes are expressed in two forms; the first form includes an non-zero
|
|
* opcode in the top two bits, with the bottom 6 bits used to provide a constant value
|
|
* operand.
|
|
*
|
|
* The second form includes zeros in the top two bits, with the actual opcode
|
|
* stored in the bottom 6 bits.
|
|
*/
|
|
typedef enum {
|
|
/** DW_CFA_advance_loc: 'delta' stored in low six bits */
|
|
DW_CFA_advance_loc = 0x40,
|
|
|
|
/** DW_CFA_offset: 'register' stored in low six bits, operand is ULEB128 offset */
|
|
DW_CFA_offset = 0x80,
|
|
|
|
/** DW_CFA_restore: 'register' stored in low six bits */
|
|
DW_CFA_restore = 0xc0,
|
|
|
|
/** DW_CFA_nop */
|
|
DW_CFA_nop = 0,
|
|
|
|
/** DW_CFA_set_loc */
|
|
DW_CFA_set_loc = 0x01,
|
|
|
|
/** DW_CFA_advance_loc1, operand is 1-byte delta */
|
|
DW_CFA_advance_loc1 = 0x02,
|
|
|
|
/** DW_CFA_advance_loc2, operand is 2-byte delta */
|
|
DW_CFA_advance_loc2 = 0x03,
|
|
|
|
/** DW_CFA_advance_loc4, operand is 4-byte delta */
|
|
DW_CFA_advance_loc4 = 0x04,
|
|
|
|
/** DW_CFA_offset_extended, operands are ULEB128 register and ULEB128 offset */
|
|
DW_CFA_offset_extended = 0x05,
|
|
|
|
/** DW_CFA_restore_extended, operand is ULEB128 register */
|
|
DW_CFA_restore_extended = 0x06,
|
|
|
|
/** DW_CFA_undefined, operand is ULEB128 register */
|
|
DW_CFA_undefined = 0x07,
|
|
|
|
/** DW_CFA_same_value, operand is ULEB128 register */
|
|
DW_CFA_same_value = 0x08,
|
|
|
|
/** DW_CFA_register, operands are ULEB128 register, and ULEB128 register */
|
|
DW_CFA_register = 0x09,
|
|
|
|
/** DW_CFA_remember_state */
|
|
DW_CFA_remember_state = 0x0a,
|
|
|
|
/** DW_CFA_restore_state */
|
|
DW_CFA_restore_state = 0x0b,
|
|
|
|
/** DW_CFA_def_cfa, operands are ULEB128 register and ULEB128 offset */
|
|
DW_CFA_def_cfa = 0x0c,
|
|
|
|
/** DW_CFA_def_cfa_register, operand is ULEB128 register */
|
|
DW_CFA_def_cfa_register = 0x0d,
|
|
|
|
/** DW_CFA_def_cfa_offset, operand is ULEB128 offset */
|
|
DW_CFA_def_cfa_offset = 0x0e,
|
|
|
|
/** DW_CFA_def_cfa_expression */
|
|
DW_CFA_def_cfa_expression = 0x0f,
|
|
|
|
/** DW_CFA_expression, operands are ULEB128 register, BLOCK */
|
|
DW_CFA_expression = 0x10,
|
|
|
|
/** DW_CFA_offset_extended_sf, operands are ULEB128 register, SLEB128 offset */
|
|
DW_CFA_offset_extended_sf = 0x11,
|
|
|
|
/** DW_CFA_def_cfa_sf, operands are ULEB128, register SLEB128 offset */
|
|
DW_CFA_def_cfa_sf = 0x12,
|
|
|
|
/** DW_CFA_def_cfa_offset_sf, operand is SLEB128 offset */
|
|
DW_CFA_def_cfa_offset_sf = 0x13,
|
|
|
|
/** DW_CFA_val_offset, operands are ULEB128, ULEB128 */
|
|
DW_CFA_val_offset = 0x14,
|
|
|
|
/** DW_CFA_val_offset_sf, operands are ULEB128, SLEB128 */
|
|
DW_CFA_val_offset_sf = 0x15,
|
|
|
|
/** DW_CFA_val_expression, operands are ULEB128, BLOCK */
|
|
DW_CFA_val_expression = 0x16,
|
|
|
|
/** DW_CFA_lo_user */
|
|
DW_CFA_lo_user = 0x1c,
|
|
|
|
/** DW_CFA_hi_user */
|
|
DW_CFA_hi_user = 0x3f,
|
|
} DW_CFA_t;
|
|
|
|
/**
|
|
* Exception handling pointer encoding constants, as defined by the LSB Specification:
|
|
* http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/dwarfext.html
|
|
*
|
|
* The upper 4 bits indicate how the value is to be applied. The lower 4 bits indicate the encoding format of the data.
|
|
*/
|
|
typedef enum DW_EH_PE {
|
|
/**
|
|
* Value is an indirect reference. This value is not specified by the LSB, and appears to be a
|
|
* GCC extension; unfortunately, the intended use is not clear:
|
|
*
|
|
* - Apple's implementation of libunwind treats this as an indirected reference to a target-width pointer value,
|
|
* as does the upstream libunwind.
|
|
* - LLDB does not appear to support indirect encoding at all.
|
|
* - LLVM's asm printer decodes it as an independent flag on the encoding type value; eg, DW_EH_PE_indirect | DW_EH_PE_uleb128 | DW_EH_PE_pcrel
|
|
* LLVM/clang does not seem to otherwise emit this value.
|
|
* - GDB explicitly does not support indirect encodings.
|
|
*
|
|
* For our purposes, we treat the value as per LLVM's asm printer, and may re-evaluate if the indirect encoding
|
|
* is ever seen in the wild.
|
|
*/
|
|
DW_EH_PE_indirect = 0x80,
|
|
|
|
/** No value is present. */
|
|
DW_EH_PE_omit = 0xff,
|
|
|
|
/**
|
|
* If no flags are set (0x0), the value is a literal pointer whose size is determined by the architecture. Note that this has two different meanings,
|
|
* and is not a flag, but rather, the absense of a flag (0x0). If a relative flag is not set in the high 4 bits of the DW_EH_PE encoding, this
|
|
* signifies that no offset is to be applied. If the bottom 4 bits are 0, this signifies a native-width pointer value reference.
|
|
*
|
|
* As such, it's possible to mix DW_EH_PE_absptr with other relative flags, eg, DW_EH_PE_textrel. Examples:
|
|
*
|
|
* - DW_EH_PE_absptr|DW_EH_PE_textrel (0x20): Address is relative to TEXT segment, and is an architecture-native pointer width.
|
|
* - DW_EH_PE_absptr|DW_EH_PE_uleb128 (0x1): The address is absolute, and is a ULEB128 value.
|
|
* - DW_EH_PE_absptr|DW_EH_PE_uleb128|DW_EH_PE_indirect (0x81): The address is absolute, indirect, and is a ULEB128 value.
|
|
*/
|
|
DW_EH_PE_absptr = 0x00,
|
|
|
|
/** Unsigned value encoded using LEB128 as defined by DWARF Debugging Information Format, Revision 2.0.0. */
|
|
DW_EH_PE_uleb128 = 0x01,
|
|
|
|
/** Unsigned 16-bit value */
|
|
DW_EH_PE_udata2 = 0x02,
|
|
|
|
/* Unsigned 32-bit value */
|
|
DW_EH_PE_udata4 = 0x03,
|
|
|
|
/** Unsigned 64-bit value */
|
|
DW_EH_PE_udata8 = 0x04,
|
|
|
|
/** Signed value encoded using LEB128 as defined by DWARF Debugging Information Format, Revision 2.0.0. */
|
|
DW_EH_PE_sleb128 = 0x09,
|
|
|
|
/** Signed 16-bit value */
|
|
DW_EH_PE_sdata2 = 0x0a,
|
|
|
|
/** Signed 32-bit value */
|
|
DW_EH_PE_sdata4 = 0x0b,
|
|
|
|
/** Signed 64-bit value */
|
|
DW_EH_PE_sdata8 = 0x0c,
|
|
|
|
/** Value is relative to the current program counter. */
|
|
DW_EH_PE_pcrel = 0x10,
|
|
|
|
/** Value is relative to the beginning of the __TEXT section. */
|
|
DW_EH_PE_textrel = 0x20,
|
|
|
|
/** Value is relative to the beginning of the __DATA section. */
|
|
DW_EH_PE_datarel = 0x30,
|
|
|
|
/** Value is relative to the beginning of the function. */
|
|
DW_EH_PE_funcrel = 0x40,
|
|
|
|
/**
|
|
* Value is aligned to an address unit sized boundary. The meaning of this flag is not defined in the
|
|
* LSB 4.1.0 specification; review of various implementations demonstrate that:
|
|
*
|
|
* - The value must be aligned relative to the VM load address of the eh_frame/debug_frame section that contains
|
|
* it.
|
|
* - Some implementations assume that an aligned pointer value is always the architecture's natural pointer size.
|
|
* Other implementations, such as gdb, permit the use of alternative value types (uleb, sleb, data2/4/8, etc).
|
|
*
|
|
* In our implementation, we support the combination of DW_EH_PE_aligned with any other supported value type.
|
|
*/
|
|
DW_EH_PE_aligned = 0x50,
|
|
} DW_EH_PE_t;
|
|
|
|
/** Mask for the lower four bits of a DW_EH_PE_t value, defining the encoding type. */
|
|
#define DW_EH_PE_MASK_ENCODING 0x0F
|
|
|
|
/**
|
|
* DWARF CFA register rules, as defined in DWARF 4 Section 6.4.1.
|
|
*/
|
|
typedef enum {
|
|
/**
|
|
* The previous value of this register is saved at the address CFA+N where CFA is the current
|
|
* CFA value and N is a signed offset.
|
|
*/
|
|
PLCRASH_DWARF_CFA_REG_RULE_OFFSET = 0,
|
|
|
|
/**
|
|
* The previous value of this register is the value CFA+N where CFA is the current CFA value and N is a signed offset.
|
|
*/
|
|
PLCRASH_DWARF_CFA_REG_RULE_VAL_OFFSET = 1,
|
|
|
|
/**
|
|
* The previous value of this register is stored in another register numbered R.
|
|
*/
|
|
PLCRASH_DWARF_CFA_REG_RULE_REGISTER = 2,
|
|
|
|
/**
|
|
* The previous value of this register is located at the address produced by executing the DWARF expression E.
|
|
*/
|
|
PLCRASH_DWARF_CFA_REG_RULE_EXPRESSION = 3,
|
|
|
|
|
|
/**
|
|
* The previous value of this register is the value produced by executing the DWARF expression E.
|
|
*/
|
|
PLCRASH_DWARF_CFA_REG_RULE_VAL_EXPRESSION = 4,
|
|
|
|
/**
|
|
* This register has not been modified from the previous frame. (By convention, it is preserved by the callee, but
|
|
* the callee has not modified it.)
|
|
*
|
|
* The register's value may be found in the frame's thread state. For frames other than the first, the
|
|
* register may not have been restored, and thus may be unavailable.
|
|
*/
|
|
PLCRASH_DWARF_CFA_REG_RULE_SAME_VALUE = 5,
|
|
} plcrash_dwarf_cfa_reg_rule_t;
|
|
|
|
|
|
/**
|
|
* @internal
|
|
*
|
|
* GNU eh_frame pointer reader. Implements reading of pointer values encoded using the GNU eh_frame scheme.
|
|
*
|
|
* @tparam machine_ptr The target's native unsigned word type.
|
|
*/
|
|
template <typename machine_ptr> class gnu_ehptr_reader {
|
|
public:
|
|
gnu_ehptr_reader (const plcrash_async_byteorder_t *byteorder);
|
|
void set_frame_section_base (machine_ptr frame_section_base, machine_ptr frame_section_vm_addr);
|
|
void set_text_base (machine_ptr text_base);
|
|
void set_data_base (machine_ptr data_base);
|
|
void set_func_base (machine_ptr func_base);
|
|
|
|
plcrash_error_t 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);
|
|
private:
|
|
const plcrash_async_byteorder_t *_byteorder;
|
|
|
|
typedef struct base_addr {
|
|
/** If false, no address is available. If true, address is valid. */
|
|
bool valid;
|
|
|
|
/** The defined base address. */
|
|
machine_ptr address;
|
|
} base_addr_t;
|
|
|
|
/** If false, no frame section base has been set */
|
|
bool _has_frame_section_base;
|
|
|
|
/**
|
|
* The base address (in-memory) of the loaded debug_frame or eh_frame section, or PL_VM_ADDRESS_INVALID. 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.
|
|
*/
|
|
machine_ptr _frame_section_base;
|
|
|
|
/**
|
|
* The base VM address of the eh_frame or debug_frame section, or PL_VM_ADDRESS_INVALID. 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.
|
|
*/
|
|
machine_ptr _frame_section_vm_addr;
|
|
|
|
/** The base address of the text segment to be applied to DW_EH_PE_textrel offsets, or PL_VM_ADDRESS_INVALID. */
|
|
base_addr_t _text_base;
|
|
|
|
/** The base address of the data segment to be applied to DW_EH_PE_datarel offsets, or PL_VM_ADDRESS_INVALID. */
|
|
base_addr_t _data_base;
|
|
|
|
/** The base address of the function to be applied to DW_EH_PE_funcrel offsets, or PL_VM_ADDRESS_INVALID. */
|
|
base_addr_t _func_base;
|
|
};
|
|
|
|
plcrash_error_t 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);
|
|
plcrash_error_t 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);
|
|
|
|
plcrash_error_t 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);
|
|
plcrash_error_t 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);
|
|
|
|
/**
|
|
* @internal
|
|
*
|
|
* Read a value that is either 1, 2, 4, or 8 bytes in size, allowing natural unsigned integer overflow
|
|
* to occur.
|
|
*
|
|
* Returns true on success, false on failure.
|
|
*
|
|
* @param mobj Memory object from which to read the value.
|
|
* @param byteorder Byte order of the target value.
|
|
* @param base_addr The base address (within @a mobj's address space) from which to perform the read.
|
|
* @param offset An offset to be applied to base_addr.
|
|
* @param data_size The size of the value to be read. If an unsupported size is supplied, false will be returned.
|
|
* @param dest The destination value.
|
|
*/
|
|
template <typename T>
|
|
plcrash_error_t plcrash_async_dwarf_read_uintmax64 (plcrash_async_mobject_t *mobj,
|
|
const plcrash_async_byteorder_t *byteorder,
|
|
pl_vm_address_t base_addr,
|
|
pl_vm_off_t offset,
|
|
uint8_t data_size,
|
|
T *dest)
|
|
{
|
|
union udata {
|
|
uint8_t u8;
|
|
uint16_t u16;
|
|
uint32_t u32;
|
|
uint64_t u64;
|
|
} *data;
|
|
|
|
data = (union udata *) plcrash_async_mobject_remap_address(mobj, base_addr, offset, data_size);
|
|
if (data == NULL)
|
|
return PLCRASH_EINVAL;
|
|
|
|
switch (data_size) {
|
|
case 1:
|
|
*dest = data->u8;
|
|
break;
|
|
|
|
case 2:
|
|
*dest = byteorder->swap16(data->u16);
|
|
break;
|
|
|
|
case 4:
|
|
*dest = byteorder->swap32(data->u32);
|
|
break;
|
|
|
|
case 8:
|
|
*dest = (T)byteorder->swap64(data->u64);
|
|
break;
|
|
|
|
default:
|
|
PLCF_DEBUG("Unhandled data width %" PRIu64, (uint64_t) data_size);
|
|
return PLCRASH_EINVAL;
|
|
}
|
|
|
|
return PLCRASH_ESUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
*
|
|
* Read a value that is either 1, 2, 4, or 8 bytes in size from @a task, allowing natural unsigned integer overflow
|
|
* to occur.
|
|
*
|
|
* Returns true on success, false on failure.
|
|
*
|
|
* @param task Task from which to read the value.
|
|
* @param byteorder Byte order of the target value.
|
|
* @param base_addr The base address (within @a mobj's address space) from which to perform the read.
|
|
* @param offset An offset to be applied to base_addr.
|
|
* @param data_size The size of the value to be read. If an unsupported size is supplied, false will be returned.
|
|
* @param dest The destination value.
|
|
*/
|
|
template <typename T>
|
|
plcrash_error_t plcrash_async_dwarf_read_task_uintmax64 (task_t task,
|
|
const plcrash_async_byteorder_t *byteorder,
|
|
pl_vm_address_t base_addr,
|
|
pl_vm_off_t offset,
|
|
uint8_t data_size,
|
|
T *dest)
|
|
{
|
|
plcrash_error_t err;
|
|
union {
|
|
uint8_t u8;
|
|
uint16_t u16;
|
|
uint32_t u32;
|
|
uint64_t u64;
|
|
} data;
|
|
|
|
switch (data_size) {
|
|
case 1:
|
|
if ((err = plcrash_async_task_read_uint8(task, base_addr, offset, &data.u8)) != PLCRASH_ESUCCESS)
|
|
return err;
|
|
*dest = data.u8;
|
|
break;
|
|
|
|
case 2:
|
|
if ((err = plcrash_async_task_read_uint16(task, byteorder, base_addr, offset, &data.u16)) != PLCRASH_ESUCCESS)
|
|
return err;
|
|
*dest = data.u16;
|
|
break;
|
|
|
|
case 4:
|
|
if ((err = plcrash_async_task_read_uint32(task, byteorder, base_addr, offset, &data.u32)) != PLCRASH_ESUCCESS)
|
|
return err;
|
|
*dest = data.u32;
|
|
break;
|
|
|
|
case 8:
|
|
if ((err = plcrash_async_task_read_uint64(task, byteorder, base_addr, offset, &data.u64)) != PLCRASH_ESUCCESS)
|
|
return err;
|
|
*dest = (T)data.u64;
|
|
break;
|
|
|
|
default:
|
|
PLCF_DEBUG("Unhandled data width %" PRIu64, (uint64_t) data_size);
|
|
return PLCRASH_EINVAL;
|
|
}
|
|
|
|
return PLCRASH_ESUCCESS;
|
|
}
|
|
|
|
PLCR_CPP_END_NS
|
|
}
|
|
|
|
/*
|
|
* @}
|
|
*/
|
|
|
|
#endif /* PLCRASH_FEATURE_UNWIND_DWARF */
|
|
#endif /* PLCRASH_ASYNC_DWARF_PRIVATE_H */
|