plcrashreporter/Source/PLCrashAsyncDwarfEncoding.cpp

215 строки
9.1 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.
*/
#include "PLCrashAsyncDwarfEncoding.hpp"
#include "PLCrashFeatureConfig.h"
#include <inttypes.h>
#if PLCRASH_FEATURE_UNWIND_DWARF
using namespace plcrash::async;
/**
* @internal
* @ingroup plcrash_async
* @defgroup plcrash_async_dwarf DWARF
*
* Implements async-safe parsing of DWARF encodings.
* @{
*/
/**
* Initialize a new DWARF frame reader using the provided memory object.
*
* @param mobj The memory object containing frame data (eh_frame or debug_frame) at the start address. This instance must
* survive for the lifetime of the reader.
* @param byteorder The byte order of the data referenced by @a mobj.
* @param m64 True if the target system uses 64-bit pointers, false if it uses 32-bit pointers.
* @param debug_frame If true, interpret the DWARF data as a debug_frame section. Otherwise, the
* frame reader will assume eh_frame data.
*
* @return Returns PLCRASH_ESUCCESS on success, or an appropriate plcrash_error_t value on error.
*/
plcrash_error_t dwarf_frame_reader::init (plcrash_async_mobject_t *mobj,
const plcrash_async_byteorder_t *byteorder,
bool m64,
bool debug_frame)
{
_mobj = mobj;
_byteorder = byteorder;
_debug_frame = debug_frame;
_m64 = m64;
return PLCRASH_ESUCCESS;
}
/**
* Locate the frame descriptor entry for @a pc, if available.
*
* @param offset A section-relative offset at which the FDE search will be initiated. This is primarily useful in combination with the compact unwind
* encoding, in cases where the unwind instructions can not be expressed, and instead a FDE offset is provided by the encoding. Pass an offset of 0
* to begin searching at the beginning of the unwind data.
* @param pc The PC value to search for within the frame data. Note that this value should be the absolute address at which
* the code is loaded into the target process, as the current implementation utilizes relative addressing to perform address
* lookups.
* @param fde_info If the FDE is found, PLFRAME_ESUCCESS will be returned and @a fde_info will be initialized with the
* FDE data. The caller is responsible for freeing the returned FDE record via plcrash_async_dwarf_fde_info_free().
*
* @return Returns PLFRAME_ESUCCCESS on success, or one of the remaining error codes if a DWARF parsing error occurs. If
* the entry can not be found, PLFRAME_ENOTFOUND will be returned.
*/
plcrash_error_t dwarf_frame_reader::find_fde (pl_vm_off_t offset,
pl_vm_address_t pc,
plcrash_async_dwarf_fde_info_t *fde_info)
{
const plcrash_async_byteorder_t *byteorder = _byteorder;
const pl_vm_address_t base_addr = plcrash_async_mobject_base_address(_mobj);
const pl_vm_address_t end_addr = base_addr + plcrash_async_mobject_length(_mobj);
plcrash_error_t err;
/* Apply the FDE offset */
pl_vm_address_t cfi_entry = base_addr;
if (!plcrash_async_address_apply_offset(base_addr, offset, &cfi_entry)) {
PLCF_DEBUG("FDE offset hint overflows the mobject's base address");
return PLCRASH_EINVAL;
}
if (cfi_entry >= end_addr) {
PLCF_DEBUG("FDE base address + offset falls outside the mapped range");
return PLCRASH_EINVAL;
}
/* Iterate over table entries */
while (cfi_entry < end_addr) {
/* Fetch the entry length (and determine wether it's 64-bit or 32-bit) */
uint64_t length;
pl_vm_size_t length_size;
uint8_t dwarf_word_size;
{
uint32_t *length32 = (uint32_t *) plcrash_async_mobject_remap_address(_mobj, cfi_entry, 0x0, sizeof(uint32_t));
if (length32 == NULL) {
PLCF_DEBUG("The current CFI entry 0x%" PRIx64 " header lies outside the mapped range", (uint64_t) cfi_entry);
return PLCRASH_EINVAL;
}
if (byteorder->swap32(*length32) == UINT32_MAX) {
uint64_t *length64 = (uint64_t *) plcrash_async_mobject_remap_address(_mobj, cfi_entry, sizeof(uint32_t), sizeof(uint64_t));
if (length64 == NULL) {
PLCF_DEBUG("The current CFI entry 0x%" PRIx64 " header lies outside the mapped range", (uint64_t) cfi_entry);
return PLCRASH_EINVAL;
}
length = byteorder->swap64(*length64);
length_size = sizeof(uint64_t) + sizeof(uint32_t);
dwarf_word_size = 8; // 64-bit DWARF
} else {
length = byteorder->swap32(*length32);
length_size = sizeof(uint32_t);
dwarf_word_size = 4; // 32-bit DWARF
}
}
/*
* APPLE EXTENSION
* Check for end marker, as per Apple's libunwind-35.1. It's unclear if this is defined by the DWARF 3 or 4 specifications; I could not
* find a reference to it.
* Section 7.2.2 defines 0xfffffff0 - 0xffffffff as being reserved for extensions to the length
* field relative to the DWARF 2 standard. There is no explicit reference to the use of an 0 value.
*
* In section 7.2.1, the value of 0 is defined as being reserved as an error value in the encodings for
* "attribute names, attribute forms, base type encodings, location operations, languages, line number program
* opcodes, macro information entries and tag names to represent an error condition or unknown value."
*
* Section 7.2.2 doesn't justify the usage of 0x0 as a termination marker, but given that Apple's code relies on it,
* we will also do so here.
*/
if (length == 0x0)
return PLCRASH_ENOTFOUND;
/* Calculate the next entry address; the length_size addition is known-safe, as we were able to successfully read the length from *cfi_entry */
pl_vm_address_t next_cfi_entry;
if (!plcrash_async_address_apply_offset(cfi_entry+length_size, (pl_vm_off_t) length, &next_cfi_entry)) {
PLCF_DEBUG("Entry length size overflows the CFI address");
return PLCRASH_EINVAL;
}
/* Fetch the entry id */
uint64_t cie_id;
if ((plcrash_async_dwarf_read_uintmax64(_mobj, byteorder, cfi_entry, length_size, dwarf_word_size, &cie_id)) != PLCRASH_ESUCCESS) {
PLCF_DEBUG("The current CFI entry 0x%" PRIx64 " cie_id lies outside the mapped range", (uint64_t) cfi_entry);
return PLCRASH_EINVAL;
}
/* Check for (and skip) CIE entries. */
{
bool is_cie = false;
/* debug_frame uses UINT?_MAX to denote CIE entries. */
if (_debug_frame && ((dwarf_word_size == 8 && cie_id == UINT64_MAX) || (dwarf_word_size == 4 && cie_id == UINT32_MAX)))
is_cie = true;
/* eh_frame uses a type of 0x0 to denote CIE entries. */
if (!_debug_frame && cie_id == 0x0)
is_cie = true;
/* If not a FDE, skip */
if (is_cie) {
/* Not a FDE -- skip */
cfi_entry = next_cfi_entry;
continue;
}
}
/* Decode the FDE */
if (_m64)
err = plcrash_async_dwarf_fde_info_init<uint64_t>(fde_info, _mobj, byteorder, cfi_entry, _debug_frame);
else
err = plcrash_async_dwarf_fde_info_init<uint32_t>(fde_info, _mobj, byteorder, cfi_entry, _debug_frame);
if (err != PLCRASH_ESUCCESS)
return err;
/* Check if our PC is within range */
if (pc >= fde_info->pc_start && pc < fde_info->pc_end)
return PLCRASH_ESUCCESS;
/* Skip to the next entry */
cfi_entry = next_cfi_entry;
}
return PLCRASH_ENOTFOUND;
}
/*
* @}
*/
#endif /* PLCRASH_FEATURE_UNWIND_DWARF */