diff --git a/stack_walker/stack_walker.cpp b/stack_walker/stack_walker.cpp index f8feece..43b3d9a 100644 --- a/stack_walker/stack_walker.cpp +++ b/stack_walker/stack_walker.cpp @@ -214,6 +214,9 @@ _fde_func_t fde_handler_for_pc(uintptr_t pc, MemoryMapEntry& mmap_entry) { } 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); if(mmap_entry == nullptr) return false; @@ -225,8 +228,6 @@ bool unwind_context(unwind_context_t& ctx) { uintptr_t tr_pc = ctx.rip - mmap_entry->beg; ctx = fde_func(ctx, tr_pc); - if(ctx.rbp == 0 || ctx.rip + 1 == 0) - return false; return true; } @@ -236,3 +237,15 @@ void walk_stack(const std::function& mapped) { mapped(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); +} diff --git a/stack_walker/stack_walker.hpp b/stack_walker/stack_walker.hpp index 970ebe1..f6f8e89 100644 --- a/stack_walker/stack_walker.hpp +++ b/stack_walker/stack_walker.hpp @@ -9,6 +9,12 @@ #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. * @@ -35,3 +41,7 @@ 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& 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); diff --git a/stack_walker_libunwind/Makefile b/stack_walker_libunwind/Makefile new file mode 100644 index 0000000..15107b2 --- /dev/null +++ b/stack_walker_libunwind/Makefile @@ -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 diff --git a/stack_walker_libunwind/stack_walker.cpp b/stack_walker_libunwind/stack_walker.cpp new file mode 100644 index 0000000..791758e --- /dev/null +++ b/stack_walker_libunwind/stack_walker.cpp @@ -0,0 +1,96 @@ +#include "../stack_walker/stack_walker.hpp" + +#include +#include +#include + +struct UnwPtrs { + unw_context_t* ctx; + unw_cursor_t* cursor; +}; + +static std::vector 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& 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; +} diff --git a/stack_walker_libunwind/stack_walker.hpp b/stack_walker_libunwind/stack_walker.hpp new file mode 100644 index 0000000..970ebe1 --- /dev/null +++ b/stack_walker_libunwind/stack_walker.hpp @@ -0,0 +1,37 @@ +/** Provides stack walking facilities exploiting the eh_elf objects, that can + * be loaded at runtime. */ + + +#pragma once + +#include +#include + +#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& mapped); diff --git a/tests/Makefile b/tests/Makefile index f43f31a..68eeeba 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,10 +1,22 @@ CXX=g++ 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 - LD_RUN_PATH=eh_elfs.$* $(CXX) $(CXXFLAGS) -o $@ $^ \ + LD_RUN_PATH=eh_elfs.$*:../stack_walker \ + $(CXX) $(CXXFLAGS) -o $@ $^ \ -L../stack_walker -ldl -lstack_walker.$* eh_elfs.per_func: stack_walked.per_func.bin diff --git a/tests/stack_walked.cpp b/tests/stack_walked.cpp index 3c0c73f..8c626a7 100644 --- a/tests/stack_walked.cpp +++ b/tests/stack_walked.cpp @@ -14,9 +14,9 @@ void stack_filled() { printf("#%d - %s — %%rip = 0x%lx, %%rbp = 0x%lx, %%rsp = 0x%lx\n", ++frame_id, (dl_rc != 0) ? func_info.dli_sname : "[Unknown func]", - ctx.rip, - ctx.rbp, - ctx.rsp); + get_register(ctx, SW_REG_RIP), + get_register(ctx, SW_REG_RBP), + get_register(ctx, SW_REG_RSP)); }); }