Add a libunwind-powered stack_walker

Preserves stack_walker's interface (one can link against stack_walker or
stack_walker_unwind indifferently).
This commit is contained in:
Théophile Bastian 2018-05-18 11:33:39 +02:00
parent 6854eb56e4
commit df2d6179a8
7 changed files with 193 additions and 7 deletions

View file

@ -214,6 +214,9 @@ _fde_func_t fde_handler_for_pc(uintptr_t pc, MemoryMapEntry& mmap_entry) {
} }
bool unwind_context(unwind_context_t& ctx) { bool unwind_context(unwind_context_t& ctx) {
if(ctx.rbp == 0 || ctx.rip + 1 == 0)
return false;
MemoryMapEntry* mmap_entry = get_mmap_entry(ctx.rip); MemoryMapEntry* mmap_entry = get_mmap_entry(ctx.rip);
if(mmap_entry == nullptr) if(mmap_entry == nullptr)
return false; return false;
@ -225,8 +228,6 @@ bool unwind_context(unwind_context_t& ctx) {
uintptr_t tr_pc = ctx.rip - mmap_entry->beg; uintptr_t tr_pc = ctx.rip - mmap_entry->beg;
ctx = fde_func(ctx, tr_pc); ctx = fde_func(ctx, tr_pc);
if(ctx.rbp == 0 || ctx.rip + 1 == 0)
return false;
return true; return true;
} }
@ -236,3 +237,15 @@ void walk_stack(const std::function<void(const unwind_context_t&)>& mapped) {
mapped(ctx); mapped(ctx);
} while(unwind_context(ctx)); } while(unwind_context(ctx));
} }
uintptr_t get_register(const unwind_context_t& ctx, StackWalkerRegisters reg) {
switch(reg) {
case SW_REG_RIP:
return ctx.rip;
case SW_REG_RSP:
return ctx.rsp;
case SW_REG_RBP:
return ctx.rbp;
}
assert(0);
}

View file

@ -9,6 +9,12 @@
#include "../shared/context_struct.h" #include "../shared/context_struct.h"
/** Handled registers list */
enum StackWalkerRegisters {
SW_REG_RIP,
SW_REG_RSP,
SW_REG_RBP
};
/** Initialize the stack walker. This must be called only once. /** Initialize the stack walker. This must be called only once.
* *
@ -35,3 +41,7 @@ bool unwind_context(unwind_context_t& ctx);
/** Call the passed function once per frame in the call stack, most recent /** Call the passed function once per frame in the call stack, most recent
* frame first, with the current context as its sole argument. */ * frame first, with the current context as its sole argument. */
void walk_stack(const std::function<void(const unwind_context_t&)>& mapped); void walk_stack(const std::function<void(const unwind_context_t&)>& mapped);
/** Get a register's value on an unwind_context_t. This is useful for other
* implementations of stack_walker that use different unwind_context_t */
uintptr_t get_register(const unwind_context_t& ctx, StackWalkerRegisters reg);

View file

@ -0,0 +1,18 @@
CXX=g++
CXXLIBS=-lunwind -lunwind-x86_64
CXXFLAGS=-O2 -fPIC -Wall -Wextra -rdynamic
TARGET=libstack_walker.so
OBJS=stack_walker.o
all: $(TARGET)
$(TARGET): $(OBJS)
$(CXX) $(CXXFLAGS) -shared $< $(CXXLIBS) -o $@
%.o: %.cpp
$(CXX) $(CXXFLAGS) -c $< -o $@
.PHONY: clean
clean:
rm -f $(OBJS) $(TARGET_BASE)*.so

View file

