Source/dwarf_opstream.hpp (135 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. */ #ifndef PLCRASH_ASYNC_DWARF_OPSTREAM_H #define PLCRASH_ASYNC_DWARF_OPSTREAM_H #include <cstddef> #include "PLCrashAsync.h" #include "PLCrashAsyncMObject.h" #include "PLCrashAsyncDwarfPrimitives.hpp" #include "PLCrashMacros.h" #include "PLCrashFeatureConfig.h" #if PLCRASH_FEATURE_UNWIND_DWARF /** * @ingroup plcrash_async_dwarf_private_opstream * @internal * @{ */ PLCR_CPP_BEGIN_NS namespace async { /** * @internal * A simple opcode stream reader for use with DWARF opcode/CFA evaluation. */ class dwarf_opstream { /** Current position within the op stream */ void *_p; /** Backing memory object */ plcrash_async_mobject_t *_mobj; /** Target-relative starting address within the memory object */ pl_vm_address_t _start; /** Target-relative end address within the memory object */ pl_vm_address_t _end; /** Locally mapped starting address. */ void *_instr; /** Locally mapped ending address. */ void *_instr_max; /** Byte order for the byte stream */ const plcrash_async_byteorder_t *_byteorder; public: plcrash_error_t init (plcrash_async_mobject_t *mobj, const plcrash_async_byteorder_t *byteorder, pl_vm_address_t address, pl_vm_off_t offset, pl_vm_size_t length); template <typename V> inline bool read_intU (V *result); inline bool read_uintmax64 (uint8_t data_size, uint64_t *result); inline bool read_uleb128 (uint64_t *result); inline bool read_sleb128 (int64_t *result); template <typename machine_ptr> inline bool read_gnueh_ptr (gnu_ehptr_reader<machine_ptr> *reader, DW_EH_PE_t encoding, machine_ptr *result); inline bool skip (pl_vm_off_t offset); inline uintptr_t get_position (void); }; /** * Read a value of type and size @a V from the stream, verifying that the read will not overrun * the mapped range and advancing the stream position past the read value. * * @warning Multi-byte values (either 2, 4, or 8 bytes in size) will be byte swapped. * * @param result The destination to which the result will be written. * * @return Returns true on success, or false if the read would exceed the boundry specified by @a maxpos. */ template <typename V> inline bool dwarf_opstream::read_intU (V *result) { if (_p < _instr) return false; if ((uint8_t *)_instr_max - (uint8_t *)_p < sizeof(V)) { return false; } *result = *((V *)_p); switch (sizeof(V)) { case 2: *result = _byteorder->swap16(*result); break; case 4: *result = _byteorder->swap32(*result); break; case 8: *result = _byteorder->swap64(*result); break; default: break; } _p = ((uint8_t *)_p) + sizeof(V); return true; } /** * @internal * * Read a value that is either 1, 2, 4, or 8 bytes in size, applying byte swapping. Verifies that the read * will not overrun the mapped range and advancing the stream position past the read value. * * @param data_size The size of the value to be read. If an unsupported size is supplied, false will be returned. * @param result The destination to which the result will be written. * * @return Returns true on success, or false if the read would exceed the boundry specified by @a maxpos. */ inline bool dwarf_opstream::read_uintmax64 (uint8_t data_size, uint64_t *result) { pl_vm_off_t offset = ((uint8_t *)_p - (uint8_t *)_instr); plcrash_error_t err; if ((err = plcrash_async_dwarf_read_uintmax64(_mobj, _byteorder, _start, offset, data_size, result)) != PLCRASH_ESUCCESS) { PLCF_DEBUG("Read of integer value failed with %u", err); return false; } /* Advance the position */ if (!skip(data_size)) { PLCF_DEBUG("Integer value extends past end of opstream"); return false; } return true; } /** * Read a ULEB128 value from the stream, verifying that the read will not overrun * the mapped range and advancing the stream position past the read value. * * @param result The destination to which the result will be written. * * @return Returns true on success, or false if the read would exceed the boundry specified by @a maxpos. */ inline bool dwarf_opstream::read_uleb128 (uint64_t *result) { plcrash_error_t err; pl_vm_off_t offset = ((uint8_t *)_p - (uint8_t *)_instr); pl_vm_size_t lebsize; if ((err = plcrash_async_dwarf_read_uleb128(_mobj, _start, offset, result, &lebsize)) != PLCRASH_ESUCCESS) { PLCF_DEBUG("Read of ULEB128 value failed with %u", err); return false; } /* Advance the position */ if (!skip(lebsize)) { PLCF_DEBUG("ULEB128 value extends past end of opstream"); return false; } return true; } /** * Read a SLEB128 value from the stream, verifying that the read will not overrun * the mapped range and advancing the stream position past the read value. * * @param result The destination to which the result will be written. * * @return Returns true on success, or false if the read would exceed the boundry specified by @a maxpos. */ inline bool dwarf_opstream::read_sleb128 (int64_t *result) { plcrash_error_t err; pl_vm_off_t offset = ((uint8_t *)_p - (uint8_t *)_instr); pl_vm_size_t lebsize; if ((err = plcrash_async_dwarf_read_sleb128(_mobj, _start, offset, result, &lebsize)) != PLCRASH_ESUCCESS) { PLCF_DEBUG("Read of ULEB128 value failed with %u", err); return false; } /* Advance the position */ if (!skip(lebsize)) { PLCF_DEBUG("SLEB128 value extends past end of opstream"); return false; } return true; } /** * Read a GNU DWARF encoded pointer value from the stream, verifying that the read does not overrun * the mapped range and advancing the stream position past the read value. * * @param reader The GNU eh_frame pointer reader to be used for reading. * @param encoding The pointer encoding to use when decoding the pointer value. * @param result On success, the pointer value. * * @tparam machine_ptr The native pointer word size of the target. */ template <typename machine_ptr> inline bool dwarf_opstream::read_gnueh_ptr (gnu_ehptr_reader<machine_ptr> *reader, DW_EH_PE_t encoding, machine_ptr *result) { pl_vm_off_t offset = ((uint8_t *)_p - (uint8_t *)_instr); plcrash_error_t err; size_t size; /* Perform the read; this will safely handle the case where the target falls outside * of the maximum range */ if ((err = reader->read(_mobj, _start, offset, encoding, result, &size)) != PLCRASH_ESUCCESS) { PLCF_DEBUG("Read of GNU EH pointer value failed with %u", err); return false; } /* Sanity check the size; this should never occur */ // This issue triggers clang's new 'tautological' warnings on some host platforms with some types of pl_vm_off_t. // Testing tautological correctness and *documenting* the issue is the whole point of the check, even though it // may always be true on some hosts. // Since older versions of clang do not support -Wtautological, we have to enable -Wunknown-pragmas first PLCR_PRAGMA_CLANG("clang diagnostic push"); PLCR_PRAGMA_CLANG("clang diagnostic ignored \"-Wunknown-pragmas\""); PLCR_PRAGMA_CLANG("clang diagnostic ignored \"-Wtautological-constant-out-of-range-compare\""); if (size > PL_VM_OFF_MAX) { PLCF_DEBUG("GNU EH pointer size exceeds our maximum supported offset size"); return false; } PLCR_PRAGMA_CLANG("clang diagnostic pop"); /* Advance the position */ if (!skip(size)) { PLCF_DEBUG("GNU EH pointer value extends past end of opstream"); return false; } return true; } /** * Apply the given offset to the instruction position, returning false * if the position falls outside of the bounds of the mapped region. */ inline bool dwarf_opstream::skip (pl_vm_off_t offset) { void *p = ((uint8_t *)_p) + offset; if (p < _instr || p > _instr_max) return false; _p = p; return true; } /** * Return the current pointer position within the opcode stream, relative * to the start of the stream. */ inline uintptr_t dwarf_opstream::get_position (void) { return ((uintptr_t)_p) - ((uintptr_t) _instr); } PLCR_CPP_END_NS } /* * @} */ #endif /* PLCRASH_FEATURE_UNWIND_DWARF */ #endif /* PLCRASH_ASYNC_DWARF_OPSTREAM_H */