Actually able to unwind the stack

This is filled with debug prints, and is quite brutal: it saves the
whole stack. It has to be optimized a lot.

Also, needs a smooth stop when trying to unwind main.
This commit is contained in:
Théophile Bastian 2018-04-05 19:17:02 +02:00
parent 0bd5a40bce
commit 97032ee31b
6 changed files with 201 additions and 21 deletions

View file

@ -1,6 +1,6 @@
LIB_DIR=lib
TARGET=$(LIB_DIR)/libdwarfinterpret.so
SRC=src/DwarfInterpret.cpp src/MemoryMap.cpp
SRC=src/DwarfInterpret.cpp src/MemoryMap.cpp src/StackDump.cpp
INCLUDE_DIR=include

View file

@ -12,6 +12,7 @@
#include <dwarfpp/root.hpp>
#include "MemoryMap.hpp"
#include "StackDump.hpp"
#define OF_WHAT_EXCEPTION(cl_name) \
cl_name: public WhatException { \
@ -64,6 +65,10 @@ class DwarfInterpret {
* 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);
@ -87,6 +92,18 @@ class DwarfInterpret {
/// The value type of a register's contents
typedef uintptr_t reg_content_t;
/// An unwind context, holding registers
struct UnwindContext {
UnwindContext(const StackDump& dump): stack(dump) {}
// Let's pretend this is enough
StackDump stack;
uintptr_t rip;
uintptr_t rsp;
uintptr_t rbp;
};
public: // methods
DwarfInterpret(DwarfInterpret const&) = delete;
void operator=(DwarfInterpret const&) = delete;
@ -104,14 +121,15 @@ class DwarfInterpret {
*
* \throws NotFound if for some reason, the Dwarf row cannot be
* accessed at this PC. */
const DwarfRow& dwarf_row_at(uintptr_t pc) const;
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 DwarfRegister& reg,
const UnwindContext& ctx
) const;
/** Retrieves the value pointed to by the given Dwarf register
@ -119,7 +137,8 @@ class DwarfInterpret {
* \throws ValuelessRegister */
reg_content_t interpret_dw_register(
const DwarfRow& row,
int reg_id
int reg_id,
const UnwindContext& ctx
) const;
/** Get the return address at a given program counter, assuming the
@ -132,6 +151,12 @@ class DwarfInterpret {
/// Get the current program counter
static uintptr_t get_current_pc();
/// Get the current UnwindContext (from the caller's point of view)
static UnwindContext get_current_unwind_context();
/// Unwinds once the given context
UnwindContext unwind_context(const UnwindContext& ctx);
private:
DwarfInterpret(const MemoryMap::MapEntry& memory_object);

View file

@ -0,0 +1,28 @@
#pragma once
#include <memory>
#include <cstdint>
#include <dwarfinterpret/MemoryMap.hpp>
class StackDump {
public:
static StackDump snapshot(uintptr_t rsp); ///< Take an instant snapshot
StackDump(const StackDump& oth); ///< copy
StackDump& operator=(const StackDump& oth); ///< copy
template <typename T> T deref(uintptr_t pos) const {
return *((T*)(stack.get() + pos - offset));
}
uintptr_t at(uintptr_t pos) const {
return deref<uintptr_t>(pos);
}
private:
StackDump();
typedef char cell_t;
std::shared_ptr<cell_t> stack;
uintptr_t offset; ///< such that stack[stack_addr - offset] is ok
};

View file

@ -5,6 +5,7 @@
#include <iomanip>
#include <vector>
#include <set>
#include <cstring>
#include <fileno.hpp>
#include <dwarfpp/lib.hpp>
@ -101,7 +102,8 @@ DwarfInterpret& DwarfInterpret::acquire(uintptr_t pc) {
DwarfInterpret::reg_content_t DwarfInterpret::interpret_dw_register(
const DwarfInterpret::DwarfRow& row,
const DwarfInterpret::DwarfRegister& reg
const DwarfInterpret::DwarfRegister& reg,
const UnwindContext& ctx
) const
{
switch(reg.k) {
@ -113,21 +115,48 @@ DwarfInterpret::reg_content_t DwarfInterpret::interpret_dw_register(
ostringstream exprs;
int dwarf_regnum = reg.register_plus_offset_r().first;
assert(dwarf_regnum != DW_FRAME_CFA_COL3);
int cpu_regnum = REG_DWARF_TO_UCONTEXT[dwarf_regnum];
reg_content_t* cpu_content =
(reg_content_t*) get_cpu_register(cpu_regnum);
reg_content_t* addr =
reg_content_t cpu_content = 9;
cerr << "@@ Interpreting register ";
switch(dwarf_regnum) {
case lib::DWARF_X86_64_RIP:
cerr << "RIP";
cpu_content = (reg_content_t) ctx.rip;
break;
case lib::DWARF_X86_64_RBP:
cerr << "RBP";
cpu_content = (reg_content_t) ctx.rbp;
break;
case lib::DWARF_X86_64_RSP:
cerr << "RSP";
cpu_content = (reg_content_t) ctx.rsp;
break;
default:
throw ExoticRegister(to_string(dwarf_regnum));
}
reg_content_t addr =
cpu_content + reg.register_plus_offset_r().second;
return *addr;
cerr << " = " << hex
<< cpu_content << " + offset = " << addr //<< " -> " << *addr
<< dec << endl;
return addr;
} break;
case core::FrameSection::register_def::SAVED_AT_OFFSET_FROM_CFA: {
reg_content_t* cfa_loc =
(reg_content_t*) interpret_dw_register(row, DW_FRAME_CFA_COL3);
reg_content_t* addr = cfa_loc;
return *addr;
reg_content_t cfa_loc =
interpret_dw_register(row, DW_FRAME_CFA_COL3, ctx);
int cfa_offset = reg.saved_at_offset_from_cfa_r();
reg_content_t addr = cfa_loc + cfa_offset;
reg_content_t value = ctx.stack.deref<reg_content_t>(addr);
cerr << "@@ Interpreting CFA offset: CFA is " << hex
<< cfa_loc << " + offset " << dec << cfa_offset << hex
<< " = " << addr
<< " -> " << value
<< dec << endl;
return value;
} break;
case core::FrameSection::register_def::SAVED_AT_EXPR:
@ -143,10 +172,11 @@ DwarfInterpret::reg_content_t DwarfInterpret::interpret_dw_register(
DwarfInterpret::reg_content_t DwarfInterpret::interpret_dw_register(
const DwarfInterpret::DwarfRow& row,
int reg_id
int reg_id,
const UnwindContext& ctx
) const
{
return interpret_dw_register(row, get_column(row, reg_id));
return interpret_dw_register(row, get_column(row, reg_id), ctx);
}
uintptr_t DwarfInterpret::get_return_address(uintptr_t cur_pc) const {
@ -157,8 +187,12 @@ uintptr_t DwarfInterpret::get_return_address(uintptr_t cur_pc) const {
const core::Cie& cie = *cie_at(cur_pc);
const DwarfRow& row = dwarf_row_at(cur_pc);
UnwindContext ctx = get_current_unwind_context();
// FIXME ^^^ ugly patch, this should not be a thing
uintptr_t translated_ra =
interpret_dw_register(row, cie.get_return_address_register_rule());
interpret_dw_register(row,
cie.get_return_address_register_rule(),
ctx);
cerr << "Return address from 0x" << hex << cur_pc << ": "
<< "0x" << translated_ra << endl;
return translated_ra;
@ -180,6 +214,61 @@ uintptr_t DwarfInterpret::get_current_pc() {
return dw.get_return_address(pc_here);
}
DwarfInterpret::UnwindContext DwarfInterpret::get_current_unwind_context() {
// FIXME for now this returns SOME unwind context (actually, the unwind
// context snapshot naively taken from inside this function). Unwinding
// it some number of times should yield the expected context
uintptr_t rsp = DwarfInterpret::acquire().get_cpu_register(REG_RSP);
UnwindContext ctx(StackDump::snapshot(rsp));
DwarfInterpret& dw = DwarfInterpret::acquire();
ctx.rip = dw.get_cpu_register(REG_RIP);
ctx.rsp = rsp;
ctx.rbp = dw.get_cpu_register(REG_RBP);
cerr << "CREATING CONTEXT. %rsp=0x" << hex
<< ctx.rsp
<< ", %rbp=0x" << ctx.rbp
<< ", %rip=0x" << ctx.rip
<< dec << endl;
return ctx;
}
DwarfInterpret::UnwindContext DwarfInterpret::unwind_context(
const DwarfInterpret::UnwindContext& ctx
)
{
DwarfInterpret* responsible = get_responsible_instance(ctx.rip);
if(responsible != nullptr)
return responsible->unwind_context(ctx);
DwarfRow cur_row = dwarf_row_at(ctx.rip);
const core::Cie& cie = *cie_at(ctx.rip);
UnwindContext new_context(ctx.stack);
cerr << "Obtaining previous context as reg "
<< cie.get_return_address_register_rule()
<< " at current IP = "
<< hex << ctx.rip << endl;
new_context.rip = interpret_dw_register(
cur_row,
cie.get_return_address_register_rule(),
ctx);
cerr << "Yielding " << hex << new_context.rip << dec << endl;
new_context.rbp = interpret_dw_register(
cur_row,
lib::DWARF_X86_64_RBP,
ctx);
new_context.rsp = interpret_dw_register(
cur_row,
DW_FRAME_CFA_COL3,
ctx);
return new_context;
}
uintptr_t DwarfInterpret::get_caller_pc() const {
// We assume we want the PC of the caller of the calling function. This
// means we have to unwind twice. `get_current_pc` unwinds once.
@ -268,7 +357,7 @@ const core::FrameSection::cie_iterator DwarfInterpret::cie_at(
return cie_it;
}
const DwarfInterpret::DwarfRow& DwarfInterpret::dwarf_row_at(
DwarfInterpret::DwarfRow DwarfInterpret::dwarf_row_at(
uintptr_t pc
) const
{

View file

@ -88,9 +88,7 @@ size_t MemoryMap::id_of_address(uintptr_t addr) const {
MemoryRegion reg_index(addr, addr);
auto bound_it = rev_addr_map.lower_bound(reg_index);
if(bound_it == rev_addr_map.end())
throw std::out_of_range("No region containing this address");
if(bound_it->first.contains(addr))
if(bound_it != rev_addr_map.end() && bound_it->first.contains(addr))
return bound_it->second;
if(bound_it != rev_addr_map.begin()) {
--bound_it;

40
src/StackDump.cpp Normal file
View file

@ -0,0 +1,40 @@
#include <dwarfinterpret/StackDump.hpp>
#include <dwarfinterpret/MemoryMap.hpp>
#include <cassert>
#include <iostream> // FIXME
#include <cstring>
using namespace std;
StackDump StackDump::snapshot(uintptr_t rsp) {
StackDump stack;
MemoryMap memory_map;
const MemoryMap::MapEntry& stack_region =
memory_map[memory_map.id_of_address(rsp)];
assert(stack_region.pathname == "[stack]");
size_t stack_size = stack_region.mem_region.end - rsp;
stack.stack = std::shared_ptr<cell_t>(new cell_t[stack_size]);
cerr << "memcpy'ing " << stack_size << " bytes" << endl;
memcpy(stack.stack.get(), (void*)rsp, stack_size); // FIXME way too brutal
stack.offset = rsp;
return stack;
}
StackDump::StackDump()
: stack(nullptr), offset(0)
{}
StackDump::StackDump(const StackDump& oth) {
this->operator=(oth);
}
StackDump& StackDump::operator=(const StackDump& oth) {
stack = oth.stack;
offset = oth.offset;
return *this;
}