@ -0,0 +1,96 @@
#include "../stack_walker/stack_walker.hpp"
#include <libunwind.h>
#include <vector>
#include <cassert>
struct UnwPtrs {
unw_context_t* ctx;
unw_cursor_t* cursor;
};
static std::vector<UnwPtrs> to_delete;
bool stack_walker_init() {
return true;
}
void stack_walker_close() {
for(auto& del: to_delete) {
delete del.ctx;
delete del.cursor;
}
}
static unw_cursor_t* get_cursor(const unwind_context_t& context) {
// This is subtly dirty.
// unwind_context_t::rip is uint64_t, thus suitable for a pointer.
return (unw_cursor_t*) context.rip;
}
static void set_cursor(unwind_context_t& context, unw_cursor_t* unw_cursor) {
// This is subtly dirty.
// unwind_context_t::rip is uint64_t, thus suitable for a pointer.
context.rip = (uintptr_t) unw_cursor;
}
unwind_context_t get_context() {
int rc;
unw_context_t* unw_context = new unw_context_t;
rc = unw_getcontext(unw_context);
if(rc < 0)
assert(0);
unw_cursor_t* cursor = new unw_cursor_t;
rc = unw_init_local(cursor, unw_context);
if(rc < 0)
assert(0);
rc = unw_step(cursor);
if(rc < 0)
assert(0);
unwind_context_t out;
set_cursor(out, cursor);
to_delete.push_back(UnwPtrs({unw_context, cursor}));
return out;
}
bool unwind_context(unwind_context_t& ctx) {
int rc = unw_step(get_cursor(ctx));
return rc > 0;
}
void walk_stack(const std::function<void(const unwind_context_t&)>& mapped) {
unwind_context_t context = get_context();
do {
mapped(context);
} while(unwind_context(context));
}
uintptr_t get_register(const unwind_context_t& ctx, StackWalkerRegisters reg) {
unw_cursor_t* cursor = get_cursor(ctx);
unw_regnum_t regnum = 0;
switch(reg) {
case SW_REG_RIP:
regnum = UNW_X86_64_RIP;
break;
case SW_REG_RSP:
regnum = UNW_X86_64_RSP;
break;
case SW_REG_RBP:
regnum = UNW_X86_64_RBP;
break;
}
uintptr_t out;
int rc = unw_get_reg(cursor, regnum, &out);
if(rc < 0)
assert(false);
return out;
}

View file

@ -0,0 +1,37 @@
/** Provides stack walking facilities exploiting the eh_elf objects, that can
* be loaded at runtime. */
#pragma once
#include <cstdint>
#include <functional>
#include "../shared/context_struct.h"
/** Initialize the stack walker. This must be called only once.
*
* \return true iff everything was correctly initialized.
*/
bool stack_walker_init();
/** Deallocate everything that was allocated by the stack walker */
void stack_walker_close();
/** Get the unwind context of the point from which this function was called.
*
* This context must then be exploited straight away: it is unsafe to alter the
* call stack before using it, in particular by returning from the calling
* function. */
unwind_context_t get_context();
/** Unwind the passed context once, in place.
*
* Returns `true` if the context was actually unwinded, or `false` if the end
* of the call stack was reached. */
bool unwind_context(unwind_context_t& ctx);
/** Call the passed function once per frame in the call stack, most recent
* frame first, with the current context as its sole argument. */
void walk_stack(const std::function<void(const unwind_context_t&)>& mapped);

View file

@ -1,10 +1,22 @@
CXX=g++ CXX=g++
CXXFLAGS=-Wall -Wextra -O0 -g -I../stack_walker -rdynamic CXXFLAGS=-Wall -Wextra -O0 -g -I../stack_walker -rdynamic
all: stack_walked.per_func.bin stack_walked.global.bin TARGET_BASE=stack_walked
TARGETS= \
$(TARGET_BASE).per_func.bin \
$(TARGET_BASE).global.bin \
$(TARGET_BASE).libunwind.bin
all: $(TARGETS)
stack_walked.libunwind.bin: stack_walked.cpp
LD_RUN_PATH=../stack_walker_libunwind \
$(CXX) $(CXXFLAGS) -o $@ $^ \
-L../stack_walker_libunwind -ldl -lstack_walker
stack_walked.%.bin: stack_walked.cpp stack_walked.%.bin: stack_walked.cpp
LD_RUN_PATH=eh_elfs.$* $(CXX) $(CXXFLAGS) -o $@ $^ \ LD_RUN_PATH=eh_elfs.$*:../stack_walker \
$(CXX) $(CXXFLAGS) -o $@ $^ \
-L../stack_walker -ldl -lstack_walker.$* -L../stack_walker -ldl -lstack_walker.$*
eh_elfs.per_func: stack_walked.per_func.bin eh_elfs.per_func: stack_walked.per_func.bin

View file

@ -14,9 +14,9 @@ void stack_filled() {
printf("#%d - %s — %%rip = 0x%lx, %%rbp = 0x%lx, %%rsp = 0x%lx\n", printf("#%d - %s — %%rip = 0x%lx, %%rbp = 0x%lx, %%rsp = 0x%lx\n",
++frame_id, ++frame_id,
(dl_rc != 0) ? func_info.dli_sname : "[Unknown func]", (dl_rc != 0) ? func_info.dli_sname : "[Unknown func]",
ctx.rip, get_register(ctx, SW_REG_RIP),
ctx.rbp, get_register(ctx, SW_REG_RBP),
ctx.rsp); get_register(ctx, SW_REG_RSP));
}); });
} }