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:
parent
6854eb56e4
commit
df2d6179a8
7 changed files with 193 additions and 7 deletions
|
@ -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<void(const unwind_context_t&)>& 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);
|
||||
}
|
||||
|
|
|
@ -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<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);
|
||||
|
|
18
stack_walker_libunwind/Makefile
Normal file
18
stack_walker_libunwind/Makefile
Normal 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
|
96
stack_walker_libunwind/stack_walker.cpp
Normal file
96
stack_walker_libunwind/stack_walker.cpp
Normal 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;
|
||||
}
|
37
stack_walker_libunwind/stack_walker.hpp
Normal file
37
stack_walker_libunwind/stack_walker.hpp
Normal 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);
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue