#pragma once #include #include #include #include #include #include #include #include #include #include "MemoryMap.hpp" #define OF_WHAT_EXCEPTION(cl_name) \ cl_name: public WhatException { \ public:\ cl_name(const std::string& what): WhatException(what) {} \ cl_name() = default; \ } class DwarfInterpret { /** Singleton-ish class holding a Dwarf interpret. * * There is an instance of this class per object path from the #MemoryMap. * This class' interface helps making it a seamless integration, by * allowing to call a method for a program counter in any object path, and * internally passing the call to the appropriate instance. */ public: // Types, sub-classes, … class WhatException: public std::exception { /** Base exception for other exceptions, not supposed to be thrown * by itself */ std::string what_str; public: /// Initialize the exception with an explanatory text chunk explicit WhatException(const std::string& what) : what_str(what) {} /// Leave the explanatory text empty WhatException(): what_str("") {} /// Get the explanatory text for this exception const char* what() const noexcept { return what_str.c_str(); } }; /** Thrown when trying to create an instance with an invalid ELF file: * this can mean that the file did not exist, was a special path (eg. * [heap]) or that it cannot be read. */ class OF_WHAT_EXCEPTION(InvalidElf); /** Thrown when trying to create an instance with an ELF file that * contains no debug information */ class OF_WHAT_EXCEPTION(NoDebugData); /** Thrown when trying to access a program counter that is not mapped * to an ELF file */ class OF_WHAT_EXCEPTION(NoElfForPC); /** This register was needed, but is not common within DWARF * expressions and thus wasn't unwinded. */ class OF_WHAT_EXCEPTION(ExoticRegister); /** Thrown when trying to get the value of a Dwarf register (column) * that is not defined or has, somehow, no value at this PC. */ class OF_WHAT_EXCEPTION(ValuelessRegister); /// Thrown when accessing unimplemented parts of this library class OF_WHAT_EXCEPTION(NotImplemented); /// Thrown when somehow, getcontext (read CPU registers) fails class OF_WHAT_EXCEPTION(FailedGetContext); /// Thrown when a Dwarf element is not found for a given PC class OF_WHAT_EXCEPTION(NotFound); /// Thrown when trying to unwind a context with nothing more to unwind class FirstUnwindFrame: public std::exception {}; /// A Dwarf register typedef dwarf::core::FrameSection::register_def DwarfRegister; /// A Dwarf row of registers (for a given PC) typedef std::set > DwarfRow; /// The value type of a register's contents typedef uintptr_t reg_content_t; /** An unwind context, holding registers. * * The registers kept here should be enough for most of the unwinding * processes. To fully emulate libunwind, we would have to save every * register that is overwritten and not caller-saved. * * You'll most probably want to instanciate such a structure using * #DwarfInterpret::get_current_unwind_context. */ struct UnwindContext { // Let's pretend this is enough uintptr_t rip; uintptr_t rsp; uintptr_t rbp; /// This context's `RIP` minus its ELF file load offset uintptr_t elf_local_rip() const; }; public: // methods DwarfInterpret(DwarfInterpret const&) = delete; void operator=(DwarfInterpret const&) = delete; /** Acquire the anonymous instance of DwarfInterpret, which works on * the empty memory range. Acts as a handle: passes every call to some * other instance */ static DwarfInterpret& acquire(); /** Acquire the relevant instance of #DwarfInterpret for the given * program counter. Instanciate it if needed. */ static DwarfInterpret& acquire(uintptr_t pc); /** Get the Dwarf registers row at the given PC. * * \throws NotFound if for some reason, the Dwarf row cannot be * accessed at this PC. */ DwarfRow dwarf_row_at(uintptr_t pc) const; /** Retrieves the value pointed to by the given Dwarf register * * \throws ValuelessRegister */ reg_content_t interpret_dw_register( const DwarfRow& row, const DwarfRegister& reg, const UnwindContext& ctx ) const; /** Retrieves the value pointed to by the given Dwarf register * * \throws ValuelessRegister */ reg_content_t interpret_dw_register( const DwarfRow& row, int reg_id, const UnwindContext& ctx ) const; /** Get the current UnwindContext (from the caller's point of view) * * **WARNING!** This context will be valid within the call frame of the * caller, and within descendant calls from this function, but will * most probably be invalid and result in weird behaviours if there is * only a single `return` made! * * This is supposed to work: * ``` * void foo() { * UnwindContext context = get_current_unwind_context(); * // have fun with context * } * ``` * * While this will probably crash: * ``` * UnwindContext wrap_get_context() { * return f(get_current_unwind_context()); * } * void foo() { * UnwindContext context = wrap_get_context(); * // have fun with context * } * ``` */ static UnwindContext get_current_unwind_context(); /** Unwinds once the given context * * \throws FirstUnwindFrame when trying to unwind the first frame. */ UnwindContext unwind_context(const UnwindContext& ctx); /** Get the offset for the instruction pointer. * * This offset is such that `actual_rip - pc_offset` is the ELF-local * PC (ie. what readelf gives). */ uintptr_t get_pc_offset() const { return pc_offset; } private: DwarfInterpret(const MemoryMap::MapEntry& memory_object); DwarfRegister get_column(const DwarfRow& row, int column) const; const dwarf::core::FrameSection::fde_iterator fde_at( uintptr_t pc) const; const dwarf::core::FrameSection::cie_iterator cie_at( uintptr_t pc) const; /** Get the #DwarfInterpret instance responsible for the given PC, or * nullptr if the current instance is responsible. */ DwarfInterpret* get_responsible_instance(uintptr_t pc) const; private: // members static MemoryMap memory_map; // This map maps a `memory_map` memory region index to some instance of // a DwarfInterpret. The special index -1 is mapped to the anonymous // instance. static std::map > instances; MemoryMap::MapEntry map_entry; std::unique_ptr root_die; uintptr_t pc_offset; friend class std::unique_ptr; };